diff --git a/THANKS b/THANKS index 8751bb44..e6641e03 100644 --- a/THANKS +++ b/THANKS @@ -16,6 +16,7 @@ Jens Georg Michael George Sven Hagemann Mathias Hasselmann +Brendan Long Timo Kluck Avi Levy Kai Mast diff --git a/src/engine/api/geary-base-object.vala b/src/engine/api/geary-base-object.vala index aef74740..92644083 100644 --- a/src/engine/api/geary-base-object.vala +++ b/src/engine/api/geary-base-object.vala @@ -10,8 +10,14 @@ public abstract class Geary.BaseObject : Object { protected BaseObject() { lock (refmap) { - if (refmap == null) - refmap = new Gee.HashMap(direct_hash, direct_equal); + if (refmap == null) { + // because strings are unowned and guaranteed to be + // unique by GType, use direct comparison functions, + // more efficient then string hash/equal + refmap = new Gee.HashMap( + Gee.Functions.get_hash_func_for(typeof(void*)), + Gee.Functions.get_equal_func_for(typeof(void*))); + } unowned string classname = get_classname(); refmap.set(classname, refmap.get(classname) + 1); @@ -42,7 +48,7 @@ public abstract class Geary.BaseObject : Object { Gee.ArrayList list = new Gee.ArrayList(); list.add_all(refmap.keys); - list.sort(strcmp); + list.sort(); foreach (unowned string classname in list) outs.printf("%9d %s\n", refmap.get(classname), classname); } diff --git a/src/engine/imap-engine/imap-engine-account-synchronizer.vala b/src/engine/imap-engine/imap-engine-account-synchronizer.vala index 3e4ce488..916404ff 100644 --- a/src/engine/imap-engine/imap-engine-account-synchronizer.vala +++ b/src/engine/imap-engine/imap-engine-account-synchronizer.vala @@ -369,6 +369,17 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject { } else if (epoch_id != null) { oldest_local_id = epoch_id; } + + // look for complete synchronization of UIDs (i.e. complete vector normalization) + // no need to keep searching once this happens + int local_count = yield folder.local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE, + bg_cancellable); + if (local_count >= folder.properties.email_total) { + debug("Total vector normalization for %s: %d/%d emails", folder.to_string(), local_count, + folder.properties.email_total); + + break; + } } while (current_epoch.compare(epoch) > 0); } else { debug("No expansion necessary for %s, oldest local (%s) is before epoch (%s)", diff --git a/src/engine/imap-engine/imap-engine-generic-folder.vala b/src/engine/imap-engine/imap-engine-generic-folder.vala index a2c127e5..e980b8eb 100644 --- a/src/engine/imap-engine/imap-engine-generic-folder.vala +++ b/src/engine/imap-engine/imap-engine-generic-folder.vala @@ -1028,7 +1028,11 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde check_open("expunge_email_async"); check_ids("expunge_email_async", email_ids); - replay_queue.schedule(new ExpungeEmail(this, email_ids, cancellable)); + ExpungeEmail expunge = new ExpungeEmail(this, (Gee.List) email_ids, + cancellable); + replay_queue.schedule(expunge); + + yield expunge.wait_for_ready_async(cancellable); } private void check_open(string method) throws EngineError { @@ -1058,22 +1062,29 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde Cancellable? cancellable = null) throws Error { check_open("mark_email_async"); - replay_queue.schedule(new MarkEmail(this, to_mark, flags_to_add, flags_to_remove, - cancellable)); + MarkEmail mark = new MarkEmail(this, to_mark, flags_to_add, flags_to_remove, cancellable); + replay_queue.schedule(mark); + yield mark.wait_for_ready_async(cancellable); } public virtual async void copy_email_async(Gee.List to_copy, Geary.FolderPath destination, Cancellable? cancellable = null) throws Error { check_open("copy_email_async"); + check_ids("copy_email_async", to_copy); - replay_queue.schedule(new CopyEmail(this, to_copy, destination)); + CopyEmail copy = new CopyEmail(this, (Gee.List) to_copy, destination); + replay_queue.schedule(copy); + yield copy.wait_for_ready_async(cancellable); } public virtual async void move_email_async(Gee.List to_move, Geary.FolderPath destination, Cancellable? cancellable = null) throws Error { check_open("move_email_async"); + check_ids("move_email_async", to_move); - replay_queue.schedule(new MoveEmail(this, to_move, destination)); + MoveEmail move = new MoveEmail(this, (Gee.List) to_move, destination); + replay_queue.schedule(move); + yield move.wait_for_ready_async(cancellable); } private void on_email_flags_changed(Gee.Map changed) { @@ -1113,7 +1124,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde // class. Magically generating EmailIdentifiers is a bad idea. (That's why this uses // list_email_by_id_async() and not fetch_email_async(), and why OLDEST_TO_NEWEST is set.) Gee.List? list = yield list_email_by_id_async(found_id, 1, Geary.Email.Field.NONE, - ListFlags.OLDEST_TO_NEWEST, cancellable); + ListFlags.OLDEST_TO_NEWEST | ListFlags.INCLUDING_ID, cancellable); if (list == null || list.size == 0) return null; diff --git a/src/engine/rfc822/rfc822-message-data.vala b/src/engine/rfc822/rfc822-message-data.vala index a6a04be2..0186da5a 100644 --- a/src/engine/rfc822/rfc822-message-data.vala +++ b/src/engine/rfc822/rfc822-message-data.vala @@ -45,26 +45,64 @@ public class Geary.RFC822.MessageIDList : Geary.MessageData.AbstractMessageData, public MessageIDList.from_rfc822_string(string value) { this (); - string[] ids = value.split_set(" \n\r\t"); + // Have seen some mailers use commas between Message-IDs, meaning that the standard + // whitespace tokenizer is not sufficient; however, can't add the comma (or every other + // delimiter that mailers dream up) because it may be used within a Message-ID. The + // only guarantee made of a Message-ID is that it's surrounded by angle brackets, so + // mark anything not an angle bracket as a space and strip + // + // NOTE: Seen at least one spamfilter mailer that imaginatively uses parens instead of + // angle brackets for its Message-IDs; accounting for that as well here. + StringBuilder canonicalized = new StringBuilder(); + int index = 0; + unichar ch; + bool in_message_id = false; + while (value.get_next_char(ref index, out ch)) { + switch (ch) { + case '<': + in_message_id = true; + break; + + case '(': + if (!in_message_id) { + ch = '<'; + in_message_id = true; + } + break; + + case '>': + in_message_id = false; + break; + + case ')': + if (in_message_id) { + ch = '>'; + in_message_id = false; + } + break; + + // anything not inside the message-id brackets is turned into spaces + default: + if (!in_message_id) + ch = ' '; + break; + } + + canonicalized.append_unichar(ch); + } + + if (value != canonicalized.str) + debug("Message-ID list corrected: \"%s\" -> \"%s\"", value, canonicalized.str); + + // there's some additional paranoia here with getting the Message-ID sliced out of the + // strings, but it's worth it to get a valid Message-ID or none at all vs. a bogus one + string[] ids = canonicalized.str.split(" "); foreach (string id in ids) { if (String.is_empty(id)) continue; - // Have seen some mailers use commas between Message-IDs, meaning that the standard - // whitespace tokenizer is not sufficient; however, can't add the comma (or every other - // delimiter that mailers dream up) because it may be used within a Message-ID. The - // only guarantee made of a Message-ID is that it's surrounded by angle brackets, so - // mark anything not an angle bracket as a space and strip - // - // NOTE: Seen at least one spamfilter mailer that imaginatively uses parens instead of - // angle brackets for its Message-IDs; accounting for that as well here. int start = id.index_of_char('<'); - if (start < 0) - start = id.index_of_char('('); - int end = id.last_index_of_char('>'); - if (end < 0) - end = id.last_index_of_char(')'); // if either end not found or the end comes before the beginning, invalid Message-ID if (start < 0 || end < 0 || (start >= end)) { diff --git a/ui/composer.glade b/ui/composer.glade index 31d9bd25..7f32f323 100644 --- a/ui/composer.glade +++ b/ui/composer.glade @@ -3,23 +3,38 @@ - + + Undo + undo + - + + Redo + redo + - + + Cut + edit-cut + - + + Copy + edit-copy + - + + Paste + edit-paste +