[geary/wip/734757-msgset] First take, some code paths not efficient w/ network resources



commit 3a590fb1a9ee2b1addbdebe0e40af3998c6ed53b
Author: Jim Nelson <jim yorba org>
Date:   Thu Aug 14 13:50:42 2014 -0700

    First take, some code paths not efficient w/ network resources

 .../imap-engine/imap-engine-minimal-folder.vala    |   66 +++++++++--------
 .../imap-engine-abstract-list-email.vala           |   10 ++-
 .../replay-ops/imap-engine-copy-email.vala         |    5 +-
 .../replay-ops/imap-engine-mark-email.vala         |    9 ++-
 .../replay-ops/imap-engine-move-email.vala         |   10 ++-
 .../replay-ops/imap-engine-remove-email.vala       |    9 +-
 src/engine/imap/command/imap-message-set.vala      |   78 ++++++++++++++------
 7 files changed, 115 insertions(+), 72 deletions(-)
---
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index 2808ab0..ceb9f2c 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -316,13 +316,17 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
         
         // fetch from the server the local store's required flags for all appended/inserted messages
         // (which is simply equal to all remaining remote UIDs)
-        Gee.List<Geary.Email>? to_create = null;
+        Gee.List<Geary.Email> to_create = new Gee.ArrayList<Geary.Email>();
         if (remote_uids.size > 0) {
             // for new messages, get the local store's required fields (which provide duplicate
             // detection)
-            to_create = yield remote_folder.list_email_async(
-                new Imap.MessageSet.uid_sparse(remote_uids.to_array()), ImapDB.Folder.REQUIRED_FIELDS,
-                cancellable);
+            Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(remote_uids);
+            foreach (Imap.MessageSet msg_set in msg_sets) {
+                Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(msg_set,
+                    ImapDB.Folder.REQUIRED_FIELDS, cancellable);
+                if (list != null && list.size > 0)
+                    to_create.add_all(list);
+            }
         }
         
         check_open("normalize_folders (list remote appended/inserted required fields)");
@@ -332,7 +336,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
         Gee.Set<ImapDB.EmailIdentifier> locally_appended_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
         Gee.Set<ImapDB.EmailIdentifier> inserted_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
         Gee.Set<ImapDB.EmailIdentifier> locally_inserted_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
-        if (to_create != null && to_create.size > 0) {
+        if (to_create.size > 0) {
             Gee.Map<Email, bool>? created_or_merged = yield local_folder.create_or_merge_email_async(
                 to_create, cancellable);
             assert(created_or_merged != null);
@@ -931,34 +935,36 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
         Gee.HashSet<Geary.EmailIdentifier> created = new Gee.HashSet<Geary.EmailIdentifier>();
         Gee.HashSet<Geary.EmailIdentifier> appended = new Gee.HashSet<Geary.EmailIdentifier>();
         try {
-            Imap.MessageSet msg_set = new Imap.MessageSet.sparse(remote_positions.to_array());
-            Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(msg_set,
-                ImapDB.Folder.REQUIRED_FIELDS, null);
-            if (list != null && list.size > 0) {
-                debug("%s do_replay_appended_message: %d new messages in %s", to_string(),
-                    list.size, msg_set.to_string());
-                
-                // need to report both if it was created (not known before) and appended (which
-                // could mean created or simply a known email associated with this folder)
-                Gee.Map<Geary.Email, bool> created_or_merged =
-                    yield local_folder.create_or_merge_email_async(list, null);
-                foreach (Geary.Email email in created_or_merged.keys) {
-                    // true means created
-                    if (created_or_merged.get(email)) {
-                        debug("%s do_replay_appended_message: appended email ID %s added",
-                            to_string(), email.id.to_string());
+            Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.sparse(remote_positions);
+            foreach (Imap.MessageSet msg_set in msg_sets) {
+                Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(msg_set,
+                    ImapDB.Folder.REQUIRED_FIELDS, null);
+                if (list != null && list.size > 0) {
+                    debug("%s do_replay_appended_message: %d new messages in %s", to_string(),
+                        list.size, msg_set.to_string());
+                    
+                    // need to report both if it was created (not known before) and appended (which
+                    // could mean created or simply a known email associated with this folder)
+                    Gee.Map<Geary.Email, bool> created_or_merged =
+                        yield local_folder.create_or_merge_email_async(list, null);
+                    foreach (Geary.Email email in created_or_merged.keys) {
+                        // true means created
+                        if (created_or_merged.get(email)) {
+                            debug("%s do_replay_appended_message: appended email ID %s added",
+                                to_string(), email.id.to_string());
+                            
+                            created.add(email.id);
+                        } else {
+                            debug("%s do_replay_appended_message: appended email ID %s associated",
+                                to_string(), email.id.to_string());
+                        }
                         
-                        created.add(email.id);
-                    } else {
-                        debug("%s do_replay_appended_message: appended email ID %s associated",
-                            to_string(), email.id.to_string());
+                        appended.add(email.id);
                     }
-                    
-                    appended.add(email.id);
+                } else {
+                    debug("%s do_replay_appended_message: no new messages in %s", to_string(),
+                        msg_set.to_string());
                 }
-            } else {
-                debug("%s do_replay_appended_message: no new messages in %s", to_string(),
-                    msg_set.to_string());
             }
         } catch (Error err) {
             debug("%s do_replay_appended_message: Unable to process: %s",
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
index ebe90c0..1e76607 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
@@ -139,10 +139,12 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
             if (unfulfilled_uids.size == 0)
                 continue;
             
-            RemoteBatchOperation remote_op = new RemoteBatchOperation(owner,
-                new Imap.MessageSet.uid_sparse(unfulfilled_uids.to_array()), unfulfilled_fields,
-                required_fields);
-            batch.add(remote_op);
+            Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(unfulfilled_uids);
+            foreach (Imap.MessageSet msg_set in msg_sets) {
+                RemoteBatchOperation remote_op = new RemoteBatchOperation(owner, msg_set,
+                    unfulfilled_fields, required_fields);
+                batch.add(remote_op);
+            }
         }
         
         yield batch.execute_all_async(cancellable);
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
index 9fec2ae..0d68888 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
@@ -45,8 +45,9 @@ private class Geary.ImapEngine.CopyEmail : Geary.ImapEngine.SendReplayOperation
             ImapDB.Folder.ListFlags.NONE, cancellable);
         
         if (uids != null && uids.size > 0) {
-            yield engine.remote_folder.copy_email_async(
-                new Imap.MessageSet.uid_sparse(uids.to_array()), destination, cancellable);
+            Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(uids);
+            foreach (Imap.MessageSet msg_set in msg_sets)
+                yield engine.remote_folder.copy_email_async(msg_set, destination, cancellable);
         }
         
         return ReplayOperation.Status.COMPLETED;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
index e40f3fa..0965920 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
@@ -63,9 +63,12 @@ private class Geary.ImapEngine.MarkEmail : Geary.ImapEngine.SendReplayOperation
         if (original_flags.size == 0)
             return ReplayOperation.Status.COMPLETED;
         
-        yield engine.remote_folder.mark_email_async(
-            new Imap.MessageSet.uid_sparse(ImapDB.EmailIdentifier.to_uids(original_flags.keys).to_array()),
-            flags_to_add, flags_to_remove, cancellable);
+        Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(
+            ImapDB.EmailIdentifier.to_uids(original_flags.keys));
+        foreach (Imap.MessageSet msg_set in msg_sets) {
+            yield engine.remote_folder.mark_email_async(msg_set, flags_to_add, flags_to_remove,
+                cancellable);
+        }
         
         return ReplayOperation.Status.COMPLETED;
     }
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
index 04d5a19..236932b 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
@@ -67,10 +67,12 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
         if (cancellable != null && cancellable.is_cancelled())
             throw new IOError.CANCELLED("Move email to %s cancelled", engine.remote_folder.to_string());
         
-        Imap.MessageSet msg_set = new 
Imap.MessageSet.uid_sparse(ImapDB.EmailIdentifier.to_uids(moved_ids).to_array());
-        
-        yield engine.remote_folder.copy_email_async(msg_set, destination, null);
-        yield engine.remote_folder.remove_email_async(msg_set, null);
+        Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(
+            ImapDB.EmailIdentifier.to_uids(moved_ids));
+        foreach (Imap.MessageSet msg_set in msg_sets) {
+            yield engine.remote_folder.copy_email_async(msg_set, destination, null);
+            yield engine.remote_folder.remove_email_async(msg_set, null);
+        }
         
         return ReplayOperation.Status.COMPLETED;
     }
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
index 99bc2d4..234ab36 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
@@ -60,11 +60,10 @@ private class Geary.ImapEngine.RemoveEmail : Geary.ImapEngine.SendReplayOperatio
         // Remove from server. Note that this causes the receive replay queue to kick into
         // action, removing the e-mail but *NOT* firing a signal; the "remove marker" indicates
         // that the signal has already been fired.
-        if (removed_ids.size > 0) {
-            yield engine.remote_folder.remove_email_async(
-                new Imap.MessageSet.uid_sparse(ImapDB.EmailIdentifier.to_uids(removed_ids).to_array()),
-                cancellable);
-        }
+        Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(
+            ImapDB.EmailIdentifier.to_uids(removed_ids));
+        foreach (Imap.MessageSet msg_set in msg_sets)
+            yield engine.remote_folder.remove_email_async(msg_set, cancellable);
         
         return ReplayOperation.Status.COMPLETED;
     }
diff --git a/src/engine/imap/command/imap-message-set.vala b/src/engine/imap/command/imap-message-set.vala
index b410e39..759f372 100644
--- a/src/engine/imap/command/imap-message-set.vala
+++ b/src/engine/imap/command/imap-message-set.vala
@@ -16,6 +16,12 @@ extern void qsort(void *base, size_t num, size_t size, CompareFunc compare_func)
  */
 
 public class Geary.Imap.MessageSet : BaseObject {
+    // 2^32 in base 10 requires four bytes (characters) on the wire plus a separator between each
+    // one, means ~11 bytes per seqnum or UID ... to keep lists below server maximums, this value
+    // is set to keep max. command length somewhere under 1K (including tag, command, parameters,
+    // etc.)
+    private const int MAX_SPARSE_VALUES_PER_SET = 50;
+    
     /**
      * True if the { link MessageSet} was created with a UID or a UID range.
      *
@@ -67,7 +73,7 @@ public class Geary.Imap.MessageSet : BaseObject {
         assert(low.value > 0);
         assert(high.value > 0);
         
-        // corrent ordering
+        // correct ordering
         if (low.value > high.value) {
             UID swap = low;
             low = high;
@@ -95,16 +101,35 @@ public class Geary.Imap.MessageSet : BaseObject {
         is_uid = true;
     }
     
-    public static Gee.List<MessageSet> sparse(SequenceNumber[] seq_nums) {
+    public MessageSet.custom(string custom) {
+        value = custom;
+    }
+    
+    public MessageSet.uid_custom(string custom) {
+        value = custom;
+        is_uid = true;
+    }
+    
+    /**
+     * Convert a collection of { link SequenceNumber}s into a list of { link MessageSet}s.
+     *
+     * Although this could return a single MessageSet, large collections could create an IMAP
+     * command beyond the server maximum, and so they will be broken up into multiple sets.
+     */
+    public static Gee.List<MessageSet> sparse(Gee.Collection<SequenceNumber> seq_nums) {
         return build_sparse(seq_array_to_int64(seq_nums), false);
     }
     
-    public static Gee.List<MessageSet> uid_sparse(UID[] msg_uids) {
+    /**
+     * Convert a collection of { link UID}s into a list of { link MessageSet}s.
+     *
+     * Although this could return a single MessageSet, large collections could create an IMAP
+     * command beyond the server maximum, and so they will be broken up into multiple sets.
+     */
+    public static Gee.List<MessageSet> uid_sparse(Gee.Collection<UID> msg_uids) {
         return build_sparse(uid_array_to_int64(msg_uids), true);
     }
     
-    private const int MAX_SPARSE_VALUES = 5;
-    
     private static Gee.List<MessageSet> build_sparse(int64[] sorted, bool is_uid) {
         Gee.List<MessageSet> list = new Gee.ArrayList<MessageSet>();
         
@@ -113,34 +138,29 @@ public class Geary.Imap.MessageSet : BaseObject {
             if (start_index >= sorted.length)
                 break;
             
-            int end_index = (start_index + MAX_SPARSE_VALUES).clamp(0, sorted.length - 1);
+            int end_index = (start_index + MAX_SPARSE_VALUES_PER_SET).clamp(0, sorted.length);
             int64[] slice = sorted[start_index:end_index];
             
-            debug("MessageSet.build_sparse: sorted=%d start_index=%d end_index=%d slice=%d",
+            debug("MessageSet.build_sparse: sorted=%d start:end=%d:%d slice=%d",
                 sorted.length, start_index, end_index, slice.length);
             
-            list.add(new MessageSet.custom(build_sparse_range(slice), is_uid));
+            string sparse_range = build_sparse_range(slice);
+            list.add(is_uid ? new MessageSet.uid_custom(sparse_range) : new MessageSet.custom(sparse_range));
+            
+            debug("MessageSet.build_sparse: sorted=%d start:end=%d:%d slice=%d sparse_range=%s",
+                sorted.length, start_index, end_index, slice.length, sparse_range);
             
-            start_index += MAX_SPARSE_VALUES;
+            start_index += MAX_SPARSE_VALUES_PER_SET;
         }
         
         return list;
     }
     
-    private MessageSet.custom(string custom, bool is_uid) {
-        value = custom;
-        this.is_uid = is_uid;
-    }
-    
-    // Builds sparse range of either UID values or message numbers.
-    // NOTE: This method assumes the supplied array is internally allocated, and so an in-place sort
-    // is allowable
+    // Builds sparse range of either UID values or message numbers.  Values should be sorted before
+    // calling to maximum finding runs.
     private static string build_sparse_range(int64[] seq_nums) {
         assert(seq_nums.length > 0);
         
-        // sort array to search for spans
-        qsort(seq_nums, seq_nums.length, sizeof(int64), Numeric.int64_compare);
-        
         int64 start_of_span = -1;
         int64 last_seq_num = -1;
         int span_count = 0;
@@ -195,17 +215,27 @@ public class Geary.Imap.MessageSet : BaseObject {
         return builder.str;
     }
     
-    private static int64[] seq_array_to_int64(SequenceNumber[] seq_nums) {
+    private static int64[] seq_array_to_int64(Gee.Collection<SequenceNumber> seq_nums) {
+        // guarantee sorted (to maximum finding runs in build_sparse_range())
+        Gee.TreeSet<SequenceNumber> sorted = new Gee.TreeSet<SequenceNumber>();
+        sorted.add_all(seq_nums);
+        
+        // build sorted array
         int64[] ret = new int64[0];
-        foreach (SequenceNumber seq_num in seq_nums)
+        foreach (SequenceNumber seq_num in sorted)
             ret += (int64) seq_num.value;
         
         return ret;
     }
     
-    private static int64[] uid_array_to_int64(UID[] msg_uids) {
+    private static int64[] uid_array_to_int64(Gee.Collection<UID> msg_uids) {
+        // guarantee sorted (to maximize finding runs in build_sparse_range())
+        Gee.TreeSet<UID> sorted = new Gee.TreeSet<UID>();
+        sorted.add_all(msg_uids);
+        
+        // build sorted array
         int64[] ret = new int64[0];
-        foreach (UID uid in msg_uids)
+        foreach (UID uid in sorted)
             ret += uid.value;
         
         return ret;


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