diff --git a/THANKS b/THANKS index 720fb1d5..b889b977 100644 --- a/THANKS +++ b/THANKS @@ -2,6 +2,7 @@ The Geary team would like to thank the following contributors: Robert Ancell Christian Dywan +Timo Kluck Charles Lindsay Matthew Pirocchi diff --git a/src/client/geary-controller.vala b/src/client/geary-controller.vala index 7f8a83ec..f16ef90a 100644 --- a/src/client/geary-controller.vala +++ b/src/client/geary-controller.vala @@ -20,17 +20,17 @@ public class GearyController { } } - private class FetchSpecialFolderOperation : Geary.NonblockingBatchOperation { + private class FetchFolderOperation : Geary.NonblockingBatchOperation { public Geary.Account account; - public Geary.SpecialFolder special_folder; + public Geary.FolderPath folder_path; - public FetchSpecialFolderOperation(Geary.Account account, Geary.SpecialFolder special_folder) { + public FetchFolderOperation(Geary.Account account, Geary.FolderPath folder_path) { this.account = account; - this.special_folder = special_folder; + this.folder_path = folder_path; } public override async Object? execute_async(Cancellable? cancellable) throws Error { - return yield account.fetch_folder_async(special_folder.path); + return yield account.fetch_folder_async(folder_path); } } @@ -311,7 +311,7 @@ public class GearyController { if (account.get_account_information().service_provider == Geary.ServiceProvider.YAHOO) main_window.title = GearyApplication.NAME + "!"; - main_window.folder_list.set_user_folders_root_name(account.get_user_folders_label()); + main_window.folder_list.set_user_folders_root_name(_("Labels")); load_folders.begin(cancellable_folder); } } @@ -327,48 +327,6 @@ public class GearyController { private async void load_folders(Cancellable? cancellable) { try { - // add all the special folders, which are assumed to always exist - Geary.SpecialFolderMap? special_folders = account.get_special_folder_map(); - if (special_folders != null) { - Geary.NonblockingBatch batch = new Geary.NonblockingBatch(); - foreach (Geary.SpecialFolder special_folder in special_folders.get_all()) - batch.add(new FetchSpecialFolderOperation(account, special_folder)); - - debug("Listing special folders"); - yield batch.execute_all_async(cancellable); - debug("Completed list of special folders"); - - foreach (int id in batch.get_ids()) { - FetchSpecialFolderOperation op = (FetchSpecialFolderOperation) - batch.get_operation(id); - try { - Geary.Folder folder = (Geary.Folder) batch.get_result(id); - main_window.folder_list.add_special_folder(op.special_folder, folder); - } catch (Error inner_error) { - message("Unable to fetch special folder %s: %s", - op.special_folder.path.to_string(), inner_error.message); - } - } - - if (cancellable.is_cancelled()) - return; - - // If inbox is available (should be!), monitor it and select it for the user - Geary.SpecialFolder? inbox = special_folders.get_folder(Geary.SpecialFolderType.INBOX); - if (inbox != null) { - // create and leave open the Inbox, which is constantly monitored for notifications - inbox_folder = yield account.fetch_folder_async(inbox.path, cancellable_inbox); - assert(inbox_folder != null); - - yield inbox_folder.open_async(false, cancellable_inbox); - - inbox_folder.email_locally_appended.connect(on_inbox_new_email); - - // select the inbox and get the show started - main_window.folder_list.select_path(inbox.path); - } - } - // pull down the root-level user folders and recursively add to sidebar Gee.Collection folders = yield account.list_folders_async(null); if (folders != null) @@ -730,31 +688,35 @@ public class GearyController { set_busy(false); } + private void on_special_folder_type_changed(Geary.Folder folder, Geary.SpecialFolderType old_type, + Geary.SpecialFolderType new_type) { + main_window.folder_list.remove_folder(folder); + main_window.folder_list.add_folder(folder); + } + private void on_folders_added_removed(Gee.Collection? added, Gee.Collection? removed) { if (added != null && added.size > 0) { - Gee.Set? ignored_paths = account.get_ignored_paths(); - - Gee.ArrayList skipped = new Gee.ArrayList(); foreach (Geary.Folder folder in added) { - if (ignored_paths != null && ignored_paths.contains(folder.get_path())) - skipped.add(folder); - else { - main_window.folder_list.add_folder(folder); - main_window.main_toolbar.copy_folder_menu.add_folder(folder); - main_window.main_toolbar.move_folder_menu.add_folder(folder); + main_window.folder_list.add_folder(folder); + main_window.main_toolbar.copy_folder_menu.add_folder(folder); + main_window.main_toolbar.move_folder_menu.add_folder(folder); + + // monitor the Inbox for notifications + if (folder.get_special_folder_type() == Geary.SpecialFolderType.INBOX && inbox_folder == null) { + inbox_folder = folder; + inbox_folder.email_locally_appended.connect(on_inbox_new_email); + + // select the inbox and get the show started + main_window.folder_list.select_path(folder.get_path()); + inbox_folder.open_async.begin(false, cancellable_inbox); } + + folder.special_folder_type_changed.connect(on_special_folder_type_changed); } - Gee.Collection remaining = added; - if (skipped.size > 0) { - remaining = new Gee.ArrayList(); - remaining.add_all(added); - remaining.remove_all(skipped); - } - - search_folders_for_children.begin(remaining); + search_folders_for_children.begin(added); } } diff --git a/src/client/ui/folder-list.vala b/src/client/ui/folder-list.vala index 646d3750..58bceb9b 100644 --- a/src/client/ui/folder-list.vala +++ b/src/client/ui/folder-list.vala @@ -11,8 +11,10 @@ public class FolderList : Sidebar.Tree { }; private class SpecialFolderBranch : Sidebar.RootOnlyBranch { - public SpecialFolderBranch(Geary.SpecialFolder special, Geary.Folder folder) { - base(new SpecialFolderEntry(special, folder)); + public SpecialFolderBranch(Geary.Folder folder) { + base(new FolderEntry(folder)); + + assert(folder.get_special_folder_type() != Geary.SpecialFolderType.NONE); } } @@ -25,51 +27,18 @@ public class FolderList : Sidebar.Tree { } public virtual string get_sidebar_name() { - return folder.get_path().basename; + return folder.get_display_name(); } public string? get_sidebar_tooltip() { return null; } - public virtual Icon? get_sidebar_icon() { - return IconFactory.instance.label_icon; - } - - public virtual string to_string() { - return "FolderEntry: " + get_sidebar_name(); - } - - public bool internal_drop_received(Gdk.DragContext context, Gtk.SelectionData data) { - // Copy or move? - Gdk.ModifierType mask; - double[] axes = new double[2]; - context.get_device().get_state(context.get_dest_window(), axes, out mask); - MainWindow main_window = GearyApplication.instance.get_main_window() as MainWindow; - if ((mask & Gdk.ModifierType.CONTROL_MASK) != 0) { - main_window.folder_list.copy_conversation(folder); - } else { - main_window.folder_list.move_conversation(folder); - } - - return true; - } - } - - private class SpecialFolderEntry : FolderEntry { - public Geary.SpecialFolder special { get; private set; } - - public SpecialFolderEntry(Geary.SpecialFolder special, Geary.Folder folder) { - base (folder); - this.special = special; - } - - public override string get_sidebar_name() { - return special.name; - } - - public override Icon? get_sidebar_icon() { - switch (special.folder_type) { + public Icon? get_sidebar_icon() { + switch (folder.get_special_folder_type()) { + case Geary.SpecialFolderType.NONE: + return IconFactory.instance.label_icon; + case Geary.SpecialFolderType.INBOX: return new ThemedIcon("mail-inbox"); @@ -99,8 +68,23 @@ public class FolderList : Sidebar.Tree { } } - public override string to_string() { - return "SpecialFolderEntry: " + get_sidebar_name(); + public virtual string to_string() { + return "FolderEntry: " + get_sidebar_name(); + } + + public bool internal_drop_received(Gdk.DragContext context, Gtk.SelectionData data) { + // Copy or move? + Gdk.ModifierType mask; + double[] axes = new double[2]; + context.get_device().get_state(context.get_dest_window(), axes, out mask); + MainWindow main_window = GearyApplication.instance.get_main_window() as MainWindow; + if ((mask & Gdk.ModifierType.CONTROL_MASK) != 0) { + main_window.folder_list.copy_conversation(folder); + } else { + main_window.folder_list.move_conversation(folder); + } + + return true; } } @@ -112,6 +96,8 @@ public class FolderList : Sidebar.Tree { private Sidebar.Branch user_folder_branch; internal Gee.HashMap entries = new Gee.HashMap< Geary.FolderPath, Sidebar.Entry>(Geary.Hashable.hash_func, Geary.Equalable.equal_func); + internal Gee.HashMap branches = new Gee.HashMap< + Geary.FolderPath, Sidebar.Branch>(Geary.Hashable.hash_func, Geary.Equalable.equal_func); public FolderList() { base(new Gtk.TargetEntry[0], Gdk.DragAction.ASK, drop_handler); @@ -136,9 +122,7 @@ public class FolderList : Sidebar.Tree { } private void on_entry_selected(Sidebar.SelectableEntry selectable) { - if (selectable is SpecialFolderEntry) { - folder_selected(((SpecialFolderEntry) selectable).folder); - } else if (selectable is FolderEntry) { + if (selectable is FolderEntry) { folder_selected(((FolderEntry) selectable).folder); } } @@ -154,31 +138,51 @@ public class FolderList : Sidebar.Tree { } public void add_folder(Geary.Folder folder) { - FolderEntry folder_entry = new FolderEntry(folder); - bool added = false; - if (folder.get_path().get_parent() == null) { + + Geary.SpecialFolderType special_folder_type = folder.get_special_folder_type(); + if (special_folder_type != Geary.SpecialFolderType.NONE) { + SpecialFolderBranch branch = new SpecialFolderBranch(folder); + graft(branch, (int) special_folder_type); + entries.set(folder.get_path(), branch.get_root()); + branches.set(folder.get_path(), branch); + added = true; + } else if (folder.get_path().get_parent() == null) { // Top-level folder. + FolderEntry folder_entry = new FolderEntry(folder); user_folder_branch.graft(user_folder_group, folder_entry); + entries.set(folder.get_path(), folder_entry); + branches.set(folder.get_path(), user_folder_branch); added = true; } else { + FolderEntry folder_entry = new FolderEntry(folder); Sidebar.Entry? entry = get_entry_for_folder_path(folder.get_path().get_parent()); if (entry != null) { user_folder_branch.graft(entry, folder_entry); + entries.set(folder.get_path(), folder_entry); + branches.set(folder.get_path(), user_folder_branch); added = true; } } - if (added) - entries.set(folder.get_path(), folder_entry); - else - debug("Could not add folder to folder list: %s", folder.to_string()); + if (!added) { + debug("Could not add folder %s of type %s to folder list", folder.to_string(), + special_folder_type.to_string()); + } } - - public void add_special_folder(Geary.SpecialFolder special, Geary.Folder folder) { - SpecialFolderBranch branch = new SpecialFolderBranch(special, folder); - graft(branch, (int) special.folder_type); - entries.set(folder.get_path(), branch.get_root()); + + public void remove_folder(Geary.Folder folder) { + Sidebar.Entry? entry = get_entry_for_folder_path(folder.get_path()); + Sidebar.Branch? branch = get_branch_for_folder_path(folder.get_path()); + if(entry != null && branch != null) { + if (branch is SpecialFolderBranch) { + this.prune(branch); + } else { + branch.prune(entry); + } + } else { + debug(@"Could not remove folder $(folder.get_path())"); + } } public void remove_all_branches() { @@ -197,6 +201,10 @@ public class FolderList : Sidebar.Tree { return entries.get(path); } + private Sidebar.Branch? get_branch_for_folder_path(Geary.FolderPath path) { + return branches.get(path); + } + public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) { // Run the base version first. bool ret = base.drag_motion(context, x, y, time); diff --git a/src/console/main.vala b/src/console/main.vala index db96582b..94cfbdb1 100644 --- a/src/console/main.vala +++ b/src/console/main.vala @@ -150,11 +150,8 @@ class ImapConsole : Gtk.Window { break; case "list": - list(cmd, args); - break; - case "xlist": - xlist(cmd, args); + list(cmd, args); break; case "examine": @@ -362,7 +359,7 @@ class ImapConsole : Gtk.Window { check_connected(cmd, args, 2, " "); status("Listing..."); - cx.send_async.begin(new Geary.Imap.ListCommand.wildcarded(args[0], args[1]), + cx.send_async.begin(new Geary.Imap.ListCommand.wildcarded(args[0], args[1], (cmd.down() == "xlist")), null, on_list); } @@ -375,13 +372,6 @@ class ImapConsole : Gtk.Window { } } - private void xlist(string cmd, string[] args) throws Error { - check_connected(cmd, args, 2, " "); - - status("Xlisting..."); - cx.send_async.begin(new Geary.Imap.XListCommand.wildcarded(args[0], args[1]), null, on_list); - } - private void examine(string cmd, string[] args) throws Error { check_connected(cmd, args, 1, ""); diff --git a/src/dbusservice/controller.vala b/src/dbusservice/controller.vala index d3a79f21..321a41a5 100644 --- a/src/dbusservice/controller.vala +++ b/src/dbusservice/controller.vala @@ -44,10 +44,19 @@ public class Geary.DBus.Controller { account.report_problem.connect(on_report_problem); // Open the Inbox folder. - Geary.SpecialFolderMap? special_folders = account.get_special_folder_map(); - Geary.Folder folder = yield account.fetch_folder_async(special_folders.get_folder( - Geary.SpecialFolderType.INBOX).path); + Geary.Folder? folder = null; + Gee.Collection folders = yield account.list_folders_async(null, null); + foreach(Geary.Folder folder_to_check in folders) { + if(folder_to_check.get_special_folder_type() == Geary.SpecialFolderType.INBOX) { + folder = folder_to_check; + break; + } + } + if (folder == null) { + warning("No inbox folder found"); + return; + } yield folder.open_async(false, null); conversations = new Geary.DBus.Conversations(folder); diff --git a/src/engine/api/geary-engine-account.vala b/src/engine/api/geary-engine-account.vala index 2f8ad9ce..a862a7f8 100644 --- a/src/engine/api/geary-engine-account.vala +++ b/src/engine/api/geary-engine-account.vala @@ -18,12 +18,6 @@ public abstract class Geary.EngineAccount : Geary.AbstractAccount, Geary.Persona return account_information; } - public abstract string get_user_folders_label(); - - public abstract Geary.SpecialFolderMap? get_special_folder_map(); - - public abstract Gee.Set? get_ignored_paths(); - public abstract bool delete_is_archive(); public abstract async void send_email_async(Geary.ComposedEmail composed, Cancellable? cancellable = null) diff --git a/src/engine/api/geary-folder-path.vala b/src/engine/api/geary-folder-path.vala index 2772dd7c..b88253ea 100644 --- a/src/engine/api/geary-folder-path.vala +++ b/src/engine/api/geary-folder-path.vala @@ -150,7 +150,6 @@ public class Geary.FolderPath : Object, Hashable, Equalable { if (other.get_path_length() != path_length) return false; - bool cs = get_root().case_sensitive; if (other.get_root().case_sensitive != cs) { message("Comparing %s and %s with different case sensitivities", to_string(), diff --git a/src/engine/api/geary-folder.vala b/src/engine/api/geary-folder.vala index f31e6f8d..7f45e9d9 100644 --- a/src/engine/api/geary-folder.vala +++ b/src/engine/api/geary-folder.vala @@ -154,6 +154,15 @@ public interface Geary.Folder : Object { * mark_email_async() method as well as changes occur remotely. */ public signal void email_flags_changed(Gee.Map map); + + /** + * "special-folder-type-changed" is fired when the special folder type has changed. + * + * This will usually happen when the local account object has been updated with data + * from the remote account. + */ + public signal void special_folder_type_changed(Geary.SpecialFolderType old_type, + Geary.SpecialFolderType new_type); protected abstract void notify_opened(OpenState state, int count); @@ -172,15 +181,22 @@ public interface Geary.Folder : Object { protected abstract void notify_email_flags_changed(Gee.Map flag_map); + protected abstract void notify_special_folder_type_changed(Geary.SpecialFolderType old_type, + Geary.SpecialFolderType new_type); + public abstract Geary.FolderPath get_path(); public abstract Geary.Trillian has_children(); /** - * Returns the special folder type of the folder. If the the folder is not a special one then - * null is returned. + * Returns the special folder type of the folder. */ - public abstract Geary.SpecialFolderType? get_special_folder_type(); + public abstract Geary.SpecialFolderType get_special_folder_type(); + + /** + * Returns a name suitable for displaying to the user. + */ + public abstract string get_display_name(); /** * Returns the state of the Folder's connections to the local and remote stores. diff --git a/src/engine/api/geary-personality.vala b/src/engine/api/geary-personality.vala index 383e06e0..62c2b2c1 100644 --- a/src/engine/api/geary-personality.vala +++ b/src/engine/api/geary-personality.vala @@ -5,12 +5,6 @@ */ public interface Geary.Personality : Object { - public abstract string get_user_folders_label(); - - public abstract Geary.SpecialFolderMap? get_special_folder_map(); - - public abstract Gee.Set? get_ignored_paths(); - public abstract bool delete_is_archive(); } diff --git a/src/engine/api/geary-special-folder.vala b/src/engine/api/geary-special-folder.vala index f30b9894..32a8c38f 100644 --- a/src/engine/api/geary-special-folder.vala +++ b/src/engine/api/geary-special-folder.vala @@ -5,6 +5,7 @@ */ public enum Geary.SpecialFolderType { + NONE, INBOX, DRAFTS, SENT, @@ -12,53 +13,38 @@ public enum Geary.SpecialFolderType { ALL_MAIL, SPAM, TRASH, - OUTBOX -} - -public class Geary.SpecialFolder : Object { - public SpecialFolderType folder_type { get; private set; } - public string name { get; private set; } - public Geary.FolderPath path { get; private set; } - public int ordering { get; private set; } + OUTBOX; - public SpecialFolder(SpecialFolderType folder_type, string name, FolderPath path, int ordering) { - this.folder_type = folder_type; - this.name = name; - this.path = path; - this.ordering = ordering; - } -} - -public class Geary.SpecialFolderMap : Object { - private Gee.HashMap map = new Gee.HashMap(); - - public SpecialFolderMap() { - } - - public void set_folder(SpecialFolder special_folder) { - map.set(special_folder.folder_type, special_folder); - } - - public SpecialFolder? get_folder(SpecialFolderType folder_type) { - return map.get(folder_type); - } - - public SpecialFolder? get_folder_by_path(FolderPath path) { - foreach (SpecialFolder folder in map.values) { - if (folder.path == path) { - return folder; - } + public unowned string get_display_name() { + switch (this) { + case INBOX: + return _("Inbox"); + + case DRAFTS: + return _("Drafts"); + + case SENT: + return _("Sent Mail"); + + case FLAGGED: + return _("Starred"); + + case ALL_MAIL: + return _("All Mail"); + + case SPAM: + return _("Spam"); + + case TRASH: + return _("Trash"); + + case OUTBOX: + return _("Outbox"); + + case NONE: + default: + return _("None"); } - return null; - } - - public Gee.Set get_supported_types() { - return map.keys.read_only_view; - } - - public Gee.Collection get_all() { - return map.values.read_only_view; } } diff --git a/src/engine/imap/command/imap-commands.vala b/src/engine/imap/command/imap-commands.vala index 698c45b5..b6bf9b7d 100644 --- a/src/engine/imap/command/imap-commands.vala +++ b/src/engine/imap/command/imap-commands.vala @@ -42,25 +42,14 @@ public class Geary.Imap.LogoutCommand : Command { public class Geary.Imap.ListCommand : Command { public const string NAME = "list"; + public const string XLIST_NAME = "xlist"; - public ListCommand(string mailbox) { - base (NAME, { "", mailbox }); + public ListCommand(string mailbox, bool use_xlist) { + base (use_xlist ? XLIST_NAME : NAME, { "", mailbox }); } - public ListCommand.wildcarded(string reference, string mailbox) { - base (NAME, { reference, mailbox }); - } -} - -public class Geary.Imap.XListCommand : Command { - public const string NAME = "xlist"; - - public XListCommand(string mailbox) { - base (NAME, { "", mailbox }); - } - - public XListCommand.wildcarded(string reference, string mailbox) { - base (NAME, { reference, mailbox }); + public ListCommand.wildcarded(string reference, string mailbox, bool use_xlist) { + base (use_xlist ? XLIST_NAME : NAME, { reference, mailbox }); } } diff --git a/src/engine/imap/decoders/imap-list-results.vala b/src/engine/imap/decoders/imap-list-results.vala index fac2bd15..ae99ad73 100644 --- a/src/engine/imap/decoders/imap-list-results.vala +++ b/src/engine/imap/decoders/imap-list-results.vala @@ -78,7 +78,7 @@ public class Geary.Imap.ListResults : Geary.Imap.CommandResults { StringParameter? delim = data.get_as_nullable_string(3); StringParameter mailbox = data.get_as_string(4); - if (!cmd.equals_ci(ListCommand.NAME) && !cmd.equals_ci(XListCommand.NAME)) { + if (!cmd.equals_ci(ListCommand.NAME) && !cmd.equals_ci(ListCommand.XLIST_NAME)) { debug("Bad list response \"%s\": Not marked as list or xlist response", data.to_string()); @@ -98,9 +98,17 @@ public class Geary.Imap.ListResults : Geary.Imap.CommandResults { attrlist.add(new MailboxAttribute(stringp.value)); } - MailboxInformation info = new MailboxInformation(mailbox.value, delim.nullable_value, - new MailboxAttributes(attrlist)); - + // Set \Inbox to standard path + MailboxInformation info; + MailboxAttributes attributes = new MailboxAttributes(attrlist); + if (Geary.Imap.MailboxAttribute.SPECIAL_FOLDER_INBOX in attributes) { + info = new MailboxInformation(Geary.Imap.Account.INBOX_NAME, delim.nullable_value, + attributes); + } else { + info = new MailboxInformation(mailbox.value, delim.nullable_value, + attributes); + } + map.set(mailbox.value, info); list.add(info); } catch (ImapError ierr) { diff --git a/src/engine/imap/message/imap-flag.vala b/src/engine/imap/message/imap-flag.vala index 98e675fc..0ddb76f9 100644 --- a/src/engine/imap/message/imap-flag.vala +++ b/src/engine/imap/message/imap-flag.vala @@ -178,6 +178,62 @@ public class Geary.Imap.MailboxAttribute : Geary.Imap.Flag { return _allows_new; } } + private static MailboxAttribute? _xlist_inbox = null; + public static MailboxAttribute SPECIAL_FOLDER_INBOX { get { + if (_xlist_inbox == null) + _xlist_inbox = new MailboxAttribute("\\Inbox"); + + return _xlist_inbox; + } } + + private static MailboxAttribute? _xlist_all_mail = null; + public static MailboxAttribute SPECIAL_FOLDER_ALL_MAIL { get { + if (_xlist_all_mail == null) + _xlist_all_mail = new MailboxAttribute("\\AllMail"); + + return _xlist_all_mail; + } } + + private static MailboxAttribute? _xlist_trash = null; + public static MailboxAttribute SPECIAL_FOLDER_TRASH { get { + if (_xlist_trash == null) + _xlist_trash = new MailboxAttribute("\\Trash"); + + return _xlist_trash; + } } + + private static MailboxAttribute? _xlist_drafts = null; + public static MailboxAttribute SPECIAL_FOLDER_DRAFTS { get { + if (_xlist_drafts == null) + _xlist_drafts = new MailboxAttribute("\\Drafts"); + + return _xlist_drafts; + } } + + private static MailboxAttribute? _xlist_sent = null; + public static MailboxAttribute SPECIAL_FOLDER_SENT { get { + if (_xlist_sent == null) + _xlist_sent = new MailboxAttribute("\\Sent"); + + return _xlist_sent; + } } + + private static MailboxAttribute? _xlist_spam = null; + public static MailboxAttribute SPECIAL_FOLDER_SPAM { get { + if (_xlist_spam == null) + _xlist_spam = new MailboxAttribute("\\Spam"); + + return _xlist_spam; + } } + + private static MailboxAttribute? _xlist_starred = null; + public static MailboxAttribute SPECIAL_FOLDER_STARRED { get { + if (_xlist_starred == null) + _xlist_starred = new MailboxAttribute("\\Starred"); + + return _xlist_starred; + } } + public MailboxAttribute(string value) { base (value); } diff --git a/src/engine/imap/message/imap-message-data.vala b/src/engine/imap/message/imap-message-data.vala index 77ddb370..60ce8928 100644 --- a/src/engine/imap/message/imap-message-data.vala +++ b/src/engine/imap/message/imap-message-data.vala @@ -183,6 +183,31 @@ public class Geary.Imap.MailboxAttributes : Geary.Imap.Flags { return new MailboxAttributes(attrs); } + + public Geary.SpecialFolderType get_special_folder_type() { + if (contains(MailboxAttribute.SPECIAL_FOLDER_INBOX)) + return Geary.SpecialFolderType.INBOX; + + if (contains(MailboxAttribute.SPECIAL_FOLDER_ALL_MAIL)) + return Geary.SpecialFolderType.ALL_MAIL; + + if (contains(MailboxAttribute.SPECIAL_FOLDER_TRASH)) + return Geary.SpecialFolderType.TRASH; + + if (contains(MailboxAttribute.SPECIAL_FOLDER_DRAFTS)) + return Geary.SpecialFolderType.DRAFTS; + + if (contains(MailboxAttribute.SPECIAL_FOLDER_SENT)) + return Geary.SpecialFolderType.SENT; + + if (contains(MailboxAttribute.SPECIAL_FOLDER_SPAM)) + return Geary.SpecialFolderType.SPAM; + + if (contains(MailboxAttribute.SPECIAL_FOLDER_STARRED)) + return Geary.SpecialFolderType.FLAGGED; + + return Geary.SpecialFolderType.NONE; + } } public class Geary.Imap.InternalDate : Geary.RFC822.Date, Geary.Imap.MessageData { diff --git a/src/engine/imap/transport/imap-client-session-manager.vala b/src/engine/imap/transport/imap-client-session-manager.vala index 9e3b7191..d317012c 100644 --- a/src/engine/imap/transport/imap-client-session-manager.vala +++ b/src/engine/imap/transport/imap-client-session-manager.vala @@ -97,7 +97,8 @@ public class Geary.Imap.ClientSessionManager { ClientSession session = yield get_authorized_session_async(cancellable); ListResults results = ListResults.decode(yield session.send_command_async( - new ListCommand.wildcarded("", "%"), cancellable)); + new ListCommand.wildcarded("", "%", session.get_capabilities().has_capability("XLIST")), + cancellable)); if (results.status_response.status != Status.OK) throw new ImapError.SERVER_ERROR("Server error: %s", results.to_string()); @@ -114,7 +115,8 @@ public class Geary.Imap.ClientSessionManager { ClientSession session = yield get_authorized_session_async(cancellable); ListResults results = ListResults.decode(yield session.send_command_async( - new ListCommand(specifier), cancellable)); + new ListCommand(specifier, session.get_capabilities().has_capability("XLIST")), + cancellable)); if (results.status_response.status != Status.OK) throw new ImapError.SERVER_ERROR("Server error: %s", results.to_string()); @@ -126,7 +128,8 @@ public class Geary.Imap.ClientSessionManager { ClientSession session = yield get_authorized_session_async(cancellable); ListResults results = ListResults.decode(yield session.send_command_async( - new ListCommand(path), cancellable)); + new ListCommand(path, session.get_capabilities().has_capability("XLIST")), + cancellable)); return (results.status_response.status == Status.OK) && (results.get_count() == 1); } @@ -136,7 +139,8 @@ public class Geary.Imap.ClientSessionManager { ClientSession session = yield get_authorized_session_async(cancellable); ListResults results = ListResults.decode(yield session.send_command_async( - new ListCommand(path), cancellable)); + new ListCommand(path, session.get_capabilities().has_capability("XLIST")), + cancellable)); if (results.status_response.status != Status.OK) throw new ImapError.SERVER_ERROR("Server error: %s", results.to_string()); diff --git a/src/engine/impl/geary-abstract-folder.vala b/src/engine/impl/geary-abstract-folder.vala index 0ec086e1..7af7c912 100644 --- a/src/engine/impl/geary-abstract-folder.vala +++ b/src/engine/impl/geary-abstract-folder.vala @@ -43,11 +43,26 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder { email_flags_changed(flag_map); } + internal virtual void notify_special_folder_type_changed(Geary.SpecialFolderType old_type, + Geary.SpecialFolderType new_type) { + special_folder_type_changed(old_type, new_type); + } + public abstract Geary.FolderPath get_path(); public abstract Geary.Trillian has_children(); - public abstract Geary.SpecialFolderType? get_special_folder_type(); + public abstract Geary.SpecialFolderType get_special_folder_type(); + + /** + * Default is to display the basename of the Folder's path. + */ + public virtual string get_display_name() { + Geary.SpecialFolderType special_folder_type = get_special_folder_type(); + + return (special_folder_type == Geary.SpecialFolderType.NONE) + ? get_path().basename : special_folder_type.get_display_name(); + } public abstract Geary.Folder.OpenState get_open_state(); diff --git a/src/engine/impl/geary-generic-imap-account.vala b/src/engine/impl/geary-generic-imap-account.vala index 840e4940..f33f87e7 100644 --- a/src/engine/impl/geary-generic-imap-account.vala +++ b/src/engine/impl/geary-generic-imap-account.vala @@ -5,6 +5,9 @@ */ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { + private static Geary.FolderPath? inbox_path = null; + private static Geary.FolderPath? outbox_path = null; + private Imap.Account remote; private Sqlite.Account local; private Gee.HashMap properties_map = new Gee.HashMap< @@ -12,6 +15,8 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { private SmtpOutboxFolder? outbox = null; private Gee.HashMap existing_folders = new Gee.HashMap< FolderPath, GenericImapFolder>(Hashable.hash_func, Equalable.equal_func); + private Gee.HashSet local_only = new Gee.HashSet( + Hashable.hash_func, Equalable.equal_func); public GenericImapAccount(string name, string username, AccountInformation? account_info, File user_data_dir, Imap.Account remote, Sqlite.Account local) { @@ -21,6 +26,16 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { this.local = local; this.remote.login_failed.connect(on_login_failed); + + if (inbox_path == null) { + inbox_path = new Geary.FolderRoot(Imap.Account.INBOX_NAME, Imap.Account.ASSUMED_SEPARATOR, + Imap.Folder.CASE_SENSITIVE); + } + + if (outbox_path == null) { + outbox_path = new SmtpOutboxFolderRoot(); + local_only.add(outbox_path); + } } internal Imap.FolderProperties? get_properties_for_folder(FolderPath path) { @@ -75,13 +90,33 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { throw remote_err; } + // Subclasses should implement this for hardcoded paths that correspond to special folders ... + // if the server supports XLIST, this doesn't have to be implemented. + // + // This won't be called for INBOX or the Outbox. + protected virtual Geary.SpecialFolderType get_special_folder_type_for_path(Geary.FolderPath path) { + return Geary.SpecialFolderType.NONE; + } + + private Geary.SpecialFolderType internal_get_special_folder_type_for_path(Geary.FolderPath path) { + if (path.equals(inbox_path)) + return Geary.SpecialFolderType.INBOX; + + if (path.equals(outbox_path)) + return Geary.SpecialFolderType.OUTBOX; + + return get_special_folder_type_for_path(path); + } + private GenericImapFolder build_folder(Sqlite.Folder local_folder) { GenericImapFolder? folder = existing_folders.get(local_folder.get_path()); if (folder != null) return folder; - folder = new GenericImapFolder(this, remote, local, local_folder, - get_special_folder(local_folder.get_path())); + folder = new GenericImapFolder(this, remote, local, local_folder); + if (folder.get_special_folder_type() == Geary.SpecialFolderType.NONE) + folder.set_special_folder_type(internal_get_special_folder_type_for_path(local_folder.get_path())); + existing_folders.set(folder.get_path(), folder); return folder; @@ -104,8 +139,11 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { engine_list.add(build_folder(local_folder)); } + // Add Outbox to root + if (parent == null) + engine_list.add(outbox); + background_update_folders.begin(parent, engine_list, cancellable); - engine_list.add(outbox); return engine_list; } @@ -163,27 +201,49 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { return; } - Gee.Set local_names = new Gee.HashSet(); - foreach (Geary.Folder folder in engine_folders) - local_names.add(folder.get_path().basename); - - Gee.Set remote_names = new Gee.HashSet(); - foreach (Geary.Imap.Folder folder in remote_folders) { - remote_names.add(folder.get_path().basename); - - // use this iteration to add discovered properties to map - properties_map.set(folder.get_path(), folder.get_properties()); + // update all remote folders properties in the local store and active in the system + foreach (Imap.Folder remote_folder in remote_folders) { + try { + yield local.update_folder_async(remote_folder, cancellable); + } catch (Error update_error) { + debug("Unable to update local folder %s with remote properties: %s", + remote_folder.to_string(), update_error.message); + } } + // Get local paths of all engine (local) folders + Gee.Set local_paths = new Gee.HashSet( + Geary.Hashable.hash_func, Geary.Equalable.equal_func); + foreach (Geary.Folder local_folder in engine_folders) + local_paths.add(local_folder.get_path()); + + // Get remote paths of all remote folders + Gee.Set remote_paths = new Gee.HashSet( + Geary.Hashable.hash_func, Geary.Equalable.equal_func); + foreach (Geary.Imap.Folder remote_folder in remote_folders) { + remote_paths.add(remote_folder.get_path()); + + // use this iteration to add discovered properties to map + properties_map.set(remote_folder.get_path(), remote_folder.get_properties()); + + // also use this iteration to set the local folder's special type + GenericImapFolder? local_folder = existing_folders.get(remote_folder.get_path()); + if (local_folder != null) + local_folder.set_special_folder_type(remote_folder.get_properties().attrs.get_special_folder_type()); + } + + // If path in remote but not local, need to add it Gee.List to_add = new Gee.ArrayList(); foreach (Geary.Imap.Folder folder in remote_folders) { - if (!local_names.contains(folder.get_path().basename)) + if (!local_paths.contains(folder.get_path())) to_add.add(folder); } + // If path in local but not remote (and isn't local-only, i.e. the Outbox), need to remove + // it Gee.List? to_remove = new Gee.ArrayList(); foreach (Geary.Folder folder in engine_folders) { - if (!remote_names.contains(folder.get_path().basename)) + if (!remote_paths.contains(folder.get_path()) && !local_only.contains(folder.get_path())) to_remove.add(folder); } @@ -193,6 +253,7 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { if (to_remove.size == 0) to_remove = null; + // For folders to add, clone them and their properties locally if (to_add != null) { foreach (Geary.Imap.Folder folder in to_add) { try { @@ -204,6 +265,7 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { } } + // Create Geary.Folder objects for all added folders Gee.Collection engine_added = null; if (to_add != null) { engine_added = new Gee.ArrayList(); @@ -217,18 +279,17 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { } } + // TODO: Remove local folders no longer available remotely. + if (to_remove != null) { + foreach (Geary.Folder folder in to_remove) { + debug(@"Need to remove folder $folder"); + } + } + if (engine_added != null) notify_folders_added_removed(engine_added, null); } - public override string get_user_folders_label() { - return _("Folders"); - } - - public override Gee.Set? get_ignored_paths() { - return null; - } - public override bool delete_is_archive() { return false; } @@ -242,12 +303,5 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount { private void on_login_failed(Geary.Credentials? credentials) { notify_report_problem(Geary.Account.Problem.LOGIN_FAILED, credentials, null); } - - private SpecialFolder? get_special_folder(FolderPath path) { - if (get_special_folder_map() != null) { - return get_special_folder_map().get_folder_by_path(path); - } - return null; - } } diff --git a/src/engine/impl/geary-generic-imap-folder.vala b/src/engine/impl/geary-generic-imap-folder.vala index c54c3334..c8ee91c4 100644 --- a/src/engine/impl/geary-generic-imap-folder.vala +++ b/src/engine/impl/geary-generic-imap-folder.vala @@ -12,7 +12,6 @@ private class Geary.GenericImapFolder : Geary.AbstractFolder { internal Sqlite.Folder local_folder { get; protected set; } internal Imap.Folder? remote_folder { get; protected set; default = null; } - internal SpecialFolder? special_folder { get; protected set; default = null; } internal int remote_count { get; private set; default = -1; } private weak GenericImapAccount account; @@ -20,18 +19,19 @@ private class Geary.GenericImapFolder : Geary.AbstractFolder { private Sqlite.Account local; private EmailFlagWatcher email_flag_watcher; private EmailPrefetcher email_prefetcher; + private SpecialFolderType special_folder_type; private bool opened = false; private NonblockingSemaphore remote_semaphore; private ReplayQueue? replay_queue = null; private NonblockingMutex normalize_email_positions_mutex = new NonblockingMutex(); public GenericImapFolder(GenericImapAccount account, Imap.Account remote, Sqlite.Account local, - Sqlite.Folder local_folder, SpecialFolder? special_folder) { + Sqlite.Folder local_folder) { this.account = account; this.remote = remote; this.local = local; this.local_folder = local_folder; - this.special_folder = special_folder; + this.special_folder_type = local_folder.get_properties().attrs.get_special_folder_type(); email_flag_watcher = new EmailFlagWatcher(this); email_flag_watcher.email_flags_changed.connect(on_email_flags_changed); @@ -48,12 +48,18 @@ private class Geary.GenericImapFolder : Geary.AbstractFolder { return local_folder.get_path(); } - public override Geary.SpecialFolderType? get_special_folder_type() { - if (special_folder == null) { - return null; - } else { - return special_folder.folder_type; - } + public override Geary.SpecialFolderType get_special_folder_type() { + return special_folder_type; + } + + public void set_special_folder_type(SpecialFolderType new_type) { + if (special_folder_type == new_type) + return; + + Geary.SpecialFolderType old_type = special_folder_type; + special_folder_type = new_type; + + notify_special_folder_type_changed(old_type, new_type); } private Imap.FolderProperties? get_folder_properties() { diff --git a/src/engine/impl/geary-gmail-account.vala b/src/engine/impl/geary-gmail-account.vala index abdfcdf5..6d5cb4ea 100644 --- a/src/engine/impl/geary-gmail-account.vala +++ b/src/engine/impl/geary-gmail-account.vala @@ -6,6 +6,7 @@ private class Geary.GmailAccount : Geary.GenericImapAccount { private const string GMAIL_FOLDER = "[Gmail]"; + private const string GOOGLEMAIL_FOLDER = "[Google Mail]"; private static Geary.Endpoint? _imap_endpoint = null; public static Geary.Endpoint IMAP_ENDPOINT { get { @@ -33,58 +34,9 @@ private class Geary.GmailAccount : Geary.GenericImapAccount { return _smtp_endpoint; } } - private static SpecialFolderMap? special_folder_map = null; - private static Gee.Set? ignored_paths = null; - public GmailAccount(string name, string username, AccountInformation account_info, File user_data_dir, Imap.Account remote, Sqlite.Account local) { base (name, username, account_info, user_data_dir, remote, local); - - if (special_folder_map == null || ignored_paths == null) - initialize_personality(); - } - - private static void initialize_personality() { - Geary.FolderPath gmail_root = new Geary.FolderRoot(GMAIL_FOLDER, Imap.Account.ASSUMED_SEPARATOR, - true); - Geary.FolderRoot inbox_folder = new Geary.FolderRoot(Imap.Account.INBOX_NAME, - Imap.Account.ASSUMED_SEPARATOR, false); - Geary.FolderRoot outbox_folder = new SmtpOutboxFolderRoot(); - - special_folder_map = new SpecialFolderMap(); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.INBOX, _("Inbox"), - inbox_folder, 0)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.DRAFTS, _("Drafts"), - gmail_root.get_child("Drafts"), 1)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.SENT, _("Sent Mail"), - gmail_root.get_child("Sent Mail"), 2)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.FLAGGED, _("Starred"), - gmail_root.get_child("Starred"), 3)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.ALL_MAIL, _("All Mail"), - gmail_root.get_child("All Mail"), 4)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.SPAM, _("Spam"), - gmail_root.get_child("Spam"), 5)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.OUTBOX, - _("Outbox"), outbox_folder, 6)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.TRASH, _("Trash"), - gmail_root.get_child("Trash"), 7)); - - ignored_paths = new Gee.HashSet(Hashable.hash_func, Equalable.equal_func); - ignored_paths.add(gmail_root); - ignored_paths.add(inbox_folder); - ignored_paths.add(outbox_folder); - } - - public override string get_user_folders_label() { - return _("Labels"); - } - - public override Geary.SpecialFolderMap? get_special_folder_map() { - return special_folder_map; - } - - public override Gee.Set? get_ignored_paths() { - return ignored_paths.read_only_view; } public override bool delete_is_archive() { diff --git a/src/engine/impl/geary-other-account.vala b/src/engine/impl/geary-other-account.vala index 18a87d68..01f55be7 100644 --- a/src/engine/impl/geary-other-account.vala +++ b/src/engine/impl/geary-other-account.vala @@ -10,16 +10,8 @@ private class Geary.OtherAccount : Geary.GenericImapAccount { base (name, username, account_info, user_data_dir, remote, local); } - public override string get_user_folders_label() { - return _("Folders"); - } - - public override Geary.SpecialFolderMap? get_special_folder_map() { - return null; - } - - public override Gee.Set? get_ignored_paths() { - return null; + protected override Geary.SpecialFolderType get_special_folder_type_for_path(Geary.FolderPath path) { + return Geary.SpecialFolderType.NONE; } public override bool delete_is_archive() { diff --git a/src/engine/impl/geary-yahoo-account.vala b/src/engine/impl/geary-yahoo-account.vala index 589686ca..affeae85 100644 --- a/src/engine/impl/geary-yahoo-account.vala +++ b/src/engine/impl/geary-yahoo-account.vala @@ -31,62 +31,28 @@ private class Geary.YahooAccount : Geary.GenericImapAccount { return _smtp_endpoint; } } - private static SpecialFolderMap? special_folder_map = null; - private static Gee.Set? ignored_paths = null; + private Gee.HashMap special_map = new Gee.HashMap< + Geary.FolderPath, Geary.SpecialFolderType>(Hashable.hash_func, Equalable.equal_func); public YahooAccount(string name, string username, AccountInformation account_info, File user_data_dir, Imap.Account remote, Sqlite.Account local) { base (name, username, account_info, user_data_dir, remote, local); - if (special_folder_map == null || ignored_paths == null) - initialize_personality(); - } - - private static void initialize_personality() { - special_folder_map = new SpecialFolderMap(); + FolderPath sent = new Geary.FolderRoot("Sent", Imap.Account.ASSUMED_SEPARATOR, false); + special_map.set(sent, Geary.SpecialFolderType.SENT); - FolderRoot inbox_folder = new FolderRoot(Imap.Account.INBOX_NAME, - Imap.Account.ASSUMED_SEPARATOR, false); - FolderRoot sent_folder = new Geary.FolderRoot("Sent", Imap.Account.ASSUMED_SEPARATOR, false); - FolderRoot drafts_folder = new Geary.FolderRoot("Draft", Imap.Account.ASSUMED_SEPARATOR, - false); - FolderRoot spam_folder = new Geary.FolderRoot("Bulk Mail", Imap.Account.ASSUMED_SEPARATOR, - false); - FolderRoot trash_folder = new Geary.FolderRoot("Trash", Imap.Account.ASSUMED_SEPARATOR, false); - FolderRoot outbox_folder = new SmtpOutboxFolderRoot(); + FolderPath drafts = new Geary.FolderRoot("Draft", Imap.Account.ASSUMED_SEPARATOR, false); + special_map.set(drafts, Geary.SpecialFolderType.DRAFTS); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.INBOX, _("Inbox"), - inbox_folder, 0)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.DRAFTS, _("Drafts"), - drafts_folder, 1)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.SENT, _("Sent Mail"), - sent_folder, 2)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.SPAM, _("Spam"), - spam_folder, 3)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.OUTBOX, - _("Outbox"), outbox_folder, 4)); - special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.TRASH, _("Trash"), - trash_folder, 5)); + FolderPath bulk = new Geary.FolderRoot("Bulk Mail", Imap.Account.ASSUMED_SEPARATOR, false); + special_map.set(bulk, Geary.SpecialFolderType.SPAM); - ignored_paths = new Gee.HashSet(Hashable.hash_func, Equalable.equal_func); - ignored_paths.add(inbox_folder); - ignored_paths.add(drafts_folder); - ignored_paths.add(sent_folder); - ignored_paths.add(spam_folder); - ignored_paths.add(outbox_folder); - ignored_paths.add(trash_folder); + FolderPath trash = new Geary.FolderRoot("Trash", Imap.Account.ASSUMED_SEPARATOR, false); + special_map.set(trash, Geary.SpecialFolderType.TRASH); } - public override string get_user_folders_label() { - return _("Folders"); - } - - public override Geary.SpecialFolderMap? get_special_folder_map() { - return special_folder_map; - } - - public override Gee.Set? get_ignored_paths() { - return ignored_paths; + protected override Geary.SpecialFolderType get_special_folder_type_for_path(Geary.FolderPath path) { + return special_map.has_key(path) ? special_map.get(path) : Geary.SpecialFolderType.NONE; } public override bool delete_is_archive() { diff --git a/src/engine/impl/outbox/smtp-outbox-folder.vala b/src/engine/impl/outbox/smtp-outbox-folder.vala index 77ae6f09..3ff8ccf5 100644 --- a/src/engine/impl/outbox/smtp-outbox-folder.vala +++ b/src/engine/impl/outbox/smtp-outbox-folder.vala @@ -117,7 +117,7 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder { return Geary.Trillian.FALSE; } - public override Geary.SpecialFolderType? get_special_folder_type() { + public override Geary.SpecialFolderType get_special_folder_type() { return Geary.SpecialFolderType.OUTBOX; } diff --git a/src/engine/sqlite/abstract/sqlite-database.vala b/src/engine/sqlite/abstract/sqlite-database.vala index 9037fa0d..1e8a48cd 100644 --- a/src/engine/sqlite/abstract/sqlite-database.vala +++ b/src/engine/sqlite/abstract/sqlite-database.vala @@ -50,7 +50,7 @@ public abstract class Geary.Sqlite.Database { return t; } - public void upgrade() throws Error { + public int upgrade() throws Error { // Get the SQLite database version. SQLHeavy.QueryResult result = db.execute("PRAGMA user_version;"); int db_version = result.fetch_int(); @@ -74,6 +74,8 @@ public abstract class Geary.Sqlite.Database { post_upgrade(db_version); } + + return db.execute("PRAGMA user_version;").fetch_int(); } private File get_upgrade_script(int version) { diff --git a/src/engine/sqlite/api/sqlite-account.vala b/src/engine/sqlite/api/sqlite-account.vala index 7f179d79..45bdd8c3 100644 --- a/src/engine/sqlite/api/sqlite-account.vala +++ b/src/engine/sqlite/api/sqlite-account.vala @@ -43,10 +43,8 @@ private class Geary.Sqlite.Account : Object { db.pre_upgrade.connect(on_pre_upgrade); db.post_upgrade.connect(on_post_upgrade); - db.upgrade(); - - // Need to clear duplicate folders (due to ticket #nnnn) - clear_duplicate_folders(); + // upgrade and do any processing that should be done on this version of the database + process_database(db.upgrade()); } catch (Error err) { warning("Unable to open database: %s", err.message); @@ -296,6 +294,25 @@ private class Geary.Sqlite.Account : Object { // TODO Add per-version data massaging. } + // Called every run after executing db.upgrade(); this gives a chance to perform work that + // cannot be easily expressed in an upgrade script and should happen whether an upgrade to that + // version has happened or not + private void process_database(int version) { + switch (version) { + case 3: + try { + clear_duplicate_folders(); + } catch (SQLHeavy.Error err) { + debug("Unable to clear duplicate folders in version %d: %s", version, err.message); + } + break; + + default: + // nothing to do + break; + } + } + private void clear_duplicate_folders() throws SQLHeavy.Error { int count = 0; @@ -320,7 +337,7 @@ private class Geary.Sqlite.Account : Object { SQLHeavy.QueryResult message_result = message_query.execute(); if (child_result.finished && message_result.finished) { - // no children, delete it + // no children and no messages, delete it SQLHeavy.Query child_delete = db.db.prepare( "DELETE FROM FolderTable WHERE id=?"); child_delete.bind_int64(0, id); diff --git a/src/engine/sqlite/api/sqlite-folder.vala b/src/engine/sqlite/api/sqlite-folder.vala index cb1aad0d..ebe4987b 100644 --- a/src/engine/sqlite/api/sqlite-folder.vala +++ b/src/engine/sqlite/api/sqlite-folder.vala @@ -32,14 +32,14 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics { private ImapDatabase db; private FolderRow folder_row; - private Geary.Imap.FolderProperties? properties; + private Geary.Imap.FolderProperties properties; private MessageTable message_table; private MessageLocationTable location_table; private MessageAttachmentTable attachment_table; private ImapMessagePropertiesTable imap_message_properties_table; private Geary.FolderPath path; - internal Folder(ImapDatabase db, FolderRow folder_row, Geary.Imap.FolderProperties? properties, + internal Folder(ImapDatabase db, FolderRow folder_row, Geary.Imap.FolderProperties properties, Geary.FolderPath path) throws Error { this.db = db; this.folder_row = folder_row; @@ -61,12 +61,12 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics { return path; } - public Geary.Imap.FolderProperties? get_properties() { - // TODO: TBD: alteration/updated signals for folders + public Geary.Imap.FolderProperties get_properties() { return properties; } - internal void update_properties(Geary.Imap.FolderProperties? properties) { + internal void update_properties(Geary.Imap.FolderProperties properties) { + // TODO: TBD: alteration/updated signals for folders this.properties = properties; }