diff --git a/src/client/application/application-controller.vala b/src/client/application/application-controller.vala index 473f18f7..38b1bfea 100644 --- a/src/client/application/application-controller.vala +++ b/src/client/application/application-controller.vala @@ -976,8 +976,6 @@ internal class Application.Controller : smtp.email_sent.connect(on_sent); smtp.sending_monitor.start.connect(on_sending_started); smtp.sending_monitor.finish.connect(on_sending_finished); - var outbox_context = new FolderContext(smtp.outbox); - context.add_folders(Geary.Collection.single(outbox_context)); } // Notify before opening so that listeners have a chance to diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala index 4a4850a5..d81661ca 100644 --- a/src/engine/api/geary-account.vala +++ b/src/engine/api/geary-account.vala @@ -434,6 +434,42 @@ public abstract class Geary.Account : BaseObject, Logging.Source { GLib.Cancellable? cancellable = null ) throws GLib.Error; + /** + * Registers a local folder with the account. + * + * The registering a local folder will cause the account will hook + * to the folder's signals such as {@link Folder.email_appended} + * and forward them on to the account-wide equivalents, include + * the list folder in the folder list, allow email in the folder + * to be found by account-wide operations, and so on. The folder + * will then be signalled as being available via {@link + * folders_available_unavailable}. + * + * A {@link EngineError.ALREADY_EXISTS} exception will be thrown + * if the given folder is already registered, or {@link + * EngineError.NOT_FOUND} if its path does not have {@link + * local_folder_root} as its root. + * + * @see deregister_local_folder + */ + public abstract void register_local_folder(Folder local) + throws GLib.Error; + + /** + * De-registers a local folder with the account. + * + * De-registering a previously registered local folder will signal + * it as being unavailable via {@link + * folders_available_unavailable} and unhook it from the account. + * + * A {@link local_folder_root} error will be thrown if the given + * folder is not already registered. + * + * @see register_local_folder + */ + public abstract void deregister_local_folder(Folder local) + throws GLib.Error; + /** * Search the local account for emails referencing a Message-ID value * (which can appear in the Message-ID header itself, as well as the diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala b/src/engine/imap-engine/imap-engine-generic-account.vala index 3b8536c3..8285af57 100644 --- a/src/engine/imap-engine/imap-engine-generic-account.vala +++ b/src/engine/imap-engine/imap-engine-generic-account.vala @@ -48,8 +48,10 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { private Cancellable? open_cancellable = null; private Nonblocking.Semaphore? remote_ready_lock = null; - private Gee.Map folder_map = + private Gee.Map remote_folders = new Gee.HashMap(); + private Gee.Map local_folders = + new Gee.HashMap(); private AccountProcessor? processor; private AccountSynchronizer sync; @@ -152,7 +154,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { // outgoing so local folders can be loaded first in case // queued mail gets sent and needs to get saved somewhere. yield this.imap.start(cancellable); - this.queue_operation(new StartPostie(this)); + this.queue_operation(new StartPostie(this, this.smtp.outbox)); // Kick off a background update of the search table. // @@ -185,13 +187,16 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { this.imap.discard_returned_sessions = true; this.remote_ready_lock.reset(); - // Close folders and ensure they do in fact close + // Notify folders are going away and wait for remotes to close - Gee.BidirSortedSet remotes = sort_by_path(this.folder_map.values); - this.folder_map.clear(); + var locals = sort_by_path(this.local_folders.values); + this.local_folders.clear(); + notify_folders_available_unavailable(null, locals); + + var remotes = sort_by_path(this.remote_folders.values); + this.remote_folders.clear(); notify_folders_available_unavailable(null, remotes); - - foreach (Geary.Folder folder in remotes) { + foreach (var folder in remotes) { debug("Waiting for remote to close: %s", folder.to_string()); yield folder.wait_for_close_async(); } @@ -402,7 +407,12 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { /** {@inheritDoc} */ public override Folder get_folder(FolderPath path) throws EngineError.NOT_FOUND { - Folder? folder = this.folder_map.get(path); + Folder? folder = null; + if (this.local.imap_folder_root.is_descendant(path)) { + folder = this.remote_folders.get(path); + } else if (this.local_folder_root.is_descendant(path)) { + folder = this.local_folders.get(path); + } if (folder == null) { throw new EngineError.NOT_FOUND( "Folder not found: %s", path.to_string() @@ -414,21 +424,37 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { /** {@inheritDoc} */ public override Gee.Collection list_folders() { var all = new Gee.HashSet(); - all.add_all(this.folder_map.values); + all.add_all(this.remote_folders.values); + all.add_all(this.local_folders.values); return all; } /** {@inheritDoc} */ public override Gee.Collection list_matching_folders(FolderPath? parent) throws EngineError.NOT_FOUND { - return traverse(folder_map.keys) + Gee.Map? folders = null; + if (this.local.imap_folder_root.is_descendant(parent)) { + folders = this.remote_folders; + } else if (this.local_folder_root.is_descendant(parent)) { + folders = this.local_folders; + } else { + throw new EngineError.NOT_FOUND( + "Unknown folder root: %s", parent.to_string() + ); + } + if (!folders.has_key(parent)) { + throw new EngineError.NOT_FOUND( + "Unknown parent: %s", parent.to_string() + ); + } + return traverse(folders.keys) .filter(p => { FolderPath? path_parent = p.parent; return ((parent == null && path_parent == null) || (parent != null && path_parent != null && path_parent.equal_to(parent))); }) - .map(p => folder_map.get(p)) + .map(p => folders.get(p)) .to_array_list(); } @@ -466,7 +492,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { FolderPath root = yield remote.get_default_personal_namespace(cancellable); FolderPath path = root.get_child(name); - if (this.folder_map.has_key(path)) { + if (this.remote_folders.has_key(path)) { throw new EngineError.ALREADY_EXISTS( "Folder already exists: %s", path.to_string() ); @@ -481,7 +507,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { remote_folder, cancellable ); add_folders(Collection.single(local_folder), false); - var folder = this.folder_map.get(path); + var folder = this.remote_folders.get(path); if (use != NONE) { promote_folders( Collection.single_map(use, folder) @@ -490,6 +516,41 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { return folder; } + /** {@inheritDoc} */ + public override void register_local_folder(Folder local) + throws GLib.Error { + var path = local.path; + if (this.local_folders.has_key(path)) { + throw new EngineError.ALREADY_EXISTS( + "Folder already exists: %s", path.to_string() + ); + } + if (!this.local_folder_root.is_descendant(path)) { + throw new EngineError.NOT_FOUND( + "Not a desendant of the local folder root: %s", path.to_string() + ); + } + this.local_folders.set(path, local); + notify_folders_available_unavailable( + sort_by_path(Collection.single(local)), null + ); + } + + /** {@inheritDoc} */ + public override void deregister_local_folder(Folder local) + throws GLib.Error { + var path = local.path; + if (!this.local_folders.has_key(path)) { + throw new EngineError.NOT_FOUND( + "Unknown folder: %s", path.to_string() + ); + } + notify_folders_available_unavailable( + null, sort_by_path(Collection.single(local)) + ); + this.local_folders.unset(path); + } + private ImapDB.EmailIdentifier check_id(Geary.EmailIdentifier id) throws EngineError { ImapDB.EmailIdentifier? imapdb_id = id as ImapDB.EmailIdentifier; if (imapdb_id == null) @@ -563,7 +624,13 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { Gee.MultiMap map = new Gee.HashMultiMap(); yield this.local.get_containing_folders_async(ids, map, cancellable); - yield this.smtp.outbox.add_to_containing_folders_async(ids, map, cancellable); + foreach (var folder in this.local_folders.values) { + var path = folder.path; + var matching = yield folder.contains_identifiers(ids, cancellable); + foreach (var id in matching) { + map.set(id, path); + } + } return (map.size == 0) ? null : map; } @@ -608,7 +675,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { ); foreach(ImapDB.Folder db_folder in db_folders) { FolderPath path = db_folder.get_path(); - if (!this.folder_map.has_key(path)) { + if (!this.remote_folders.has_key(path)) { MinimalFolder folder = new_folder(db_folder); folder.report_problem.connect(notify_report_problem); if (folder.used_as == NONE) { @@ -618,7 +685,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { } } built_folders.add(folder); - this.folder_map.set(folder.path, folder); + this.remote_folders.set(folder.path, folder); } } @@ -699,9 +766,9 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { Account.folder_path_comparator ); foreach(Geary.Folder folder in folders) { - MinimalFolder? impl = this.folder_map.get(folder.path); + MinimalFolder? impl = this.remote_folders.get(folder.path); if (impl != null) { - this.folder_map.unset(folder.path); + this.remote_folders.unset(folder.path); removed.add(impl); } } @@ -742,7 +809,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { Gee.List search_names = special_search_names.get(use); foreach (string search_name in search_names) { FolderPath search_path = root.get_child(search_name); - foreach (FolderPath test_path in folder_map.keys) { + foreach (FolderPath test_path in this.remote_folders.keys) { if (test_path.compare_normalized_ci(search_path) == 0) { path = search_path; break; @@ -764,8 +831,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { ); } - if (this.folder_map.has_key(path)) { - special = this.folder_map.get(path); + if (this.remote_folders.has_key(path)) { + special = this.remote_folders.get(path); promote_folders( Collection.single_map(use, special) ); @@ -1077,12 +1144,17 @@ internal class Geary.ImapEngine.LoadFolders : AccountOperation { internal class Geary.ImapEngine.StartPostie : AccountOperation { - internal StartPostie(Account account) { + private Outbox.Folder outbox; + + + internal StartPostie(Account account, Outbox.Folder outbox) { base(account); + this.outbox = outbox; } public override async void execute(GLib.Cancellable cancellable) throws GLib.Error { + this.account.register_local_folder(this.outbox); yield this.account.outgoing.start(cancellable); } diff --git a/src/engine/outbox/outbox-folder.vala b/src/engine/outbox/outbox-folder.vala index 9fb84c2b..51c3c7b2 100644 --- a/src/engine/outbox/outbox-folder.vala +++ b/src/engine/outbox/outbox-folder.vala @@ -383,29 +383,6 @@ public class Geary.Outbox.Folder : throw new EngineError.UNSUPPORTED("Folder special use cannot be changed"); } - internal async void - add_to_containing_folders_async(Gee.Collection ids, - Gee.MultiMap map, - GLib.Cancellable? cancellable) - throws GLib.Error { - check_open(); - yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => { - foreach (Geary.EmailIdentifier id in ids) { - EmailIdentifier? outbox_id = id as EmailIdentifier; - if (outbox_id == null) - continue; - - OutboxRow? row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable); - if (row == null) - continue; - - map.set(id, path); - } - - return Db.TransactionOutcome.DONE; - }, cancellable); - } - // Utility for getting an email object back from an outbox row. private Geary.Email row_to_email(OutboxRow row) throws Error { Geary.Email? email = null; diff --git a/test/mock/mock-account.vala b/test/mock/mock-account.vala index 7d598a0a..173b2eca 100644 --- a/test/mock/mock-account.vala +++ b/test/mock/mock-account.vala @@ -88,6 +88,18 @@ public class Mock.Account : Geary.Account, ); } + /** {@inheritDoc} */ + public override void register_local_folder(Geary.Folder local) + throws GLib.Error { + void_call("register_local_folder", { local }); + } + + /** {@inheritDoc} */ + public override void deregister_local_folder(Geary.Folder local) + throws GLib.Error { + void_call("deregister_local_folder", { local }); + } + public override Geary.EmailIdentifier to_email_identifier(GLib.Variant serialised) throws Geary.EngineError.BAD_PARAMETERS { try {