[geary/wip/721828-undo] Working round-trip of undo with archive and trash



commit 7abfc1fb8d4ce215a47ae88bc937d03fc9a89183
Author: Jim Nelson <jim yorba org>
Date:   Tue Dec 30 17:11:28 2014 -0800

    Working round-trip of undo with archive and trash

 src/engine/app/app-conversation.vala               |    8 ++-
 src/engine/imap-db/imap-db-folder.vala             |   54 ++++++++++++----
 .../imap-engine/imap-engine-batch-operations.vala  |    2 +-
 .../imap-engine/imap-engine-minimal-folder.vala    |   36 ++++++-----
 .../imap-engine/imap-engine-revokable-move.vala    |    6 +-
 .../replay-ops/imap-engine-copy-email.vala         |   13 +---
 .../replay-ops/imap-engine-move-email.vala         |   69 ++++++++++++++++----
 src/engine/imap/api/imap-folder.vala               |   31 +++++++--
 src/engine/util/util-collection.vala               |    4 +
 9 files changed, 156 insertions(+), 67 deletions(-)
---
diff --git a/src/engine/app/app-conversation.vala b/src/engine/app/app-conversation.vala
index 121a707..3a05693 100644
--- a/src/engine/app/app-conversation.vala
+++ b/src/engine/app/app-conversation.vala
@@ -209,7 +209,7 @@ public class Geary.App.Conversation : BaseObject {
      * known_paths should contain all the known FolderPaths this email is contained in.
      * Conversation will monitor Account for additions and removals as they occur.
      */
-    internal bool add(Email email, Gee.Collection<Geary.FolderPath> known_paths) {
+    internal bool add(Email email, Gee.Collection<Geary.FolderPath>? known_paths) {
         if (emails.has_key(email.id))
             return false;
         
@@ -221,8 +221,10 @@ public class Geary.App.Conversation : BaseObject {
         if (ancestors != null)
             message_ids.add_all(ancestors);
         
-        foreach (Geary.FolderPath path in known_paths)
-            path_map.set(email.id, path);
+        if (known_paths != null) {
+            foreach (Geary.FolderPath path in known_paths)
+                path_map.set(email.id, path);
+        }
         
         appended(email);
         
diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala
index 91d50a8..d382c3f 100644
--- a/src/engine/imap-db/imap-db-folder.vala
+++ b/src/engine/imap-db/imap-db-folder.vala
@@ -727,13 +727,43 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         return id;
     }
     
-    public async void detach_multiple_emails_async(Gee.Collection<ImapDB.EmailIdentifier> ids,
+    // EmailIdentifiers should contain valid UIDs *for this folder* for successful linking.
+    public async void link_multiple_emails_async(Gee.Collection<ImapDB.EmailIdentifier> ids,
+        Cancellable? cancellable) throws Error {
+        int unread_count = 0;
+        yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => {
+            // Can't use do_get_locations_for_ids, as that's keyed to this folder, which these
+            // emails don't belong to yet
+            foreach (ImapDB.EmailIdentifier id in ids) {
+                if (id.uid == null)
+                    continue;
+                
+                Db.Statement stmt = cx.prepare("""
+                    INSERT INTO MessageLocationTable
+                    (message_id, folder_id, ordering)
+                    VALUES (?, ?, ?)
+                """);
+                stmt.bind_rowid(0, id.message_id);
+                stmt.bind_rowid(1, folder_id);
+                stmt.bind_int64(2, id.uid.value);
+                
+                stmt.exec(cancellable);
+            }
+            
+            // Now with messages linked to folder, update unread count
+            unread_count = do_get_unread_count_for_ids(cx, ids, cancellable);
+            do_add_to_unread_count(cx, unread_count, cancellable);
+            
+            return Db.TransactionOutcome.COMMIT;
+        }, cancellable);
+        
+        if (unread_count > 0)
+            properties.set_status_unseen(properties.email_unread + unread_count);
+    }
+    
+    public async void unlink_multiple_emails_async(Gee.Collection<ImapDB.EmailIdentifier> ids,
         Cancellable? cancellable) throws Error {
         int unread_count = 0;
-        // TODO: Right now, deleting an email is merely detaching its association with a folder
-        // (since it may be located in multiple folders).  This means at some point in the future
-        // a vacuum will be required to remove emails that are completely unassociated with the
-        // account.
         yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => {
             Gee.List<LocationIdentifier>? locs = do_get_locations_for_ids(cx, ids,
                 ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
@@ -766,7 +796,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
             properties.set_status_unseen(properties.email_unread - unread_count);
     }
     
-    public async void detach_all_emails_async(Cancellable? cancellable) throws Error {
+    public async void unlink_all_emails_async(Cancellable? cancellable) throws Error {
         yield db.exec_transaction_async(Db.TransactionType.WO, (cx) => {
             Db.Statement stmt = cx.prepare(
                 "DELETE FROM MessageLocationTable WHERE folder_id=?");
@@ -899,7 +929,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         }
     }
     
-    public async void detach_single_email_async(ImapDB.EmailIdentifier id, out bool is_marked,
+    public async void unlink_single_email_async(ImapDB.EmailIdentifier id, out bool is_marked,
         Cancellable? cancellable) throws Error {
         bool internal_is_marked = false;
         bool was_unread = false;
@@ -920,7 +950,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
             
             internal_is_marked = location.marked_removed;
             
-            do_remove_association_with_folder(cx, location, cancellable);
+            do_unlink_from_folder(cx, location, cancellable);
             
             return Db.TransactionOutcome.COMMIT;
         }, cancellable);
@@ -1225,7 +1255,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
     }
     
     // Note: does NOT check if message is already associated with thie folder
-    private void do_associate_with_folder(Db.Connection cx, int64 message_id, Imap.UID uid,
+    private void do_link_with_folder(Db.Connection cx, int64 message_id, Imap.UID uid,
         Cancellable? cancellable) throws Error {
         assert(message_id != Db.INVALID_ROWID);
         
@@ -1239,7 +1269,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         stmt.exec(cancellable);
     }
     
-    private void do_remove_association_with_folder(Db.Connection cx, LocationIdentifier location,
+    private void do_unlink_from_folder(Db.Connection cx, LocationIdentifier location,
         Cancellable? cancellable) throws Error {
         Db.Statement stmt = cx.prepare(
             "DELETE FROM MessageLocationTable WHERE folder_id=? AND message_id=?");
@@ -1261,7 +1291,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         // if found, merge, and associate if necessary
         if (location != null) {
             if (!associated)
-                do_associate_with_folder(cx, location.message_id, location.uid, cancellable);
+                do_link_with_folder(cx, location.message_id, location.uid, cancellable);
             
             // If the email came from the Imap layer, we need to fill in the id.
             ImapDB.EmailIdentifier email_id = (ImapDB.EmailIdentifier) email.id;
@@ -1331,7 +1361,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         if (email_id.message_id == Db.INVALID_ROWID)
             email_id.promote_with_message_id(message_id);
         
-        do_associate_with_folder(cx, message_id, uid, cancellable);
+        do_link_with_folder(cx, message_id, uid, cancellable);
         
         // write out attachments, if any
         // TODO: Because this involves saving files, it potentially means holding up access to the
diff --git a/src/engine/imap-engine/imap-engine-batch-operations.vala 
b/src/engine/imap-engine/imap-engine-batch-operations.vala
index 904d2fe..6aae979 100644
--- a/src/engine/imap-engine/imap-engine-batch-operations.vala
+++ b/src/engine/imap-engine/imap-engine-batch-operations.vala
@@ -71,7 +71,7 @@ private class Geary.ImapEngine.RemoveLocalEmailOperation : Geary.Nonblocking.Bat
     }
     
     public override async Object? execute_async(Cancellable? cancellable) throws Error {
-        yield folder.detach_multiple_emails_async((Gee.Collection<ImapDB.EmailIdentifier>) email_ids, 
cancellable);
+        yield folder.unlink_multiple_emails_async((Gee.Collection<ImapDB.EmailIdentifier>) email_ids, 
cancellable);
         
         return null;
     }
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index c3956de..9089df6 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -109,11 +109,11 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
     }
     
     // used by normalize_folders() during the normalization process; should not be used elsewhere
-    private async void detach_all_emails_async(Cancellable? cancellable) throws Error {
+    private async void unlink_all_emails_async(Cancellable? cancellable) throws Error {
         Gee.List<Email>? all = yield local_folder.list_email_by_id_async(null, -1,
             Geary.Email.Field.NONE, ImapDB.Folder.ListFlags.NONE, cancellable);
         
-        yield local_folder.detach_all_emails_async(cancellable);
+        yield local_folder.unlink_all_emails_async(cancellable);
         
         if (all != null && all.size > 0) {
             Gee.List<EmailIdentifier> ids =
@@ -159,7 +159,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
                 local_properties.uid_validity.value.to_string(),
                 remote_properties.uid_validity.value.to_string());
             
-            yield detach_all_emails_async(cancellable);
+            yield unlink_all_emails_async(cancellable);
             
             return true;
         }
@@ -215,7 +215,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
                 to_string(), local_earliest_id.uid.to_string(), local_latest_id.uid.to_string(),
                 last_uid.to_string());
             
-            yield detach_all_emails_async(cancellable);
+            yield unlink_all_emails_async(cancellable);
             
             return true;
         }
@@ -380,7 +380,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
             removed_ids = yield local_folder.get_ids_async(removed_uids,
                 ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
             if (removed_ids != null && removed_ids.size > 0) {
-                yield local_folder.detach_multiple_emails_async(removed_ids, cancellable);
+                yield local_folder.unlink_multiple_emails_async(removed_ids, cancellable);
             }
         }
         
@@ -1065,7 +1065,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
                 owned_id.to_string());
             try {
                 // Reflect change in the local store and notify subscribers
-                yield local_folder.detach_single_email_async(owned_id, out marked, null);
+                yield local_folder.unlink_single_email_async(owned_id, out marked, null);
             } catch (Error err) {
                 debug("%s do_replay_removed_message: unable to remove message #%s: %s", to_string(),
                     remote_position.to_string(), err.message);
@@ -1271,6 +1271,10 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
         replay_queue.schedule(move);
         yield move.wait_for_ready_async(cancellable);
         
+        // No destination EmailIdentifiers, no way to revoke (undo) this operation
+        if (Collection.is_empty(move.destination_ids))
+            return null;
+        
         return new RevokableMove(_account, path, destination, move.destination_ids);
     }
     
@@ -1369,26 +1373,24 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
         return ret;
     }
     
-    // To be used only by RevokableMove.  Return false if UIDs are unknown to local store.
-    internal async bool revoke_move_async(Gee.Collection<Imap.UID> uids, FolderPath source,
+    // To be used only by RevokableMove.
+    internal async void revoke_move_async(Gee.Collection<ImapDB.EmailIdentifier> ids, FolderPath source,
         Cancellable? cancellable) throws Error {
         check_open("revoke_move_async");
         
-        // need to wait for fully open to ensure UIDs are normalized between local and remove
-        yield wait_for_open_async(cancellable);
-        
-        Gee.Set<ImapDB.EmailIdentifier>? ids = yield local_folder.get_ids_async(uids,
-            ImapDB.Folder.ListFlags.NONE, cancellable);
-        if (ids == null || ids.size == 0)
-            return false;
+        // revoking back to this folder is a no-no
+        if (source.equal_to(path)) {
+            warning("[%s] Attempted to revoke move to source same as destination (%s)", to_string(),
+                source.to_string());
+            
+            return;
+        }
         
         MoveEmail move = new MoveEmail(this, traverse<ImapDB.EmailIdentifier>(ids).to_array_list(),
             source, cancellable);
         replay_queue.schedule(move);
         
         yield move.wait_for_ready_async(cancellable);
-        
-        return true;
     }
 }
 
diff --git a/src/engine/imap-engine/imap-engine-revokable-move.vala 
b/src/engine/imap-engine/imap-engine-revokable-move.vala
index f670116..64e9a5e 100644
--- a/src/engine/imap-engine/imap-engine-revokable-move.vala
+++ b/src/engine/imap-engine/imap-engine-revokable-move.vala
@@ -57,11 +57,11 @@ private class Geary.ImapEngine.RevokableMove : Revokable {
         
         // open, revoke, close, ensuring the close and signal disconnect are performed in all cases
         try {
-            yield dest_folder.open_async(Geary.Folder.OpenFlags.NO_DELAY, cancellable);
+            yield dest_folder.open_async(Geary.Folder.OpenFlags.NONE, cancellable);
             
             // watch out for messages detected as gone when folder is opened
-            if (can_revoke && !yield dest_folder.revoke_move_async(destination_ids, original_source,
-                cancellable)) {
+            if (can_revoke) {
+                yield dest_folder.revoke_move_async(destination_ids, original_source, cancellable);
                 can_revoke = false;
             }
         } finally {
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 42ccc09..d12b5de 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
@@ -47,18 +47,9 @@ private class Geary.ImapEngine.CopyEmail : Geary.ImapEngine.SendReplayOperation
             ImapDB.Folder.ListFlags.NONE, cancellable);
         
         if (uids != null && uids.size > 0) {
-            Gee.Set<Imap.UID> acc_uids = new Gee.HashSet<Imap.UID>();
-            
             Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(uids);
-            foreach (Imap.MessageSet msg_set in msg_sets) {
-                Gee.Set<Imap.UID>? dest_uids = yield engine.remote_folder.copy_email_async(msg_set,
-                    destination, cancellable);
-                if (dest_uids != null)
-                    acc_uids.add_all(dest_uids);
-            }
-            
-            if (acc_uids.size > 0)
-                destination_uids = acc_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-move-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
index 30b19a4..aa89846 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
@@ -5,13 +5,14 @@
  */
 
 private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation {
-    public Gee.Set<ImapDb.EmailIdentifier>? destination_ids { get; private set; default = null; }
+    public Gee.Set<ImapDB.EmailIdentifier>? destination_ids { get; private set; default = null; }
     
     private MinimalFolder engine;
     private Gee.List<ImapDB.EmailIdentifier> to_move = new Gee.ArrayList<ImapDB.EmailIdentifier>();
     private Geary.FolderPath destination;
     private Cancellable? cancellable;
     private Gee.Set<ImapDB.EmailIdentifier>? moved_ids = null;
+    private Gee.Set<ImapDB.EmailIdentifier>? predicted_ids = null;
     private int original_count = 0;
 
     public MoveEmail(MinimalFolder engine, Gee.List<ImapDB.EmailIdentifier> to_move, 
@@ -52,11 +53,18 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
         engine.notify_email_count_changed(Numeric.int_floor(original_count - to_move.size, 0),
             Geary.Folder.CountChangeReason.REMOVED);
         
+        // Report to the local destination Folder that new messages are predicted to arrive
         MinimalFolder? dest_folder = (yield engine.account.fetch_folder_async(destination, cancellable))
             as MinimalFolder;
         if (dest_folder != null) {
-            debug("NOTIFY PREDICTED: %d", moved_ids.size);
-            dest_folder.notify_predict_email_inserted(moved_ids);
+            // convert moved identifiers to prediction identifiers, which have no UID (that comes
+            // after the network portion of the operation finishes) ... this allows for open folders
+            // to report new mail without waiting for the operation to finish
+            predicted_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
+            foreach (ImapDB.EmailIdentifier moved_id in moved_ids)
+                predicted_ids.add(new ImapDB.EmailIdentifier(moved_id.message_id, null));
+            
+            dest_folder.notify_predict_email_inserted(predicted_ids);
         }
         
         return ReplayOperation.Status.CONTINUE;
@@ -76,23 +84,54 @@ 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());
         
-        // TODO: Need to generate fully-valid ImapDB.EmailIdentifiers for the destination folder
-        // (with message_ids and UIDs properly associated)
+        // Use this accumulator through the iteration of message set(s) and then store it all at
+        // once in destination_ids
         Gee.Set<ImapDB.EmailIdentifier> acc_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
         
+        // Create a map of UIDs to moved_ids to make lookup easy when generating destination ids
+        Gee.Map<Imap.UID, ImapDB.EmailIdentifier> source_uid_to_source_id =
+            traverse<ImapDB.EmailIdentifier>(moved_ids)
+                .to_hash_map<Imap.UID>(id => id.uid);
+        
         Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(
             ImapDB.EmailIdentifier.to_uids(moved_ids));
         foreach (Imap.MessageSet msg_set in msg_sets) {
-            Gee.Set<Imap.UID>? dest_uids = yield engine.remote_folder.copy_email_async(msg_set,
+            Gee.Map<Imap.UID, Imap.UID>? copyuids = yield engine.remote_folder.copy_email_async(msg_set,
                 destination, null);
-            if (dest_uids != null)
-                acc_uids.add_all(dest_uids);
+            
+            // convert returned COPYUID response into EmailIdentifiers for the destination folder
+            if (copyuids != null && copyuids.size > 0) {
+                Gee.MapIterator<Imap.UID, Imap.UID> iter = copyuids.map_iterator();
+                while (iter.next()) {
+                    Imap.UID source_uid = iter.get_key();
+                    if (source_uid_to_source_id.has_key(source_uid)) {
+                        ImapDB.EmailIdentifier source_id = source_uid_to_source_id[source_uid];
+                        
+                        acc_ids.add(new ImapDB.EmailIdentifier(source_id.message_id, iter.get_value()));
+                    }
+                }
+            }
             
             yield engine.remote_folder.remove_email_async(msg_set, null);
         }
         
-        if (acc_uids.size > 0)
-            destination_uids = acc_uids;
+        if (acc_ids.size > 0) {
+            // link the destination id's to the local folder, which saves a little trouble when
+            // normalizing with the remote as well as makes revoking them work properly
+            MinimalFolder? dest_folder = (yield engine.account.fetch_folder_async(destination, cancellable))
+                as MinimalFolder;
+            if (dest_folder != null) {
+                try {
+                    // Note that this doesn't require dest_folder be open because we're going straight
+                    // to the database...dest_folder will announce to its subscribers changes when
+                    // normalization/notification occurs via IMAP
+                    yield dest_folder.local_folder.link_multiple_emails_async(acc_ids, cancellable);
+                    destination_ids = acc_ids;
+                } catch (Error err) {
+                    debug("Unable to link moved emails to new folder: %s", err.message);
+                }
+            }
+        }
         
         return ReplayOperation.Status.COMPLETED;
     }
@@ -103,10 +142,12 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
         engine.notify_email_inserted(moved_ids);
         engine.notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.INSERTED);
         
-        MinimalFolder? dest_folder = (yield engine.account.fetch_folder_async(destination, cancellable))
-            as MinimalFolder;
-        if (dest_folder != null)
-            dest_folder.notify_unpredict_email_inserted(moved_ids);
+        if (!Collection.is_empty(predicted_ids)) {
+            MinimalFolder? dest_folder = (yield engine.account.fetch_folder_async(destination, cancellable))
+                as MinimalFolder;
+            if (dest_folder != null)
+                dest_folder.notify_unpredict_email_inserted(predicted_ids);
+        }
     }
 
     public override string describe_state() {
diff --git a/src/engine/imap/api/imap-folder.vala b/src/engine/imap/api/imap-folder.vala
index d5bde97..b07f447 100644
--- a/src/engine/imap/api/imap-folder.vala
+++ b/src/engine/imap/api/imap-folder.vala
@@ -675,7 +675,9 @@ private class Geary.Imap.Folder : BaseObject {
         yield exec_commands_async(cmds, null, null, cancellable);
     }
     
-    public async Gee.List<UID>? copy_email_async(MessageSet msg_set, Geary.FolderPath destination,
+    // Returns a mapping of the source UID to the destination UID.  If the MessageSet is not for
+    // UIDs, then null is returned.  If the server doesn't support COPYUID, null is returned.
+    public async Gee.Map<UID, UID>? copy_email_async(MessageSet msg_set, Geary.FolderPath destination,
         Cancellable? cancellable) throws Error {
         check_open();
         
@@ -689,16 +691,33 @@ private class Geary.Imap.Folder : BaseObject {
             return null;
         
         StatusResponse response = responses.get(cmd);
-        if (response.response_code != null) {
-            Gee.List<UID>? destination_uids = null;
+        if (response.response_code != null && msg_set.is_uid) {
+            Gee.List<UID>? src_uids = null;
+            Gee.List<UID>? dst_uids = null;
             try {
-                response.response_code.get_copyuid(null, null, out destination_uids);
+                response.response_code.get_copyuid(null, out src_uids, out dst_uids);
             } catch (ImapError ierr) {
                 debug("Unable to retrieve COPYUID UIDs: %s", ierr.message);
             }
             
-            if (destination_uids != null && destination_uids.size > 0)
-                return Geary.traverse<UID>(destination_uids).to_hash_set();
+            if (!Collection.is_empty(src_uids) && !Collection.is_empty(dst_uids)) {
+                Gee.Map<UID, UID> copyuids = new Gee.HashMap<UID, UID>();
+                int ctr = 0;
+                for (;;) {
+                    UID? src_uid = (ctr < src_uids.size) ? src_uids[ctr] : null;
+                    UID? dst_uid = (ctr < dst_uids.size) ? dst_uids[ctr] : null;
+                    
+                    if (src_uid != null && dst_uid != null)
+                        copyuids.set(src_uid, dst_uid);
+                    else
+                        break;
+                    
+                    ctr++;
+                }
+                
+                if (copyuids.size > 0)
+                    return copyuids;
+            }
         }
         
         return null;
diff --git a/src/engine/util/util-collection.vala b/src/engine/util/util-collection.vala
index 294de11..e374021 100644
--- a/src/engine/util/util-collection.vala
+++ b/src/engine/util/util-collection.vala
@@ -8,6 +8,10 @@ namespace Geary.Collection {
 
 public delegate uint8 ByteTransformer(uint8 b);
 
+public bool is_empty(Gee.Collection? c) {
+    return c == null || c.size == 0;
+}
+
 // A substitute for ArrayList<G>.wrap() for compatibility with older versions of Gee.
 public Gee.ArrayList<G> array_list_wrap<G>(G[] a, owned Gee.EqualDataFunc<G>? equal_func = null) {
     Gee.ArrayList<G> list = new Gee.ArrayList<G>(equal_func);


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