[geary/wip/713150-conversations] Optimization: get associations and load emails in one call



commit 0e4423a44c6fdb2abc54fd7eadb7ee830ac2fe15
Author: Jim Nelson <jim yorba org>
Date:   Thu Mar 26 17:38:22 2015 -0700

    Optimization: get associations and load emails in one call

 src/client/application/geary-controller.vala       |    3 +
 src/engine/api/geary-account.vala                  |    4 +-
 src/engine/api/geary-associated-emails.vala        |   34 +++++++++++--
 .../api/geary-folder-supports-associations.vala    |    3 +-
 src/engine/app/app-conversation-monitor.vala       |   51 +++++++-------------
 src/engine/imap-db/imap-db-account.vala            |    6 +-
 src/engine/imap-db/imap-db-conversation.vala       |   16 ++++--
 src/engine/imap-db/imap-db-folder.vala             |    6 +-
 .../imap-engine/imap-engine-generic-account.vala   |    8 ++--
 .../imap-engine/imap-engine-minimal-folder.vala    |    6 +-
 10 files changed, 76 insertions(+), 61 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index dfc9c2e..4c54249 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -1579,6 +1579,9 @@ public class GearyController : Geary.BaseObject {
         int list_height = main_window.conversation_list_view.get_allocated_height();
         int cells = (list_height / cell_dimensions.cell_height) + 1;
         
+        // add one LOAD_MORE_PERCENTAGE, as though the user had requested one load more already
+        cells += (int) Math.round((double) cells * LOAD_MORE_PERCENTAGE);
+        
         if (current_conversations.min_window_count < cells)
             current_conversations.min_window_count = cells;
     }
diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala
index 204a5c3..e6f63d8 100644
--- a/src/engine/api/geary-account.vala
+++ b/src/engine/api/geary-account.vala
@@ -404,8 +404,8 @@ public abstract class Geary.Account : BaseObject {
      * locally.
      */
     public abstract async Gee.Collection<Geary.AssociatedEmails>? local_search_associated_emails_async(
-        Gee.Collection<Geary.EmailIdentifier> email_ids, EmailSearchPredicate? search_predicate,
-        Cancellable? cancellable = null) throws Error;
+        Gee.Collection<Geary.EmailIdentifier> email_ids, Geary.Email.Field required_fields,
+        EmailSearchPredicate? search_predicate, Cancellable? cancellable = null) throws Error;
     
     /**
      * Return a listing of local { link Email} fulfilling the required fields.
diff --git a/src/engine/api/geary-associated-emails.vala b/src/engine/api/geary-associated-emails.vala
index d84ca3f..d64a254 100644
--- a/src/engine/api/geary-associated-emails.vala
+++ b/src/engine/api/geary-associated-emails.vala
@@ -19,7 +19,14 @@ public class Geary.AssociatedEmails : BaseObject {
     /**
      * All associated { link EmailIdentifier}s.
      */
-    public Gee.Collection<Geary.EmailIdentifier> email_ids { get; private set; }
+    public Gee.Set<Geary.EmailIdentifier> email_ids { get; private set; }
+    
+    /**
+     * All associated { link Email}s with { link required_fields} fulfilled, if possible.
+     *
+     * It's possible for the Email to have ''more'' than the required fields as well.
+     */
+    public Gee.Map<Geary.EmailIdentifier, Geary.Email> emails { get; private set; }
     
     /**
      * All known { link FolderPath}s for each { link EmailIdentifier}.
@@ -28,15 +35,30 @@ public class Geary.AssociatedEmails : BaseObject {
      */
     public Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath?> known_paths { get; private set; }
     
-    public AssociatedEmails() {
-        email_ids = new Gee.ArrayList<EmailIdentifier>();
+    /**
+     * The required { link Geary.Email.Field}s specified when the { link AssociatedEmails} was
+     * generated.
+     */
+    public Geary.Email.Field required_fields { get; private set; }
+    
+    public AssociatedEmails(Geary.Email.Field required_fields) {
+        this.required_fields = required_fields;
+        
+        email_ids = new Gee.HashSet<EmailIdentifier>();
+        emails = new Gee.HashMap<EmailIdentifier, Email>();
         known_paths = new Gee.HashMultiMap<Email, FolderPath?>();
     }
     
-    public void add(Geary.EmailIdentifier email_id, Gee.Collection<Geary.FolderPath?> paths) {
-        email_ids.add(email_id);
+    /**
+     * Add the { link Email} to the set of { link AssociatedEmails}.
+     *
+     * No checking is performed to ensure the Email fulfills { link required_fields}.
+     */
+    public void add(Geary.Email email, Gee.Collection<Geary.FolderPath?> paths) {
+        email_ids.add(email.id);
+        emails.set(email.id, email);
         foreach (FolderPath path in paths)
-            known_paths.set(email_id, path);
+            known_paths.set(email.id, path);
     }
 }
 
diff --git a/src/engine/api/geary-folder-supports-associations.vala 
b/src/engine/api/geary-folder-supports-associations.vala
index 9886972..20a5685 100644
--- a/src/engine/api/geary-folder-supports-associations.vala
+++ b/src/engine/api/geary-folder-supports-associations.vala
@@ -52,7 +52,8 @@ public interface Geary.FolderSupport.Associations : Geary.Folder {
      * @see Geary.Folder.find_boundaries_async
      */
     public abstract async Gee.Collection<Geary.AssociatedEmails>? local_list_associated_emails_async(
-        Geary.EmailIdentifier? initial_id, int count, Geary.Account.EmailSearchPredicate? predicate,
+        Geary.EmailIdentifier? initial_id, int count, Geary.Email.Field required_fields,
+        Geary.Account.EmailSearchPredicate? predicate,
         Gee.Collection<Geary.EmailIdentifier>? primary_loaded_ids,
         Gee.Collection<Geary.EmailIdentifier>? already_seen_ids, Cancellable? cancellable = null)
         throws Error;
diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala
index 448d315..43b8a03 100644
--- a/src/engine/app/app-conversation-monitor.vala
+++ b/src/engine/app/app-conversation-monitor.vala
@@ -19,8 +19,8 @@ public class Geary.App.ConversationMonitor : BaseObject {
      * These are the fields Conversations require to thread emails together.  These fields will
      * be retrieved regardless of the Field parameter passed to the constructor.
      */
-    public const Geary.Email.Field REQUIRED_FIELDS = Geary.Email.Field.REFERENCES |
-        Geary.Email.Field.FLAGS | Geary.Email.Field.DATE;
+    public const Geary.Email.Field REQUIRED_FIELDS =
+        Geary.Email.Field.FLAGS | Geary.Email.Field.DATE | Geary.Email.Field.PROPERTIES;
     
     // An approximate multipler of messages-in-folder-to-conversation ... because the Engine doesn't
     // offer a way to directly load conversations in a folder as a vector unto itself, this class
@@ -501,7 +501,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
         Gee.Collection<EmailIdentifier> primary_email_ids = new Gee.HashSet<EmailIdentifier>();
         try {
             associations = yield supports_associations.local_list_associated_emails_async(
-                low_id, count, predicate_instance.search_predicate, primary_email_ids,
+                low_id, count, required_fields, predicate_instance.search_predicate, primary_email_ids,
                 all_email_id_to_conversation.keys, cancellable);
         } catch (Error err) {
             debug("Unable to load associated emails from %s: %s", supports_associations.to_string(),
@@ -510,7 +510,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
         }
         
         if (associations != null && associations.size > 0) {
-            yield process_associations_async(supports_associations.path, associations, primary_email_ids,
+            process_associations(supports_associations.path, associations, primary_email_ids,
                 cancellable);
         }
         
@@ -544,7 +544,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
         Gee.Collection<AssociatedEmails>? associations = null;
         try {
             associations = yield folder.account.local_search_associated_emails_async(
-                email_ids, predicate_instance.search_predicate, cancellable);
+                email_ids, required_fields, predicate_instance.search_predicate, cancellable);
         } catch (Error err) {
             debug("Unable to search for associated emails: %s", err.message);
         }
@@ -552,20 +552,20 @@ public class Geary.App.ConversationMonitor : BaseObject {
         if (associations == null || associations.size == 0)
             return;
         
-        yield process_associations_async(path, associations, email_ids, cancellable);
+        process_associations(path, associations, email_ids, cancellable);
         
         Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::process_email completed: %d 
emails",
             folder.to_string(), email_ids.size);
     }
     
-    private async void process_associations_async(FolderPath path, Gee.Collection<AssociatedEmails> 
associations,
+    private void process_associations(FolderPath path, Gee.Collection<AssociatedEmails> associations,
         Gee.Collection<Geary.EmailIdentifier> original_email_ids, Cancellable? cancellable) {
         Gee.HashSet<Conversation> added = new Gee.HashSet<Conversation>();
         Gee.HashMultiMap<Conversation, Email> appended = new Gee.HashMultiMap<Conversation, Email>();
         Gee.HashMultiMap<Conversation, EmailIdentifier> paths_changed = new Gee.HashMultiMap<Conversation, 
EmailIdentifier>();
         foreach (AssociatedEmails association in associations) {
-            yield process_association_async(association, path, original_email_ids, added, appended,
-                paths_changed, cancellable);
+            process_association(association, path, original_email_ids, added, appended, paths_changed,
+                cancellable);
         }
         
         if (added.size > 0) {
@@ -591,7 +591,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
             notify_email_paths_changed(conversation, paths_changed.get(conversation));
     }
     
-    private async void process_association_async(AssociatedEmails association, FolderPath path,
+    private void process_association(AssociatedEmails association, FolderPath path,
         Gee.Collection<EmailIdentifier> original_email_ids, Gee.Set<Conversation> added,
         Gee.MultiMap<Conversation, Email> appended, Gee.MultiMap<Conversation, EmailIdentifier> 
paths_changed,
         Cancellable? cancellable) {
@@ -633,15 +633,17 @@ public class Geary.App.ConversationMonitor : BaseObject {
         
         // for all ids, add known paths and associate id to conversation in the various tables,
         // since the paths and conversation may have changed
-        //
-        // if email is not loaded, add to list for loading
-        Gee.HashSet<EmailIdentifier> list_email_ids = new Gee.HashSet<EmailIdentifier>();
+        Gee.Collection<Email> emails_appended = new Gee.ArrayList<Email>();
         foreach (EmailIdentifier associated_id in association.email_ids) {
             if (conversation.has_email(associated_id)) {
                 if (conversation.add_paths(associated_id, association.known_paths[associated_id]))
                     paths_changed.set(conversation, associated_id);
             } else {
-                list_email_ids.add(associated_id);
+                // don't worry if add() reports paths_changed, because we've already checked if the
+                // email was known, don't want to report "email-paths-changed" for a new add
+                Geary.Email email = association.emails[associated_id];
+                conversation.add(email, association.known_paths[associated_id], null);
+                emails_appended.add(email);
             }
             
             all_email_id_to_conversation[associated_id] = conversation;
@@ -652,29 +654,12 @@ public class Geary.App.ConversationMonitor : BaseObject {
                 primary_email_id_to_conversation[associated_id] = conversation;
         }
         
-        // load remaining emails for the conversation objects
-        Gee.Collection<Email>? emails = null;
-        try {
-            emails = yield folder.account.local_list_email_async(list_email_ids, required_fields,
-                cancellable);
-        } catch (Error err) {
-            debug("Unable to list local account email: %s", err.message);
-        }
-        
-        // add all emails and each known path(s) to the Conversation and EmailIdentifier mapping
-        // ... don't worry if add() reports paths_changed, because we've already checked if the
-        // email was known, don't want to report "email-paths-changed" for a new add
-        if (emails != null) {
-            foreach (Email email in emails)
-                conversation.add(email, association.known_paths[email.id], null);
-        }
-        
         // if new, added, otherwise appended (if not already added)
         if (!conversations.contains(conversation)) {
             conversations.add(conversation);
             added.add(conversation);
-        } else if (!added.contains(conversation) && emails != null) {
-            foreach (Email email in emails)
+        } else if (!added.contains(conversation) && emails_appended.size > 0) {
+            foreach (Email email in emails_appended)
                 appended.set(conversation, email);
         }
     }
diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala
index ed1273f..b81bc6e 100644
--- a/src/engine/imap-db/imap-db-account.vala
+++ b/src/engine/imap-db/imap-db-account.vala
@@ -680,8 +680,8 @@ private class Geary.ImapDB.Account : BaseObject {
     }
     
     public async Gee.Collection<Geary.AssociatedEmails>? search_associated_emails_async(
-        Gee.Collection<ImapDB.EmailIdentifier> db_ids, Geary.Account.EmailSearchPredicate? search_predicate,
-        Cancellable? cancellable) throws Error {
+        Gee.Collection<ImapDB.EmailIdentifier> db_ids, Email.Field required_fields,
+        Geary.Account.EmailSearchPredicate? search_predicate, Cancellable? cancellable) throws Error {
         check_open();
         
         Gee.Collection<AssociatedEmails> associations = new Gee.ArrayList<AssociatedEmails>();
@@ -701,7 +701,7 @@ private class Geary.ImapDB.Account : BaseObject {
                 found_ids.add_all(associated_ids);
                 
                 AssociatedEmails? association = Conversation.do_generate_associations(cx, associated_ids,
-                    search_predicate, cancellable);
+                    required_fields, search_predicate, cancellable);
                 if (association != null && association.email_ids.size > 0)
                     associations.add(association);
             }
diff --git a/src/engine/imap-db/imap-db-conversation.vala b/src/engine/imap-db/imap-db-conversation.vala
index 1d6a8e3..905a86c 100644
--- a/src/engine/imap-db/imap-db-conversation.vala
+++ b/src/engine/imap-db/imap-db-conversation.vala
@@ -261,15 +261,19 @@ private Gee.HashSet<ImapDB.EmailIdentifier>? do_fetch_associated_email_ids(Db.Co
 }
 
 internal AssociatedEmails? do_generate_associations(Db.Connection cx,
-    Gee.Collection<ImapDB.EmailIdentifier> associated_ids, Geary.Account.EmailSearchPredicate? predicate,
-    Cancellable? cancellable) throws Error {
-    AssociatedEmails association = new AssociatedEmails();
+    Gee.Collection<ImapDB.EmailIdentifier> associated_ids, Geary.Email.Field required_fields,
+    Geary.Account.EmailSearchPredicate? predicate, Cancellable? cancellable) throws Error {
+    AssociatedEmails association = new AssociatedEmails(required_fields);
+    
+    // need to pull flags for the predicate function
+    if (predicate != null)
+        required_fields |= Geary.Email.Field.FLAGS;
+    
     foreach (ImapDB.EmailIdentifier associated_id in associated_ids) {
         Email? email;
         Gee.Collection<FolderPath?>? known_paths;
         try {
-            Account.do_fetch_message(cx, associated_id.message_id,
-                predicate != null ? Geary.Email.Field.NONE : Geary.Email.Field.FLAGS,
+            Account.do_fetch_message(cx, associated_id.message_id, required_fields,
                 false, predicate, out email, out known_paths, cancellable);
         } catch (Error err) {
             if (err is EngineError.NOT_FOUND)
@@ -279,7 +283,7 @@ internal AssociatedEmails? do_generate_associations(Db.Connection cx,
         }
         
         if (email != null)
-            association.add(email.id, known_paths);
+            association.add(email, known_paths);
     }
     
     return association.email_ids.size > 0 ? association : null;
diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala
index c437f8f..6343dc1 100644
--- a/src/engine/imap-db/imap-db-folder.vala
+++ b/src/engine/imap-db/imap-db-folder.vala
@@ -598,8 +598,8 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
     }
     
     public async Gee.Collection<Geary.AssociatedEmails>? list_associated_emails_async(
-        ImapDB.EmailIdentifier? start_id, int count, Geary.Account.EmailSearchPredicate? predicate,
-        Gee.Collection<Geary.EmailIdentifier>? primary_email_ids,
+        ImapDB.EmailIdentifier? start_id, int count, Email.Field required_fields,
+        Geary.Account.EmailSearchPredicate? predicate, Gee.Collection<Geary.EmailIdentifier>? 
primary_email_ids,
         Gee.Collection<Geary.EmailIdentifier>? already_seen_ids, Cancellable? cancellable) throws Error {
         if (count == 0)
             return null;
@@ -661,7 +661,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
                 found_ids.add_all(associated_ids);
                 
                 AssociatedEmails? association = Conversation.do_generate_associations(cx, associated_ids,
-                    predicate, cancellable);
+                    required_fields, predicate, cancellable);
                 if (association != null && association.email_ids.size > 0) {
                     list.add(association);
                     if (list.size >= count)
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala 
b/src/engine/imap-engine/imap-engine-generic-account.vala
index 6cfb356..e20b38e 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -868,10 +868,10 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
     }
     
     public override async Gee.Collection<Geary.AssociatedEmails>? local_search_associated_emails_async(
-        Gee.Collection<Geary.EmailIdentifier> email_ids, Account.EmailSearchPredicate? search_predicate,
-        Cancellable? cancellable = null) throws Error {
-        return yield local.search_associated_emails_async(check_ids(email_ids), search_predicate,
-            cancellable);
+        Gee.Collection<Geary.EmailIdentifier> email_ids, Email.Field required_fields,
+        Account.EmailSearchPredicate? search_predicate, Cancellable? cancellable = null) throws Error {
+        return yield local.search_associated_emails_async(check_ids(email_ids), required_fields,
+            search_predicate, cancellable);
     }
     
     public override async Gee.Collection<Geary.Email>? local_list_email_async(
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index f029181..feea4d1 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -1479,8 +1479,8 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
     }
     
     public virtual async Gee.Collection<AssociatedEmails>? local_list_associated_emails_async(
-        EmailIdentifier? initial_id, int count, Account.EmailSearchPredicate? predicate,
-        Gee.Collection<EmailIdentifier>? primary_email_ids,
+        EmailIdentifier? initial_id, int count, Email.Field required_fields,
+        Account.EmailSearchPredicate? predicate, Gee.Collection<EmailIdentifier>? primary_email_ids,
         Gee.Collection<Geary.EmailIdentifier>? already_seen_ids, Cancellable? cancellable = null)
         throws Error {
         check_open("local_list_associated_emails_async");
@@ -1493,7 +1493,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
             throw new EngineError.BAD_PARAMETERS("count may not be negative");
         
         return yield local_folder.list_associated_emails_async((ImapDB.EmailIdentifier) initial_id,
-            count, predicate, primary_email_ids, already_seen_ids, cancellable);
+            count, required_fields, predicate, primary_email_ids, already_seen_ids, cancellable);
     }
     
     public void schedule_op(ReplayOperation op) throws Error {


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