diff --git a/src/engine/api/geary-conversations.vala b/src/engine/api/geary-conversations.vala index 1497b750..6af1dc6e 100644 --- a/src/engine/api/geary-conversations.vala +++ b/src/engine/api/geary-conversations.vala @@ -276,8 +276,8 @@ public class Geary.Conversations : Object { ~Conversations() { if (monitor_new) { - folder.messages_appended.disconnect(on_folder_messages_appended); - folder.message_removed.disconnect(on_folder_message_removed); + folder.email_appended.disconnect(on_folder_email_appended); + folder.email_removed.disconnect(on_folder_email_removed); } // Manually detach all the weak refs in the Conversation objects @@ -335,8 +335,8 @@ public class Geary.Conversations : Object { monitor_new = true; cancellable_monitor = cancellable; - folder.messages_appended.connect(on_folder_messages_appended); - folder.message_removed.connect(on_folder_message_removed); + folder.email_appended.connect(on_folder_email_appended); + folder.email_removed.connect(on_folder_email_removed); return true; } @@ -709,7 +709,7 @@ public class Geary.Conversations : Object { } } - private void on_folder_messages_appended() { + private void on_folder_email_appended() { // Find highest identifier by ordering // TODO: optimize. Geary.EmailIdentifier? highest = null; @@ -733,8 +733,9 @@ public class Geary.Conversations : Object { lazy_load_by_id(highest, int.MAX, Folder.ListFlags.EXCLUDING_ID, cancellable_monitor); } - private void on_folder_message_removed(Geary.EmailIdentifier removed_id) { - remove_email(removed_id); + private void on_folder_email_removed(Gee.Collection removed_ids) { + foreach (Geary.EmailIdentifier id in removed_ids) + remove_email(id); } } diff --git a/src/engine/api/geary-folder.vala b/src/engine/api/geary-folder.vala index 1ab2aaae..a579eb6c 100644 --- a/src/engine/api/geary-folder.vala +++ b/src/engine/api/geary-folder.vala @@ -65,14 +65,27 @@ public interface Geary.Folder : Object { public signal void closed(CloseReason reason); /** - * "messages-appended" is fired when new messages have been appended to the list of messages in + * "email-appended" is fired when new messages have been appended to the list of messages in * the folder (and therefore old message position numbers remain valid, but the total count of * the messages in the folder has changed). */ - public signal void messages_appended(int total); + public signal void email_appended(Gee.Collection ids); /** - * "message-removed" is fired when a message has been removed (deleted or moved) from the + * "email-locally-appended" is fired when previously unknown messages have been appended to the + * list of messages in the folder. This is similar to "email-appended", but that signal + * lists all messages appended to the folder. "email-locally-appended" only reports emails that + * have not been seen prior. Hence, an email that is removed from the folder and returned + * later will not be listed here (unless it was removed from the local store in the meantime). + * + * Note that these messages were appended as well, hence their positional addressing may have + * changed since last seen in this folder. However, it's important to realize that this list + * does *not* represent all newly appended messages. + */ + public signal void email_locally_appended(Gee.Collection ids); + + /** + * "email-removed" is fired when a message has been removed (deleted or moved) from the * folder (and therefore old message position numbers may no longer be valid, i.e. those after * the removed message). * @@ -80,14 +93,14 @@ public interface Geary.Folder : Object { * known locally (and therefore the caller could not have record of). If this happens, this * signal will *not* fire, although "email-count-changed" will. */ - public signal void message_removed(Geary.EmailIdentifier id); + public signal void email_removed(Gee.Collection ids); /** * "email-count-changed" is fired when the total count of email in a folder has changed in any way. * * Note that this signal will be fired alongside "messages-appended" or "message-removed". * That is, do not use both signals to process email count changes; one will suffice. - * This signal will fire after those (although see the note at "message-removed"). + * This signal will fire after those (although see the note at "messages-removed"). */ public signal void email_count_changed(int new_count, CountChangeReason reason); @@ -97,8 +110,7 @@ public interface Geary.Folder : Object { * This signal will be fired both when changes occur on the client side via the * mark_email_async() method as well as changes occur remotely. */ - public signal void email_flags_changed(Gee.Map flag_map); + public signal void email_flags_changed(Gee.Map map); /** * This helper method should be called by implementors of Folder rather than firing the signal @@ -119,14 +131,21 @@ public interface Geary.Folder : Object { * directly. This allows subclasses and superclasses the opportunity to inspect the email * and update state before and/or after the signal has been fired. */ - protected abstract void notify_messages_appended(int total); + protected abstract void notify_email_appended(Gee.Collection ids); /** * This helper method should be called by implementors of Folder rather than firing the signal * directly. This allows subclasses and superclasses the opportunity to inspect the email * and update state before and/or after the signal has been fired. */ - protected abstract void notify_message_removed(Geary.EmailIdentifier id); + protected abstract void notify_email_locally_appended(Gee.Collection ids); + + /** + * This helper method should be called by implementors of Folder rather than firing the signal + * directly. This allows subclasses and superclasses the opportunity to inspect the email + * and update state before and/or after the signal has been fired. + */ + protected abstract void notify_email_removed(Gee.Collection ids); /** * This helper method should be called by implementors of Folder rather than firing the signal @@ -271,35 +290,6 @@ public interface Geary.Folder : Object { public abstract void lazy_list_email(int low, int count, Geary.Email.Field required_fields, ListFlags flags, EmailCallback cb, Cancellable? cancellable = null); - /** - * Like list_email_async(), but the caller passes a sparse list of email by it's ordered - * position in the folder. If any of the positions in the sparse list are out of range, - * only the emails within range are reported. The list is not guaranteed to be in any - * particular order. - * - * See the notes in list_email_async() regarding issues about local versus remote stores. - * - * The Folder must be opened prior to attempting this operation. - * - * All positions are one-based. - */ - public abstract async Gee.List? list_email_sparse_async(int[] by_position, - Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null) - throws Error; - - /** - * Similar in contract to list_email_sparse_async(), but like lazy_list_email(), the - * messages are passed back to the caller in chunks as they're retrieved. When null is passed - * as the first parameter, all the messages have been fetched. If an Error occurs during - * processing, it's passed as the second parameter. There's no guarantee of the returned - * messages' order. - * - * The Folder must be opened prior to attempting this operation. - */ - public abstract void lazy_list_email_sparse(int[] by_position, - Geary.Email.Field required_fields, ListFlags flags, EmailCallback cb, - Cancellable? cancellable = null); - /** * Similar in contract to list_email_async(), but uses Geary.EmailIdentifier rather than * positional addressing. This allows for a batch of messages to be listed from a starting diff --git a/src/engine/impl/geary-abstract-folder.vala b/src/engine/impl/geary-abstract-folder.vala index b26c5b22..e34e1dec 100644 --- a/src/engine/impl/geary-abstract-folder.vala +++ b/src/engine/impl/geary-abstract-folder.vala @@ -5,20 +5,29 @@ */ public abstract class Geary.AbstractFolder : Object, Geary.Folder { - protected virtual void notify_opened(Geary.Folder.OpenState state, int count) { + /* + * notify_* methods for AbstractFolder are marked internal because the SendReplayOperations + * need access to them to report changes as they occur. + */ + + internal virtual void notify_opened(Geary.Folder.OpenState state, int count) { opened(state, count); } - protected virtual void notify_closed(Geary.Folder.CloseReason reason) { + internal virtual void notify_closed(Geary.Folder.CloseReason reason) { closed(reason); } - internal virtual void notify_messages_appended(int total) { - messages_appended(total); + internal virtual void notify_email_appended(Gee.Collection ids) { + email_appended(ids); } - internal virtual void notify_message_removed(Geary.EmailIdentifier id) { - message_removed(id); + internal virtual void notify_email_locally_appended(Gee.Collection ids) { + email_locally_appended(ids); + } + + internal virtual void notify_email_removed(Gee.Collection ids) { + email_removed(ids); } internal virtual void notify_email_count_changed(int new_count, Folder.CountChangeReason reason) { @@ -50,14 +59,6 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder { public abstract void lazy_list_email(int low, int count, Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null); - public abstract async Gee.List? list_email_sparse_async(int[] by_position, - Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null) - throws Error; - - public abstract void lazy_list_email_sparse(int[] by_position, - Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, - Cancellable? cancellable = null); - public abstract async Gee.List? list_email_by_id_async(Geary.EmailIdentifier initial_id, int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null) throws Error; diff --git a/src/engine/impl/geary-engine-folder.vala b/src/engine/impl/geary-engine-folder.vala index b8926a3d..99fd81ac 100644 --- a/src/engine/impl/geary-engine-folder.vala +++ b/src/engine/impl/geary-engine-folder.vala @@ -19,9 +19,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder { private SendReplayQueue? send_replay_queue = null; private NonblockingMutex normalize_email_positions_mutex = new NonblockingMutex(); - public virtual signal void local_added(Gee.Collection added) { - } - public EngineFolder(Imap.Account remote, Sqlite.Account local, Sqlite.Folder local_folder) { this.remote = remote; this.local = local; @@ -33,10 +30,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder { warning("Folder %s destroyed without closing", to_string()); } - protected virtual void notify_local_added(Gee.Collection added) { - local_added(added); - } - public override Geary.FolderPath get_path() { return local_folder.get_path(); } @@ -245,19 +238,25 @@ private class Geary.EngineFolder : Geary.AbstractFolder { Gee.HashSet created = new Gee.HashSet( Hashable.hash_func, Equalable.equal_func); + Gee.HashSet appended = new Gee.HashSet( + Hashable.hash_func, Equalable.equal_func); foreach (Geary.Email email in list) { debug("Creating Email ID %s", email.id.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) if (yield local_folder.create_email_async(email, null)) created.add(email.id); + + appended.add(email.id); } // save new remote count remote_count = new_remote_count; - notify_messages_appended(new_remote_count); - + notify_email_appended(appended); if (created.size > 0) - notify_local_added(created); + notify_email_locally_appended(created); } catch (Error err) { debug("Unable to normalize local store of newly appended messages to %s: %s", to_string(), err.message); @@ -295,7 +294,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder { yield local_folder.remove_marked_email_async(owned_id, out marked, null); if (!marked) - notify_message_removed(owned_id); + notify_email_removed(new Geary.Singleton(owned_id)); } catch (Error err2) { debug("Unable to remove message #%d from %s: %s", remote_position, to_string(), err2.message); @@ -375,48 +374,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder { yield op.wait_for_ready(); } - public override async Gee.List? list_email_sparse_async(int[] by_position, - Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null) - throws Error { - if (by_position.length == 0) - return null; - - Gee.List accumulator = new Gee.ArrayList(); - yield do_list_email_sparse_async(by_position, required_fields, accumulator, null, - cancellable, flags.is_all_set(Folder.ListFlags.LOCAL_ONLY)); - - return accumulator; - } - - // TODO: Capture Error and report via EmailCallback. - public override void lazy_list_email_sparse(int[] by_position, Geary.Email.Field required_fields, - Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null) { - // schedule listing in the background, using the callback to drive availability of email - do_list_email_sparse_async.begin(by_position, required_fields, null, cb, cancellable, - flags.is_all_set(Folder.ListFlags.LOCAL_ONLY)); - } - - private async void do_list_email_sparse_async(int[] by_position, Geary.Email.Field required_fields, - Gee.List? accumulator, EmailCallback? cb, Cancellable? cancellable, bool local_only) - throws Error { - if (!opened) - throw new EngineError.OPEN_REQUIRED("%s is not open", to_string()); - - if (by_position.length == 0) { - // signal finished - if (cb != null) - cb(null, null); - - return; - } - - // Schedule list operation and wait for completion. - ListEmailSparse op = new ListEmailSparse(this, by_position, required_fields, accumulator, - cb, cancellable, local_only); - send_replay_queue.schedule(op); - yield op.wait_for_ready(); - } - public override async Gee.List? list_email_by_id_async(Geary.EmailIdentifier initial_id, int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { @@ -529,7 +486,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder { } if (created_ids.size > 0) - notify_local_added(created_ids); + notify_email_locally_appended(created_ids); if (cb != null) cb(remote_list, null); @@ -579,13 +536,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder { Geary.Email email = yield remote_folder.fetch_email_async(id, fields, cancellable); // save to local store - if (yield local_folder.create_email_async(email, cancellable)) { - // TODO: A Singleton collection would be useful here. - Gee.ArrayList ids = new Gee.ArrayList(); - ids.add(email.id); - - notify_local_added(ids); - } + if (yield local_folder.create_email_async(email, cancellable)) + notify_email_locally_appended(new Geary.Singleton(email.id)); return email; } @@ -681,7 +633,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder { } if (created_ids.size > 0) - notify_local_added(created_ids); + notify_email_locally_appended(created_ids); } catch (Error e) { local_count = 0; // prevent compiler warning error = e; diff --git a/src/engine/impl/geary-generic-imap-folder.vala b/src/engine/impl/geary-generic-imap-folder.vala index 55bb7298..e5300a91 100644 --- a/src/engine/impl/geary-generic-imap-folder.vala +++ b/src/engine/impl/geary-generic-imap-folder.vala @@ -165,6 +165,7 @@ private class Geary.GenericImapFolder : Geary.EngineFolder { int remote_ctr = 0; int local_ctr = 0; + Gee.ArrayList appended_ids = new Gee.ArrayList(); Gee.ArrayList removed_ids = new Gee.ArrayList(); Gee.Map flags_changed = new Gee.HashMap(); @@ -195,6 +196,7 @@ private class Geary.GenericImapFolder : Geary.EngineFolder { } else if (remote_uid.value < local_uid.value) { // one we'd not seen before is present, add and move to next remote batch.add(new CreateLocalEmailOperation(local_folder, remote_email)); + appended_ids.add(remote_email.id); remote_ctr++; } else { @@ -212,10 +214,9 @@ private class Geary.GenericImapFolder : Geary.EngineFolder { // CreateEmailOperations were updates of emails existing previously or additions of emails // that were on the server earlier but not stored locally (i.e. this value represents emails // added to the top of the stack) - int appended = 0; for (; remote_ctr < remote_length; remote_ctr++) { batch.add(new CreateLocalEmailOperation(local_folder, old_remote[remote_ctr])); - appended++; + appended_ids.add(old_remote[remote_ctr].id); } // remove anything left over ... use local count rather than remote as we're still in a stage @@ -240,14 +241,13 @@ private class Geary.GenericImapFolder : Geary.EngineFolder { // signalled) if (removed_ids.size > 0) { debug("Notifying of %d removed emails since %s last seen", removed_ids.size, to_string()); - foreach (Geary.EmailIdentifier removed_id in removed_ids) - notify_message_removed(removed_id); + notify_email_removed(removed_ids); } // notify additions - if (appended > 0) { - debug("Notifying of %d appended emails since %s last seen", appended, to_string()); - notify_messages_appended(appended); + if (appended_ids.size > 0) { + debug("Notifying of %d appended emails since %s last seen", appended_ids.size, to_string()); + notify_email_appended(appended_ids); } // notify flag changes @@ -282,10 +282,10 @@ private class Geary.GenericImapFolder : Geary.EngineFolder { base.notify_closed(reason); } - protected override void notify_local_added(Gee.Collection added) { - schedule_prefetch(added); + protected override void notify_email_locally_appended(Gee.Collection ids) { + schedule_prefetch(ids); - base.notify_local_added(added); + base.notify_email_locally_appended(ids); } /** diff --git a/src/engine/impl/geary-send-replay-operations.vala b/src/engine/impl/geary-send-replay-operations.vala index 75ff6fa2..e6018634 100644 --- a/src/engine/impl/geary-send-replay-operations.vala +++ b/src/engine/impl/geary-send-replay-operations.vala @@ -11,7 +11,7 @@ private class Geary.MarkEmail : Geary.SendReplayOperation { private Geary.EmailFlags? flags_to_remove; private Gee.Map? original_flags = null; private Cancellable? cancellable; - + public MarkEmail(EngineFolder engine, Gee.List to_mark, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, Cancellable? cancellable = null) { @@ -70,7 +70,7 @@ private class Geary.RemoveEmail : Geary.SendReplayOperation { public override async bool replay_local() throws Error { foreach (Geary.EmailIdentifier id in to_remove) { yield engine.local_folder.mark_removed_async(id, true, cancellable); - engine.notify_message_removed(id); + engine.notify_email_removed(new Geary.Singleton(id)); } original_count = engine.remote_count; @@ -93,8 +93,8 @@ private class Geary.RemoveEmail : Geary.SendReplayOperation { foreach (Geary.EmailIdentifier id in to_remove) yield engine.local_folder.mark_removed_async(id, false, cancellable); - engine.notify_messages_appended(to_remove.size); - engine.notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.REMOVED); + engine.notify_email_appended(to_remove); + engine.notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.ADDED); } } diff --git a/src/engine/util/util-singleton.vala b/src/engine/util/util-singleton.vala new file mode 100755 index 00000000..ef133212 --- /dev/null +++ b/src/engine/util/util-singleton.vala @@ -0,0 +1,85 @@ +/* Copyright 2012 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Singleton is a simple way of creating a one-item read-only collection. + */ +private class Geary.Singleton : Gee.AbstractCollection { + private class IteratorImpl : Object, Gee.Iterator { + private G item; + private bool done = false; + + public IteratorImpl(G item) { + this.item = item; + } + + public bool first() { + done = false; + + return true; + } + + public new G? get() { + return item; + } + + public bool has_next() { + return !done; + } + + public bool next() { + if (done) + return false; + + done = true; + + return true; + } + + public void remove() { + message("Geary.Singleton is read-only"); + } + } + + public G item { get; private set; } + public override int size { get { return 1; } } + + private EqualFunc equal_func; + + public Singleton(G item, EqualFunc? equal_func = null) { + this.item = item; + + if (equal_func != null) + this.equal_func = equal_func; + else if (typeof(G).is_a(typeof(Geary.Equalable))) + this.equal_func = Geary.Equalable.equal_func; + else + this.equal_func = Gee.Functions.get_equal_func_for(typeof(G)); + } + + public override bool add(G element) { + return false; + } + + public override void clear() { + message("Geary.Singleton is read-only"); + } + + public override bool contains(G element) { + return equal_func(item, element); + } + + public override Gee.Iterator iterator() { + return new IteratorImpl(item); + } + + public override bool remove(G element) { + message("Geary.Singleton is read-only"); + + return false; + } +} + diff --git a/src/wscript b/src/wscript index 10f9e7ab..66223862 100644 --- a/src/wscript +++ b/src/wscript @@ -155,6 +155,7 @@ def build(bld): '../engine/util/util-numeric.vala', '../engine/util/util-reference-semantics.vala', '../engine/util/util-scheduler.vala', + '../engine/util/util-singleton.vala', '../engine/util/util-stream.vala', '../engine/util/util-string.vala', '../engine/util/util-trillian.vala'