[geary/mjog/invert-folder-class-hierarchy: 347/362] engine: Update all methods used to access collections of email by id




commit bd7a1c328b2053688482891d47d34384bcd64028
Author: Michael Gratton <mike vee net>
Date:   Wed Feb 17 21:33:13 2021 +1100

    engine: Update all methods used to access collections of email by id
    
    Rename all of:
    
     * `Geary.Account.list_local_email_async`
     * `Geary.Folder.list_email_by_sparse_id_async`
     * `Geary.App.EmailStore.list_email_by_sparse_id_async`
    
    To `get_multiple_email_by_id` and to have the same signature and
    contract, for consistency.
    
    Specify they all return email from local storage only, not ever any
    remote. Update callers to use the new signatures, or to directly access
    the required data from the remote session, as appropriate. As a result,
    remove the now-unused `ListEmailBySparseId` replay op.

 po/POTFILES.in                                     |   1 -
 .../application-email-store-factory.vala           |   5 +-
 .../application-notification-plugin-context.vala   |   9 +-
 .../application/application-plugin-manager.vala    |   7 +-
 src/client/composer/composer-widget.vala           |   7 +-
 .../conversation-list/conversation-list-store.vala |  13 +--
 .../plugin/mail-merge/mail-merge-folder.vala       |  37 ++++---
 src/engine/api/geary-account.vala                  |  21 ++--
 src/engine/api/geary-folder.vala                   |  45 +++++----
 src/engine/app/app-conversation-monitor.vala       |  31 +++---
 src/engine/app/app-email-store.vala                |  24 +++--
 src/engine/app/app-search-folder.vala              |  32 +++---
 .../app-external-append-operation.vala             |   4 +-
 src/engine/app/email-store/app-list-operation.vala |  27 ++---
 src/engine/imap-db/imap-db-account.vala            |  10 +-
 src/engine/imap-db/imap-db-folder.vala             |  65 ++++++++----
 .../imap-engine/imap-engine-email-prefetcher.vala  |  45 +++++++--
 .../imap-engine/imap-engine-generic-account.vala   |   2 +-
 .../imap-engine/imap-engine-minimal-folder.vala    | 112 +++++++++++----------
 .../imap-engine-list-email-by-sparse-id.vala       |  81 ---------------
 .../imap-engine-server-search-email.vala           |   7 +-
 src/engine/meson.build                             |   1 -
 src/engine/outbox/outbox-folder.vala               |  52 +++++-----
 test/engine/app/app-conversation-monitor-test.vala |  28 ++++--
 test/engine/imap-db/imap-db-account-test.vala      |   8 +-
 test/mock/mock-account.vala                        |   6 +-
 test/mock/mock-folder.vala                         |  25 +++--
 test/mock/mock-remote-folder.vala                  |  25 +++--
 28 files changed, 359 insertions(+), 371 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b0d440c9e..b6fed0361 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -298,7 +298,6 @@ src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
 src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
 src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala
 src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala
-src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala
 src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
 src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala
 src/engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala
diff --git a/src/client/application/application-email-store-factory.vala 
b/src/client/application/application-email-store-factory.vala
index c5c5925e7..ab5c1daf6 100644
--- a/src/client/application/application-email-store-factory.vala
+++ b/src/client/application/application-email-store-factory.vala
@@ -69,13 +69,12 @@ internal class Application.EmailStoreFactory : Geary.BaseObject {
 
             foreach (var context in found_accounts.keys) {
                 Gee.Collection<Geary.Email> batch =
-                    yield context.emails.list_email_by_sparse_id_async(
+                    yield context.emails.get_multiple_email_by_id(
                         found_accounts.get(context),
                         REQUIRED_FIELDS,
-                        NONE,
                         context.cancellable
                     );
-                if (batch != null) {
+                if (!batch.is_empty) {
                     foreach (var email in batch) {
                         emails.add(
                             new EmailImpl(
diff --git a/src/client/application/application-notification-plugin-context.vala 
b/src/client/application/application-notification-plugin-context.vala
index 57f30f745..4201ecc9f 100644
--- a/src/client/application/application-notification-plugin-context.vala
+++ b/src/client/application/application-notification-plugin-context.vala
@@ -287,12 +287,11 @@ internal class Application.NotificationPluginContext :
     ) {
         MonitorInformation info = this.folder_information.get(folder);
         if (info != null) {
-            Gee.List<Geary.Email>? list = null;
+            Gee.Set<Geary.Email> email = null;
             try {
-                list = yield folder.list_email_by_sparse_id_async(
+                email = yield folder.get_multiple_email_by_id(
                     email_ids,
                     REQUIRED_FIELDS,
-                    NONE,
                     info.cancellable
                 );
             } catch (GLib.Error err) {
@@ -300,8 +299,8 @@ internal class Application.NotificationPluginContext :
                     "Unable to list new email for notification: %s", err.message
                 );
             }
-            if (list != null && !list.is_empty) {
-                new_messages(info, list);
+            if (!email.is_empty) {
+                new_messages(info, email);
             } else {
                 warning(
                     "%d new emails, but none could be listed for notification",
diff --git a/src/client/application/application-plugin-manager.vala 
b/src/client/application/application-plugin-manager.vala
index 1d736222e..39cc915c4 100644
--- a/src/client/application/application-plugin-manager.vala
+++ b/src/client/application/application-plugin-manager.vala
@@ -163,12 +163,11 @@ public class Application.PluginManager : GLib.Object {
             if (id == null) {
                 throw new Plugin.Error.NOT_FOUND("Email id not found");
             }
-            Gee.Collection<Geary.Email>? email = null;
+            Gee.Collection<Geary.Email> email = null;
             try {
-                email = yield source_impl.backing.emails.list_email_by_sparse_id_async(
+                email = yield source_impl.backing.emails.get_multiple_email_by_id(
                     Geary.Collection.single(id),
                     Composer.Widget.REQUIRED_FIELDS,
-                    NONE,
                     source_impl.backing.cancellable
                 );
             } catch (GLib.Error err) {
@@ -176,7 +175,7 @@ public class Application.PluginManager : GLib.Object {
                     "Error looking up email: %s", err.message
                 );
             }
-            if (email == null || email.is_empty) {
+            if (email.is_empty) {
                 throw new Plugin.Error.NOT_FOUND("Email not found for id");
             }
             var context = Geary.Collection.first(email);
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 5e6616858..912f63712 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -692,14 +692,13 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
 
         var full_context = context;
         if (!context.fields.is_all_set(REQUIRED_FIELDS)) {
-            Gee.Collection<Geary.Email>? email =
-                yield this.sender_context.emails.list_email_by_sparse_id_async(
+            Gee.Collection<Geary.Email> email =
+                yield this.sender_context.emails.get_multiple_email_by_id(
                     Geary.Collection.single(context.id),
                     REQUIRED_FIELDS,
-                    NONE,
                     this.sender_context.cancellable
                 );
-            if (email == null || email.is_empty) {
+            if (email.is_empty) {
                 throw new Geary.EngineError.INCOMPLETE_MESSAGE(
                     "Unable to load email fields required for composer: %s",
                     context.fields.to_string()
diff --git a/src/client/conversation-list/conversation-list-store.vala 
b/src/client/conversation-list/conversation-list-store.vala
index 3854253c4..ad5c07e9d 100644
--- a/src/client/conversation-list/conversation-list-store.vala
+++ b/src/client/conversation-list/conversation-list-store.vala
@@ -203,12 +203,13 @@ public class ConversationListStore : Gtk.ListStore {
 
     private async Gee.Collection<Geary.Email> do_get_previews_async(
         Gee.Collection<Geary.EmailIdentifier> emails_needing_previews) {
-        Geary.Folder.ListFlags flags = (loading_local_only) ? Geary.Folder.ListFlags.LOCAL_ONLY
-            : Geary.Folder.ListFlags.NONE;
-        Gee.Collection<Geary.Email>? emails = null;
+        Gee.Collection<Geary.Email> emails = null;
         try {
-            emails = yield email_store.list_email_by_sparse_id_async(emails_needing_previews,
-                ConversationListStore.WITH_PREVIEW_FIELDS, flags, cancellable);
+            emails = yield email_store.get_multiple_email_by_id(
+                emails_needing_previews,
+                WITH_PREVIEW_FIELDS,
+                cancellable
+            );
         } catch (GLib.IOError.CANCELLED err) {
             // All good
         } catch (Geary.EngineError.NOT_FOUND err) {
@@ -218,7 +219,7 @@ public class ConversationListStore : Gtk.ListStore {
             warning("Unable to fetch preview: %s", err.message);
         }
 
-        return emails ?? new Gee.ArrayList<Geary.Email>();
+        return emails;
     }
 
     private Gee.Set<Geary.EmailIdentifier> get_emails_needing_previews() {
diff --git a/src/client/plugin/mail-merge/mail-merge-folder.vala 
b/src/client/plugin/mail-merge/mail-merge-folder.vala
index 9f78242de..c6ad228ec 100644
--- a/src/client/plugin/mail-merge/mail-merge-folder.vala
+++ b/src/client/plugin/mail-merge/mail-merge-folder.vala
@@ -205,6 +205,24 @@ public class MailMerge.Folder : Geary.BaseObject,
         return email;
     }
 
+    public async Gee.Set<Geary.Email> get_multiple_email_by_id(
+        Gee.Collection<Geary.EmailIdentifier> ids,
+        Geary.Email.Field required_fields,
+        GLib.Cancellable? cancellable = null
+    ) throws GLib.Error {
+        var results = Geary.Email.new_identifier_based_set();
+        foreach (var id in ids) {
+            var email = this.email.get(id);
+            if (email == null) {
+                throw new Geary.EngineError.NOT_FOUND(
+                    "No email with ID %s in merge", id.to_string()
+                );
+            }
+            results.add(email);
+        }
+        return results;
+    }
+
     public async Gee.List<Geary.Email>?
         list_email_by_id_async(Geary.EmailIdentifier? initial_id,
                                int count,
@@ -253,25 +271,6 @@ public class MailMerge.Folder : Geary.BaseObject,
         return (list.size > 0) ? list : null;
     }
 
-    public async Gee.List<Geary.Email>?
-        list_email_by_sparse_id_async(Gee.Collection<Geary.EmailIdentifier> ids,
-                                      Geary.Email.Field required_fields,
-                                      Geary.Folder.ListFlags flags,
-                                      GLib.Cancellable? cancellable = null)
-        throws GLib.Error {
-        Gee.List<Geary.Email> list = new Gee.ArrayList<Geary.Email>();
-        foreach (var id in ids) {
-            var email = this.email.get(id);
-            if (email == null) {
-                throw new Geary.EngineError.NOT_FOUND(
-                    "No email with ID %s in merge", id.to_string()
-                );
-            }
-            list.add(email);
-        }
-        return (list.size > 0) ? list : null;
-    }
-
     /** {@inheritDoc} */
     public void set_used_as_custom(bool enabled)
         throws Geary.EngineError.UNSUPPORTED {
diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala
index fd4fa12b5..3cf727a7f 100644
--- a/src/engine/api/geary-account.vala
+++ b/src/engine/api/geary-account.vala
@@ -544,16 +544,23 @@ public abstract class Geary.Account : BaseObject, Logging.Source {
         Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
 
     /**
-     * Return a collection of email with the given identifiers.
+     * Returns a set of non-contiguous email objects from local storage.
      *
-     * The returned collection will be in the same order as the
-     * natural ordering of the given identifiers.
+     * Any {@link Gee.Collection} of email identifiers is accepted,
+     * but the returned set will only contain one email for each
+     * requested; duplicates are ignored.
      *
-     * Throws {@link EngineError.NOT_FOUND} if any email is not found
-     * and {@link EngineError.INCOMPLETE_MESSAGE} if the fields aren't
-     * available.
+     * Note that for remote-backed folders, email may not have yet
+     * been fully downloaded and hence might exist incomplete in local
+     * storage. If the requested fields are not available for all
+     * given identifiers, {@link EngineError.INCOMPLETE_MESSAGE} is
+     * thrown. Connect to the {@link Account.email_complete} signal to
+     * be notified of when email is fully downloaded in this case.
+     *
+     * If any of the given email identifiers are not present in the
+     * vector, an {@link EngineError.NOT_FOUND} error is thrown.
      */
-    public abstract async Gee.List<Email> list_local_email_async(
+    public abstract async Gee.Set<Email> get_multiple_email_by_id(
         Gee.Collection<EmailIdentifier> ids,
         Email.Field required_fields,
         GLib.Cancellable? cancellable = null
diff --git a/src/engine/api/geary-folder.vala b/src/engine/api/geary-folder.vala
index db0d0bd0c..1db1c9541 100644
--- a/src/engine/api/geary-folder.vala
+++ b/src/engine/api/geary-folder.vala
@@ -696,6 +696,33 @@ public interface Geary.Folder : GLib.Object, Logging.Source {
         GLib.Cancellable? cancellable = null
     ) throws GLib.Error;
 
+    /**
+     * Returns a set of non-contiguous emails from the folder's vector.
+     *
+     * Similar in contract to {@link get_email_by_id}, but for a
+     * collection of {@link Geary.EmailIdentifier}s rather than a
+     * single email.
+     *
+     * Any {@link Gee.Collection} of email identifiers is accepted,
+     * but the returned set will only contain one email for each
+     * requested; duplicates are ignored.
+     *
+     * Note that for remote-backed folders, email may not have yet
+     * been fully downloaded and hence might exist incomplete in local
+     * storage. If the requested fields are not available for all
+     * given identifiers, {@link EngineError.INCOMPLETE_MESSAGE} is
+     * thrown. Connect to the {@link Account.email_complete} signal to
+     * be notified of when email is fully downloaded in this case.
+     *
+     * If any of the given email identifiers are not present in the
+     * vector, an {@link EngineError.NOT_FOUND} error is thrown.
+     */
+    public abstract async Gee.Set<Email> get_multiple_email_by_id(
+        Gee.Collection<EmailIdentifier> ids,
+        Email.Field required_fields,
+        GLib.Cancellable? cancellable = null
+    ) throws GLib.Error;
+
     /**
      * List a number of contiguous emails in the folder's vector.
      *
@@ -747,24 +774,6 @@ public interface Geary.Folder : GLib.Object, Logging.Source {
         int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null)
         throws Error;
 
-    /**
-     * List a set of non-contiguous emails in the folder's vector.
-     *
-     * Similar in contract to {@link list_email_by_id_async}, but uses a list of
-     * {@link Geary.EmailIdentifier}s rather than a range.
-     *
-     * Any Gee.Collection is accepted for EmailIdentifiers, but the returned list will only contain
-     * one email for each requested; duplicates are ignored.  ListFlags.INCLUDING_ID is ignored
-     * for this call.
-     *
-     * If the remote connection fails, this call will return locally-available Email without error.
-     *
-     * The Folder must be opened prior to attempting this operation.
-     */
-    public abstract async Gee.List<Geary.Email>? list_email_by_sparse_id_async(
-        Gee.Collection<Geary.EmailIdentifier> ids, Geary.Email.Field required_fields, ListFlags flags,
-        Cancellable? cancellable = null) throws Error;
-
     /**
      * Sets whether this folder has a custom special use.
      *
diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala
index 876b75cbd..d1bc805cf 100644
--- a/src/engine/app/app-conversation-monitor.vala
+++ b/src/engine/app/app-conversation-monitor.vala
@@ -492,23 +492,21 @@ public class Geary.App.ConversationMonitor : BaseObject, Logging.Source {
     }
 
     /** Loads messages from the base folder into the window. */
-    internal async void load_by_sparse_id(Gee.Collection<EmailIdentifier> ids,
-                                          Folder.ListFlags flags = Folder.ListFlags.NONE)
+    internal async void load_by_sparse_id(Gee.Collection<EmailIdentifier> ids)
         throws Error {
         notify_scan_started();
 
         GLib.Error? scan_error = null;
         try {
-            Gee.Collection<Geary.Email>? messages =
-                yield this.base_folder.list_email_by_sparse_id_async(
-                    ids, required_fields, flags, this.operation_cancellable
+            Gee.Collection<Geary.Email> messages =
+                yield this.base_folder.get_multiple_email_by_id(
+                    ids, required_fields, this.operation_cancellable
                 );
 
-            if (messages != null && !messages.is_empty) {
+            if (!messages.is_empty) {
                 foreach (Email email in messages) {
                     this.window.add(email.id);
                 }
-
                 yield process_email_async(messages, ProcessJobContext());
             }
         } catch (GLib.Error err) {
@@ -529,16 +527,14 @@ public class Geary.App.ConversationMonitor : BaseObject, Logging.Source {
      * handled separately.
      */
     internal async void external_load_by_sparse_id(Folder folder,
-                                                   Gee.Collection<EmailIdentifier> ids,
-                                                   Folder.ListFlags flags)
+                                                   Gee.Collection<EmailIdentifier> ids)
         throws GLib.Error {
         // First just get the bare minimum we need to determine if we even
         // care about the messages.
-        Gee.List<Geary.Email>? emails =
-            yield folder.list_email_by_sparse_id_async(
-                ids, Geary.Email.Field.REFERENCES, flags, this.operation_cancellable
-            );
-        if (emails != null) {
+        Gee.Set<Geary.Email> emails = yield folder.get_multiple_email_by_id(
+            ids, REFERENCES, this.operation_cancellable
+        );
+        if (!emails.is_empty) {
             var relevant_ids = new Gee.HashSet<Geary.EmailIdentifier>();
             foreach (var email in emails) {
                 Gee.Set<RFC822.MessageID>? ancestors = email.get_ancestors();
@@ -552,18 +548,17 @@ public class Geary.App.ConversationMonitor : BaseObject, Logging.Source {
             // fields, to make sure when we load them from the
             // database we have all the data we need.
             if (!relevant_ids.is_empty) {
-                emails = yield folder.list_email_by_sparse_id_async(
+                emails = yield folder.get_multiple_email_by_id(
                     relevant_ids,
                     required_fields,
-                    flags,
                     this.operation_cancellable
                 );
             } else {
-                emails = null;
+                emails.clear();
             }
         }
 
-        if (emails != null && !emails.is_empty) {
+        if (!emails.is_empty) {
             debug("Fetched %d relevant emails locally", emails.size);
             yield process_email_async(emails, ProcessJobContext());
         }
diff --git a/src/engine/app/app-email-store.vala b/src/engine/app/app-email-store.vala
index a280c55c7..a6cf28d8e 100644
--- a/src/engine/app/app-email-store.vala
+++ b/src/engine/app/app-email-store.vala
@@ -61,17 +61,6 @@ public class Geary.App.EmailStore : BaseObject {
         return (map.size > 0 ? map : null);
     }
 
-    /**
-     * Lists any set of EmailIdentifiers as if they were all in one folder.
-     */
-    public async Gee.Collection<Geary.Email>? list_email_by_sparse_id_async(
-        Gee.Collection<Geary.EmailIdentifier> emails, Geary.Email.Field required_fields,
-        Geary.Folder.ListFlags flags, Cancellable? cancellable = null) throws Error {
-        ListOperation op = new Geary.App.ListOperation(required_fields, flags);
-        yield do_folder_operation_async(op, emails, cancellable);
-        return (op.results.size > 0 ? op.results : null);
-    }
-
     /**
      * Fetches any EmailIdentifier regardless of what folder it's in.
      */
@@ -88,6 +77,19 @@ public class Geary.App.EmailStore : BaseObject {
         return op.result;
     }
 
+    /**
+     * Lists any set of EmailIdentifiers as if they were all in one folder.
+     */
+    public async Gee.Collection<Geary.Email> get_multiple_email_by_id(
+        Gee.Collection<EmailIdentifier> emails,
+        Email.Field required_fields,
+        GLib.Cancellable? cancellable = null
+    ) throws GLib.Error {
+        var op = new Geary.App.ListOperation(required_fields);
+        yield do_folder_operation_async(op, emails, cancellable);
+        return op.results;
+    }
+
     /**
      * Marks any set of EmailIdentifiers as if they were all in one
      * Geary.FolderSupport.Mark folder.
diff --git a/src/engine/app/app-search-folder.vala b/src/engine/app/app-search-folder.vala
index d0cef85fa..4eb58d7ca 100644
--- a/src/engine/app/app-search-folder.vala
+++ b/src/engine/app/app-search-folder.vala
@@ -228,6 +228,17 @@ public class Geary.App.SearchFolder : BaseObject,
         );
     }
 
+    /** {@inheritDoc} */
+    public async Gee.Set<Email> get_multiple_email_by_id(
+        Gee.Collection<Geary.EmailIdentifier> ids,
+        Email.Field required_fields,
+        GLib.Cancellable? cancellable = null
+    ) throws GLib.Error {
+        return yield this.account.get_multiple_email_by_id(
+            check_ids(ids), required_fields, cancellable
+        );
+    }
+
     public async Gee.List<Email>? list_email_by_id_async(
         EmailIdentifier? initial_id,
         int count,
@@ -305,11 +316,11 @@ public class Geary.App.SearchFolder : BaseObject,
             }
         }
 
-        Gee.List<Email>? results = null;
+        Gee.Set<Email> results = null;
         GLib.Error? list_error = null;
         if (!engine_ids.is_empty) {
             try {
-                results = yield this.account.list_local_email_async(
+                results = yield this.account.get_multiple_email_by_id(
                     engine_ids,
                     required_fields,
                     cancellable
@@ -323,18 +334,9 @@ public class Geary.App.SearchFolder : BaseObject,
             throw list_error;
         }
 
-        return results;
-    }
-
-    public async Gee.List<Email>? list_email_by_sparse_id_async(
-        Gee.Collection<EmailIdentifier> list,
-        Email.Field required_fields,
-        Folder.ListFlags flags,
-        Cancellable? cancellable = null
-    ) throws GLib.Error {
-        return yield this.account.list_local_email_async(
-            check_ids(list), required_fields, cancellable
-        );
+        var list = new Gee.ArrayList<Email>();
+        list.add_all(EmailIdentifier.sort_emails(results));
+        return list.is_empty ? null : list;
     }
 
     public virtual async void remove_email_async(
@@ -537,7 +539,7 @@ public class Geary.App.SearchFolder : BaseObject,
                 // Fetch email to get the received date for
                 // correct ordering in the search folder
                 Gee.Collection<Email> email_results =
-                    yield this.account.list_local_email_async(
+                    yield this.account.get_multiple_email_by_id(
                         id_results,
                         PROPERTIES,
                         cancellable
diff --git a/src/engine/app/conversation-monitor/app-external-append-operation.vala 
b/src/engine/app/conversation-monitor/app-external-append-operation.vala
index 530122e72..df679d163 100644
--- a/src/engine/app/conversation-monitor/app-external-append-operation.vala
+++ b/src/engine/app/conversation-monitor/app-external-append-operation.vala
@@ -27,9 +27,7 @@ private class Geary.App.ExternalAppendOperation : BatchOperation<EmailIdentifier
                   batch.size,
                   this.folder.to_string());
 
-            yield this.monitor.external_load_by_sparse_id(
-                this.folder, batch, Geary.Folder.ListFlags.NONE
-            );
+            yield this.monitor.external_load_by_sparse_id(this.folder, batch);
         }
     }
 
diff --git a/src/engine/app/email-store/app-list-operation.vala 
b/src/engine/app/email-store/app-list-operation.vala
index 08afd6406..7d288ccb6 100644
--- a/src/engine/app/email-store/app-list-operation.vala
+++ b/src/engine/app/email-store/app-list-operation.vala
@@ -5,25 +5,26 @@
  */
 
 private class Geary.App.ListOperation : Geary.App.AsyncFolderOperation {
-    public override Type folder_type { get { return typeof(Geary.Folder); } }
 
-    public Gee.HashSet<Geary.Email> results;
-    public Geary.Email.Field required_fields;
-    public Geary.Folder.ListFlags flags;
+    public override Type folder_type { get { return typeof(Folder); } }
 
-    public ListOperation(Geary.Email.Field required_fields, Geary.Folder.ListFlags flags) {
-        results = new Gee.HashSet<Geary.Email>();
+    public Gee.Collection<Geary.Email> results { get; private set; }
+    public Email.Field required_fields { get; private set; }
+
+
+    public ListOperation(Email.Field required_fields) {
+        this.results = new Gee.HashSet<Geary.Email>();
         this.required_fields = required_fields;
-        this.flags = flags;
     }
 
     public override async Gee.Collection<Geary.EmailIdentifier> execute_async(
-        Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids,
-        Cancellable? cancellable) throws Error {
-        Gee.List<Geary.Email>? list = yield folder.list_email_by_sparse_id_async(
-            ids, required_fields, flags, cancellable);
-        if (list != null)
-            results.add_all(list);
+        Folder folder,
+        Gee.Collection<EmailIdentifier> ids,
+        GLib.Cancellable? cancellable
+    ) throws GLib.Error {
+        this.results = yield folder.get_multiple_email_by_id(
+            ids, required_fields, cancellable
+        );
         return ids;
     }
 }
diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala
index abc975d8d..6f575ca93 100644
--- a/src/engine/imap-db/imap-db-account.vala
+++ b/src/engine/imap-db/imap-db-account.vala
@@ -666,13 +666,13 @@ private class Geary.ImapDB.Account : BaseObject {
         return search_matches;
     }
 
-    public async Gee.List<Email>? list_email(Gee.Collection<EmailIdentifier> ids,
-                                             Email.Field required_fields,
-                                             GLib.Cancellable? cancellable = null)
-    throws GLib.Error {
+    public async Gee.Set<Email> list_email(Gee.Collection<EmailIdentifier> ids,
+                                           Email.Field required_fields,
+                                           GLib.Cancellable? cancellable = null)
+        throws GLib.Error {
         check_open();
 
-        var results = new Gee.ArrayList<Email>();
+        var results = Email.new_identifier_based_set();
         yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
                 foreach (var id in ids) {
                     // TODO: once we have a way of deleting messages, we won't be able
diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala
index eeed32d86..c5dfdce4f 100644
--- a/src/engine/imap-db/imap-db-folder.vala
+++ b/src/engine/imap-db/imap-db-folder.vala
@@ -503,8 +503,11 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         if (only_incomplete)
             locations = yield remove_complete_locations_in_chunks_async(locations, cancellable);
 
-        // Next, read in email in chunks
-        return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable);
+        var results = new Gee.ArrayList<Email>();
+        yield list_email_in_chunks_async(
+            locations, results, required_fields, flags, cancellable
+        );
+        return results.is_empty ? null : results;
     }
 
     // ListFlags.OLDEST_TO_NEWEST is ignored.  INCLUDING_ID means including *both* identifiers.
@@ -557,8 +560,11 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
             return Db.TransactionOutcome.SUCCESS;
         }, cancellable);
 
-        // Next, read in email in chunks
-        return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable);
+        var results = new Gee.ArrayList<Email>();
+        yield list_email_in_chunks_async(
+            locations, results, required_fields, flags, cancellable
+        );
+        return results.is_empty ? null : results;
     }
 
     // ListFlags.OLDEST_TO_NEWEST is ignored.  INCLUDING_ID means including *both* identifiers.
@@ -605,14 +611,23 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         if (only_incomplete)
             locations = yield remove_complete_locations_in_chunks_async(locations, cancellable);
 
-        // Next, read in email in chunks
-        return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable);
-    }
-
-    public async Gee.List<Geary.Email>? list_email_by_sparse_id_async(Gee.Collection<ImapDB.EmailIdentifier> 
ids,
-        Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error {
-        if (ids.size == 0)
-            return null;
+        var results = new Gee.ArrayList<Email>();
+        yield list_email_in_chunks_async(
+            locations, results, required_fields, flags, cancellable
+        );
+        return results.is_empty ? null : results;
+    }
+
+    public async Gee.Set<Email> list_email_by_sparse_id_async(
+        Gee.Collection<ImapDB.EmailIdentifier> ids,
+        Geary.Email.Field required_fields,
+        ListFlags flags,
+        GLib.Cancellable? cancellable
+    ) throws GLib.Error {
+        var results = Email.new_identifier_based_set();
+        if (ids.size == 0) {
+            return results;
+        }
 
         bool only_incomplete = flags.is_all_set(ListFlags.ONLY_INCOMPLETE);
 
@@ -660,8 +675,10 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         if (only_incomplete)
             locations = yield remove_complete_locations_in_chunks_async(locations, cancellable);
 
-        // Next, read in email in chunks
-        return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable);
+        yield list_email_in_chunks_async(
+            locations, results, required_fields, flags, cancellable
+        );
+        return results;
     }
 
     private async Gee.List<LocationIdentifier>? remove_complete_locations_in_chunks_async(
@@ -695,10 +712,15 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         return (incomplete_locations.size > 0) ? incomplete_locations : null;
     }
 
-    private async Gee.List<Geary.Email>? list_email_in_chunks_async(Gee.List<LocationIdentifier>? ids,
-        Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error {
+    private async void list_email_in_chunks_async(
+        Gee.List<LocationIdentifier>? ids,
+        Gee.Collection<Email> results,
+        Geary.Email.Field required_fields,
+        ListFlags flags,
+        GLib.Cancellable? cancellable
+    ) throws Error {
         if (ids == null || ids.size == 0)
-            return null;
+            return;
 
         // chunk count depends on whether or not the message -- body + headers -- is being fetched
         int chunk_count = required_fields.requires_any(Email.Field.BODY | Email.Field.HEADER)
@@ -706,7 +728,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
 
         int length_rounded_up = Numeric.int_round_up(ids.size, chunk_count);
 
-        Gee.List<Geary.Email> results = new Gee.ArrayList<Geary.Email>();
         for (int start = 0; start < length_rounded_up; start += chunk_count) {
             // stop is the index *after* the end of the slice
             int stop = Numeric.int_ceiling((start + chunk_count), ids.size);
@@ -721,14 +742,14 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
                 return Db.TransactionOutcome.SUCCESS;
             }, cancellable);
 
-            if (list != null)
+            if (list != null) {
                 results.add_all(list);
+            }
         }
 
-        if (results.size != ids.size)
+        if (results.size != ids.size) {
             debug("list_email_in_chunks_async: Requested %d email, returned %d", ids.size, results.size);
-
-        return (results.size > 0) ? results : null;
+        }
     }
 
     public async Geary.Email fetch_email_async(ImapDB.EmailIdentifier id,
diff --git a/src/engine/imap-engine/imap-engine-email-prefetcher.vala 
b/src/engine/imap-engine/imap-engine-email-prefetcher.vala
index 978a4bdcc..42fb1389a 100644
--- a/src/engine/imap-engine/imap-engine-email-prefetcher.vala
+++ b/src/engine/imap-engine/imap-engine-email-prefetcher.vala
@@ -251,22 +251,45 @@ private class Geary.ImapEngine.EmailPrefetcher : BaseObject,
     private async bool do_prefetch_email_async(Gee.Collection<EmailIdentifier> ids,
                                                int64 chunk_bytes) {
         debug("Prefetching %d emails (%sb)", ids.size, chunk_bytes.to_string());
+        var success = true;
+        var cancellable = this.running;
 
         try {
-            yield folder.list_email_by_sparse_id_async(
-                ids, PREFETCH_FIELDS, NONE, this.running
+            Imap.FolderSession remote = yield this.folder.claim_remote_session(
+                cancellable
             );
-        } catch (Error err) {
-            if (!(err is IOError.CANCELLED) && !(err is EngineError.OPEN_REQUIRED)) {
-                debug("Error prefetching %d emails for %s: %s", ids.size, folder.to_string(),
-                    err.message);
-            } else {
-                // only exit if cancelled or not open; fetch_email_async() can error out on lots of things,
-                // including mail that's been deleted, and that shouldn't stop the prefetcher
-                return false;
+
+            Gee.Collection<Imap.UID> uids = ImapDB.EmailIdentifier.to_uids(
+                (Gee.Collection<ImapDB.EmailIdentifier>) ids
+            );
+            Gee.Collection<Imap.MessageSet> message_sets =
+                Imap.MessageSet.uid_sparse(uids);
+            foreach (var message_set in message_sets) {
+                Gee.List<Email>? email = yield remote.list_email_async(
+                    message_set,
+                    PREFETCH_FIELDS,
+                    cancellable
+                );
+                if (email != null && !email.is_empty) {
+                    yield this.folder.local_folder.create_or_merge_email_async(
+                        email,
+                        true,
+                        this.folder.harvester,
+                        cancellable
+                    );
+                }
             }
+        } catch (GLib.IOError.CANCELLED err) {
+            // fine
+        } catch (EngineError.SERVER_UNAVAILABLE err) {
+            // fine
+            debug("Error prefetching %d emails: %s", ids.size, err.message);
+        } catch (GLib.Error err) {
+            // not fine
+            success = false;
+            warning("Error prefetching %d emails: %s", ids.size, err.message);
         }
 
-        return true;
+        return success;
     }
 }
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala 
b/src/engine/imap-engine/imap-engine-generic-account.vala
index 1ac0fb169..b7097d0d5 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -604,7 +604,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
     }
 
     /** {@inheritDoc} */
-    public override async Gee.List<Email> list_local_email_async(
+    public override async Gee.Set<Email> get_multiple_email_by_id(
         Gee.Collection<EmailIdentifier> ids,
         Email.Field required_fields,
         GLib.Cancellable? cancellable = null
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index 3167c3e07..6ee57bcda 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -894,6 +894,20 @@ private class Geary.ImapEngine.MinimalFolder : BaseObject,
         );
     }
 
+    /** {@inheritDoc} */
+    public async Gee.Set<Email> get_multiple_email_by_id(
+        Gee.Collection<Geary.EmailIdentifier> ids,
+        Email.Field required_fields,
+        GLib.Cancellable? cancellable = null
+    ) throws GLib.Error {
+        return yield this.local_folder.list_email_by_sparse_id_async(
+            check_ids("get_multiple_email_by_id", ids),
+            required_fields,
+            NONE,
+            cancellable
+        );
+    }
+
     //
     // list email variants
     //
@@ -918,26 +932,6 @@ private class Geary.ImapEngine.MinimalFolder : BaseObject,
         return !op.accumulator.is_empty ? op.accumulator : null;
     }
 
-    public async Gee.List<Geary.Email>? list_email_by_sparse_id_async(
-        Gee.Collection<Geary.EmailIdentifier> ids, Geary.Email.Field required_fields, Folder.ListFlags flags,
-        Cancellable? cancellable = null) throws Error {
-        check_flags("list_email_by_sparse_id_async", flags);
-        check_ids("list_email_by_sparse_id_async", ids);
-
-        if (ids.size == 0)
-            return null;
-
-        // Schedule list operation and wait for completion.
-        // TODO: Break up requests to avoid hogging the queue
-        ListEmailBySparseID op = new ListEmailBySparseID(this, (Gee.Collection<ImapDB.EmailIdentifier>) ids,
-            required_fields, flags, cancellable);
-        replay_queue.schedule(op);
-
-        yield op.wait_for_ready_async(cancellable);
-
-        return !op.accumulator.is_empty ? op.accumulator : null;
-    }
-
     // Helper function for child classes dealing with the
     // delete/archive question.  This method will mark the message as
     // deleted and expunge it.
@@ -982,9 +976,14 @@ private class Geary.ImapEngine.MinimalFolder : BaseObject,
             throw new EngineError.BAD_PARAMETERS("Email ID %s is not IMAP Email ID", id.to_string());
     }
 
-    private void check_ids(string method, Gee.Collection<EmailIdentifier> ids) throws EngineError {
-        foreach (EmailIdentifier id in ids)
+    private Gee.Collection<ImapDB.EmailIdentifier> check_ids(
+        string method,
+        Gee.Collection<EmailIdentifier> ids
+    ) throws EngineError {
+        foreach (EmailIdentifier id in ids) {
             check_id(method, id);
+        }
+        return (Gee.Collection<ImapDB.EmailIdentifier>) ids;
     }
 
     public virtual async void
@@ -1186,52 +1185,59 @@ private class Geary.ImapEngine.MinimalFolder : BaseObject,
         // we support IMAP CONDSTORE (Bug 713117).
         int chunk_size = FLAG_UPDATE_START_CHUNK;
         Geary.EmailIdentifier? lowest = null;
-        while (this.remote_session != null) {
-            Gee.List<Geary.Email>? list_local = yield list_email_by_id_async(
+        while (true) {
+            Gee.List<Geary.Email>? local = yield list_email_by_id_async(
                 lowest, chunk_size,
                 Geary.Email.Field.FLAGS,
                 Geary.Folder.ListFlags.LOCAL_ONLY,
                 cancellable
             );
-            if (list_local == null || list_local.is_empty)
+            if (local == null ||
+                local.is_empty ||
+                this.remote_session == null) {
                 break;
+            }
 
             // find the lowest for the next iteration
-            lowest = Geary.EmailIdentifier.sort_emails(list_local).first().id;
+            var sorted = EmailIdentifier.sort_emails(local);
+            lowest = sorted.first().id;
 
             // Get all email identifiers in the local folder mapped to their EmailFlags
-            Gee.HashMap<Geary.EmailIdentifier, Geary.EmailFlags> local_map =
-                new Gee.HashMap<Geary.EmailIdentifier, Geary.EmailFlags>();
-            foreach (Geary.Email e in list_local)
+            var local_map = new Gee.HashMap<EmailIdentifier,EmailFlags>();
+            foreach (Geary.Email e in local) {
                 local_map.set(e.id, e.email_flags);
+            }
 
-            // Fetch e-mail from folder using force update, which will cause the cache to be bypassed
-            // and the latest to be gotten from the server (updating the cache in the process)
             debug("Fetching %d flags", local_map.keys.size);
-            Gee.List<Geary.Email>? list_remote = yield list_email_by_sparse_id_async(
-                local_map.keys,
-                Email.Field.FLAGS,
-                Folder.ListFlags.FORCE_UPDATE |
-                // Updating read/unread count here breaks the unread
-                // count, so don't do it. See issue #213.
-                Folder.ListFlags.NO_UNREAD_UPDATE,
-                cancellable
-            );
-            if (list_remote == null || list_remote.is_empty)
-                break;
-
-            // Build map of emails that have changed.
-            Gee.HashMap<Geary.EmailIdentifier, Geary.EmailFlags> changed_map =
-                new Gee.HashMap<Geary.EmailIdentifier, Geary.EmailFlags>();
-            foreach (Geary.Email e in list_remote) {
-                if (!local_map.has_key(e.id))
-                    continue;
+            Gee.List<Email>? remote =
+                yield this.remote_session.list_email_async(
+                    new Imap.MessageSet.uid_range(
+                        ((ImapDB.EmailIdentifier) lowest).uid,
+                        ((ImapDB.EmailIdentifier) sorted.last().id).uid
+                    ),
+                    FLAGS,
+                    cancellable
+                );
 
-                if (!local_map.get(e.id).equal_to(e.email_flags))
-                    changed_map.set(e.id, e.email_flags);
-            }
+            if (remote != null && !remote.is_empty) {
+                var changed_set = new Gee.HashSet<Email>();
+                var changed_map = new Gee.HashMap<EmailIdentifier,EmailFlags>();
+                foreach (var email in remote) {
+                    var local_flags = local_map.get(email.id);
+                    var remote_flags = email.email_flags;
+                    if (local_flags != null &&
+                        local_flags.equal_to(remote_flags)) {
+                        changed_set.add(email);
+                        changed_map.set(email.id, remote_flags);
+                    }
+                }
 
-            if (!cancellable.is_cancelled() && changed_map.size > 0) {
+                yield this.local_folder.create_or_merge_email_async(
+                    changed_set,
+                    true,
+                    this.harvester,
+                    cancellable
+                );
                 email_flags_changed(changed_map);
             }
 
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
index af9899336..3f9864fe5 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
@@ -59,14 +59,13 @@ private class Geary.ImapEngine.ServerSearchEmail : Geary.ImapEngine.AbstractList
                 local_ids.add(id);
         }
 
-        Gee.List<Geary.Email>? local_list = yield owner.local_folder.list_email_by_sparse_id_async(
+        Gee.Set<Geary.Email> local_list = yield owner.local_folder.list_email_by_sparse_id_async(
             local_ids, required_fields, ImapDB.Folder.ListFlags.PARTIAL_OK, cancellable);
 
         // Build list of local email
         Gee.Map<ImapDB.EmailIdentifier, Geary.Email> map = new Gee.HashMap<ImapDB.EmailIdentifier, 
Geary.Email>();
-        if (local_list != null) {
-            foreach (Geary.Email email in local_list)
-                map.set((ImapDB.EmailIdentifier) email.id, email);
+        foreach (var email in local_list) {
+            map.set((ImapDB.EmailIdentifier) email.id, email);
         }
 
         // Convert into fulfilled and unfulfilled email for the base class to complete
diff --git a/src/engine/meson.build b/src/engine/meson.build
index 92259a1ba..997675d85 100644
--- a/src/engine/meson.build
+++ b/src/engine/meson.build
@@ -208,7 +208,6 @@ engine_vala_sources = files(
   'imap-engine/replay-ops/imap-engine-create-email.vala',
   'imap-engine/replay-ops/imap-engine-empty-folder.vala',
   'imap-engine/replay-ops/imap-engine-list-email-by-id.vala',
-  'imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala',
   'imap-engine/replay-ops/imap-engine-mark-email.vala',
   'imap-engine/replay-ops/imap-engine-move-email-commit.vala',
   'imap-engine/replay-ops/imap-engine-move-email-prepare.vala',
diff --git a/src/engine/outbox/outbox-folder.vala b/src/engine/outbox/outbox-folder.vala
index ac29cb930..a5b2147e7 100644
--- a/src/engine/outbox/outbox-folder.vala
+++ b/src/engine/outbox/outbox-folder.vala
@@ -238,6 +238,32 @@ public class Geary.Outbox.Folder : BaseObject,
         return row_to_email(row);
     }
 
+    /** {@inheritDoc} */
+    public async Gee.Set<Email> get_multiple_email_by_id(
+        Gee.Collection<Geary.EmailIdentifier> ids,
+        Email.Field required_fields,
+        GLib.Cancellable? cancellable = null
+    ) throws GLib.Error {
+        var email = Email.new_identifier_based_set();
+        yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
+            foreach (Geary.EmailIdentifier id in ids) {
+                EmailIdentifier? outbox_id = id as EmailIdentifier;
+                if (outbox_id == null)
+                    throw new EngineError.BAD_PARAMETERS("%s is not outbox EmailIdentifier", id.to_string());
+
+                OutboxRow? row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable);
+                if (row == null)
+                    continue;
+
+                email.add(row_to_email(row));
+            }
+
+            return DONE;
+        }, cancellable);
+
+        return email;
+    }
+
     public async Gee.List<Email>?
         list_email_by_id_async(Geary.EmailIdentifier? _initial_id,
                                int count,
@@ -323,32 +349,6 @@ public class Geary.Outbox.Folder : BaseObject,
         return list;
     }
 
-    public async Gee.List<Geary.Email>?
-        list_email_by_sparse_id_async(Gee.Collection<Geary.EmailIdentifier> ids,
-                                      Geary.Email.Field required_fields,
-                                      Geary.Folder.ListFlags flags,
-                                      GLib.Cancellable? cancellable = null)
-        throws GLib.Error {
-        Gee.List<Geary.Email> list = new Gee.ArrayList<Geary.Email>();
-        yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
-            foreach (Geary.EmailIdentifier id in ids) {
-                EmailIdentifier? outbox_id = id as EmailIdentifier;
-                if (outbox_id == null)
-                    throw new EngineError.BAD_PARAMETERS("%s is not outbox EmailIdentifier", id.to_string());
-
-                OutboxRow? row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable);
-                if (row == null)
-                    continue;
-
-                list.add(row_to_email(row));
-            }
-
-            return Db.TransactionOutcome.DONE;
-        }, cancellable);
-
-        return (list.size > 0) ? list : null;
-    }
-
     /** {@inheritDoc} */
     public void set_used_as_custom(bool enabled)
         throws EngineError.UNSUPPORTED {
diff --git a/test/engine/app/app-conversation-monitor-test.vala 
b/test/engine/app/app-conversation-monitor-test.vala
index 36be8abc5..40ac2d29c 100644
--- a/test/engine/app/app-conversation-monitor-test.vala
+++ b/test/engine/app/app-conversation-monitor-test.vala
@@ -273,8 +273,11 @@ class Geary.App.ConversationMonitorTest : TestCase {
         ConversationMonitor monitor = setup_monitor();
         assert_equal<int?>(monitor.size, 0, "Initial conversation count");
 
-        this.base_folder.expect_call("list_email_by_sparse_id_async")
-            .returns_object(new Gee.ArrayList<Email>.wrap({e1}));
+        this.base_folder.expect_call(
+            "get_multiple_email_by_id"
+        ).returns_object(
+            Collection.single_set(e1)
+        );
 
         this.account.expect_call("get_special_folder");
         this.account.expect_call("get_special_folder");
@@ -284,7 +287,9 @@ class Geary.App.ConversationMonitorTest : TestCase {
         this.account.expect_call("get_containing_folders_async")
             .returns_object(paths);
 
-        this.base_folder.email_appended(new Gee.ArrayList<EmailIdentifier>.wrap({e1.id}));
+        this.base_folder.email_appended(
+            new Gee.ArrayList<EmailIdentifier>.wrap({e1.id})
+        );
 
         wait_for_signal(monitor, "conversations-added");
         this.base_folder.assert_expectations();
@@ -353,10 +358,17 @@ class Geary.App.ConversationMonitorTest : TestCase {
         ConversationMonitor monitor = setup_monitor({e1}, paths);
         assert_equal<int?>(monitor.size, 1, "Initial conversation count");
 
-        this.other_folder.expect_call("list_email_by_sparse_id_async")
-            .returns_object(new Gee.ArrayList<Email>.wrap({e3}));
-        this.other_folder.expect_call("list_email_by_sparse_id_async")
-            .returns_object(new Gee.ArrayList<Email>.wrap({e3}));
+        this.other_folder.expect_call(
+            "get_multiple_email_by_id"
+        ).returns_object(
+            Collection.single_set(e3)
+        );
+
+        this.other_folder.expect_call(
+            "get_multiple_email_by_id"
+        ).returns_object(
+            Collection.single_set(e3)
+        );
 
         // ExternalAppendOperation's blacklist check
         this.account.expect_call("get_special_folder");
@@ -435,7 +447,7 @@ class Geary.App.ConversationMonitorTest : TestCase {
             this.base_folder
         );
 
-        this.base_folder.expect_call("list_email_by_sparse_id_async");
+        this.base_folder.expect_call("get_multiple_email_by_id");
         this.base_folder.expect_call("list_email_by_id_async");
 
         wait_for_signal(monitor, "email-flags-changed");
diff --git a/test/engine/imap-db/imap-db-account-test.vala b/test/engine/imap-db/imap-db-account-test.vala
index 41b433603..db51d6b64 100644
--- a/test/engine/imap-db/imap-db-account-test.vala
+++ b/test/engine/imap-db/imap-db-account-test.vala
@@ -332,13 +332,15 @@ class Geary.ImapDB.AccountTest : TestCase {
             null,
             this.async_completion
         );
-        Gee.List<Email> result = this.account.list_email.end(
+        Gee.Set<Email> result = this.account.list_email.end(
             async_result()
         );
 
         assert_equal<int?>(result.size, 2, "Not enough email listed");
-        assert_true(new EmailIdentifier(1, null).equal_to(result[0].id));
-        assert_true(new EmailIdentifier(2, null).equal_to(result[1].id));
+
+        var list = traverse(result).to_array_list();
+        assert_true(new EmailIdentifier(1, null).equal_to(list[0].id));
+        assert_true(new EmailIdentifier(2, null).equal_to(list[1].id));
 
         this.account.list_email.begin(
             Collection.single(new EmailIdentifier(3, null)),
diff --git a/test/mock/mock-account.vala b/test/mock/mock-account.vala
index e1f9d72a9..1622f1894 100644
--- a/test/mock/mock-account.vala
+++ b/test/mock/mock-account.vala
@@ -198,13 +198,13 @@ public class Mock.Account : Geary.Account,
         );
     }
 
-    public override async Gee.List<Geary.Email> list_local_email_async(
+    public override async Gee.Set<Geary.Email> get_multiple_email_by_id(
         Gee.Collection<Geary.EmailIdentifier> ids,
         Geary.Email.Field required_fields,
         GLib.Cancellable? cancellable = null
     ) throws GLib.Error {
-        return object_or_throw_call<Gee.List<Geary.Email>>(
-            "list_local_email_async",
+        return object_or_throw_call<Gee.Set<Geary.Email>>(
+            "get_multiple_email_by_id",
             {ids, box_arg(required_fields), cancellable},
             new Geary.EngineError.NOT_FOUND("Mock call")
         );
diff --git a/test/mock/mock-folder.vala b/test/mock/mock-folder.vala
index ae00d4840..0663b7d49 100644
--- a/test/mock/mock-folder.vala
+++ b/test/mock/mock-folder.vala
@@ -82,6 +82,18 @@ public class Mock.Folder : GLib.Object,
         );
     }
 
+    public async Gee.Set<Geary.Email>
+        get_multiple_email_by_id(Gee.Collection<Geary.EmailIdentifier> ids,
+                                 Geary.Email.Field required_fields,
+                                 GLib.Cancellable? cancellable = null
+        ) throws GLib.Error {
+        return yield object_call_async<Gee.Set<Geary.Email>>(
+            "get_multiple_email_by_id",
+            {ids, box_arg(required_fields), cancellable},
+            new Gee.HashSet<Geary.Email>()
+        );
+    }
+
     public async Gee.List<Geary.Email>?
         list_email_by_id_async(Geary.EmailIdentifier? initial_id,
                                int count,
@@ -96,19 +108,6 @@ public class Mock.Folder : GLib.Object,
         );
     }
 
-    public async Gee.List<Geary.Email>?
-        list_email_by_sparse_id_async(Gee.Collection<Geary.EmailIdentifier> ids,
-                                      Geary.Email.Field required_fields,
-                                      Geary.Folder.ListFlags flags,
-                                      GLib.Cancellable? cancellable = null)
-        throws GLib.Error {
-        return yield object_call_async<Gee.List<Geary.Email>?>(
-            "list_email_by_sparse_id_async",
-            {ids, box_arg(required_fields), box_arg(flags), cancellable},
-            null
-        );
-    }
-
     public void set_used_as_custom(bool enabled)
         throws Geary.EngineError.UNSUPPORTED {
         throw new Geary.EngineError.UNSUPPORTED("Mock method");
diff --git a/test/mock/mock-remote-folder.vala b/test/mock/mock-remote-folder.vala
index 1ccc0db80..ef7717024 100644
--- a/test/mock/mock-remote-folder.vala
+++ b/test/mock/mock-remote-folder.vala
@@ -130,6 +130,18 @@ public class Mock.RemoteFolder : GLib.Object,
         );
     }
 
+    public async Gee.Set<Geary.Email>
+        get_multiple_email_by_id(Gee.Collection<Geary.EmailIdentifier> ids,
+                                 Geary.Email.Field required_fields,
+                                 GLib.Cancellable? cancellable = null
+        ) throws GLib.Error {
+        return yield object_call_async<Gee.Set<Geary.Email>>(
+            "get_multiple_email_by_id",
+            {ids, box_arg(required_fields), cancellable},
+            new Gee.HashSet<Geary.Email>()
+        );
+    }
+
     public async Gee.List<Geary.Email>?
         list_email_by_id_async(Geary.EmailIdentifier? initial_id,
                                int count,
@@ -144,19 +156,6 @@ public class Mock.RemoteFolder : GLib.Object,
         );
     }
 
-    public async Gee.List<Geary.Email>?
-        list_email_by_sparse_id_async(Gee.Collection<Geary.EmailIdentifier> ids,
-                                      Geary.Email.Field required_fields,
-                                      Geary.Folder.ListFlags flags,
-                                      GLib.Cancellable? cancellable = null)
-        throws GLib.Error {
-        return yield object_call_async<Gee.List<Geary.Email>?>(
-            "list_email_by_sparse_id_async",
-            {ids, box_arg(required_fields), box_arg(flags), cancellable},
-            null
-        );
-    }
-
     public void set_used_as_custom(bool enabled)
         throws Geary.EngineError.UNSUPPORTED {
         throw new Geary.EngineError.UNSUPPORTED("Mock method");


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]