diff --git a/src/client/plugin/mail-merge/mail-merge-folder.vala b/src/client/plugin/mail-merge/mail-merge-folder.vala index 0a15743a..c07e009f 100644 --- a/src/client/plugin/mail-merge/mail-merge-folder.vala +++ b/src/client/plugin/mail-merge/mail-merge-folder.vala @@ -141,6 +141,18 @@ public class Plugin.MailMergeFolder : Geary.AbstractLocalFolder { ); } + /** {@inheritDoc} */ + public override async Gee.Collection contains_identifiers( + Gee.Collection ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + return Geary.traverse( + ids + ).filter( + (id) => this.map.has_key(id) + ).to_hash_set(); + } + public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id, Geary.Email.Field required_fields, diff --git a/src/engine/api/geary-folder.vala b/src/engine/api/geary-folder.vala index 89a940fd..58701427 100644 --- a/src/engine/api/geary-folder.vala +++ b/src/engine/api/geary-folder.vala @@ -626,6 +626,20 @@ public abstract class Geary.Folder : BaseObject, Logging.Source { public abstract async void synchronise_remote(GLib.Cancellable? cancellable) throws GLib.Error; + /** + * Determines the email identifiers that are contained in the folder. + * + * The returned collection will be a subset of the given input + * collection that contains an input identifier only if that + * identifier belongs to an email contained by the folder. + * + * The Folder must be opened prior to attempting this operation. + */ + public abstract async Gee.Collection contains_identifiers( + Gee.Collection ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error; + /** * List a number of contiguous emails in the folder's vector. * diff --git a/src/engine/app/app-search-folder.vala b/src/engine/app/app-search-folder.vala index 85c8c75c..0ad7552f 100644 --- a/src/engine/app/app-search-folder.vala +++ b/src/engine/app/app-search-folder.vala @@ -213,6 +213,18 @@ public class Geary.App.SearchFolder : return results; } + /** {@inheritDoc} */ + public override async Gee.Collection contains_identifiers( + Gee.Collection ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + return Geary.traverse( + ids + ).filter( + (id) => this.id_map.has_key(id) + ).to_hash_set(); + } + public override async Gee.List? list_email_by_id_async( EmailIdentifier? initial_id, int count, diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala index bcb41d6a..3960a072 100644 --- a/src/engine/imap-db/imap-db-folder.vala +++ b/src/engine/imap-db/imap-db-folder.vala @@ -341,6 +341,55 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics { return results; } + /** Returns a subset of the given ids that are in this folder. */ + public async Gee.Collection contains_identifiers( + Gee.Collection ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + var contained_ids = new Gee.HashMap( + Collection.int64_hash_func, + Collection.int64_equal_func + ); + if (!ids.is_empty) { + var valid_ids = new Gee.HashMap( + Collection.int64_hash_func, + Collection.int64_equal_func + ); + yield db.exec_transaction_async( + RO, + (cx, cancellable) => { + var sql = new StringBuilder(""" + SELECT message_id + FROM MessageLocationTable + WHERE message_id IN ( + """); + foreach (var id in ids) { + var id_impl = id as EmailIdentifier; + if (id_impl != null) { + sql.append(id_impl.message_id.to_string()); + valid_ids.set(id_impl.message_id, id_impl); + } + } + sql.append(") AND folder_id=? AND remove_marker<>?"); + + Db.Statement stmt = cx.prepare(sql.str); + stmt.bind_rowid(0, this.folder_id); + stmt.bind_bool(0, false); + + Db.Result results = stmt.exec(cancellable); + while (!results.finished) { + var message_id = results.int64_at(0); + contained_ids.set(message_id, valid_ids.get(message_id)); + results.next(cancellable); + } + return COMMIT; + }, + cancellable + ); + } + return contained_ids.values; + } + public async Gee.List? list_email_by_id_async(ImapDB.EmailIdentifier? initial_id, int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error { diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala b/src/engine/imap-engine/imap-engine-minimal-folder.vala index a9905b69..23159147 100644 --- a/src/engine/imap-engine/imap-engine-minimal-folder.vala +++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala @@ -1201,6 +1201,15 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport this.replay_queue.schedule_server_notification(op); } + /** {@inheritDoc} */ + public override async Gee.Collection contains_identifiers( + Gee.Collection ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open("contains_identifiers"); + return yield this.local_folder.contains_identifiers(ids, cancellable); + } + // // list email variants // diff --git a/src/engine/outbox/outbox-folder.vala b/src/engine/outbox/outbox-folder.vala index 6e402477..9fb84c2b 100644 --- a/src/engine/outbox/outbox-folder.vala +++ b/src/engine/outbox/outbox-folder.vala @@ -210,6 +210,34 @@ public class Geary.Outbox.Folder : } } + /** {@inheritDoc} */ + public override async Gee.Collection contains_identifiers( + Gee.Collection ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + var contains = new Gee.HashSet(); + yield db.exec_transaction_async( + RO, + (cx, cancellable) => { + foreach (Geary.EmailIdentifier id in ids) { + var outbox_id = id as EmailIdentifier; + if (outbox_id != null) { + var row = do_fetch_row_by_ordering( + cx, outbox_id.ordering, cancellable + ); + if (row != null) { + contains.add(id); + } + } + } + return DONE; + }, + cancellable + ); + return contains; + } + public override async Gee.List? list_email_by_id_async(Geary.EmailIdentifier? _initial_id, int count, diff --git a/test/mock/mock-folder.vala b/test/mock/mock-folder.vala index 52d100ba..59e5a220 100644 --- a/test/mock/mock-folder.vala +++ b/test/mock/mock-folder.vala @@ -85,6 +85,17 @@ public class Mock.Folder : Geary.Folder, void_call("synchronise_remote", { cancellable }); } + public override async Gee.Collection contains_identifiers( + Gee.Collection ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + return yield object_call_async>( + "contains_identifiers", + {ids, cancellable}, + new Gee.LinkedList() + ); + } + public override async Gee.List? list_email_by_id_async(Geary.EmailIdentifier? initial_id, int count,