From f08b5606c0105b5b733051d0131e6b8ba37e0474 Mon Sep 17 00:00:00 2001 From: Jim Nelson Date: Fri, 18 Nov 2011 12:28:45 -0800 Subject: [PATCH] Adds support for unsolicited server data: Closes #4406 Unsolicited server data is how the server can notify of changes (adds, removes) while the client is pulling data for an unrelated command. This patch catches that unsolicited information and propagates it via signals. Also some small fixes dealing with local/remote positional addressing in EngineFolder and a bug that was introduced by the add/remove notifications in MainWindow. --- src/client/ui/main-window.vala | 15 +-- src/client/ui/message-list-store.vala | 24 ++-- src/engine/imap/api/imap-folder.vala | 2 +- .../imap/command/imap-command-response.vala | 8 ++ .../imap/decoders/imap-noop-results.vala | 50 +++----- .../imap/message/imap-message-data.vala | 19 +-- src/engine/imap/message/imap-parameter.vala | 22 ++++ .../imap-unsolicited-server-data.vala | 116 ++++++++++++++++++ .../imap-client-session-manager.vala | 2 +- .../imap/transport/imap-client-session.vala | 51 +++++++- src/engine/imap/transport/imap-mailbox.vala | 16 ++- src/engine/impl/geary-engine-folder.vala | 67 ++++++++-- src/wscript | 1 + 13 files changed, 305 insertions(+), 88 deletions(-) create mode 100755 src/engine/imap/response/imap-unsolicited-server-data.vala diff --git a/src/client/ui/main-window.vala b/src/client/ui/main-window.vala index 712287a8..e4791c4e 100644 --- a/src/client/ui/main-window.vala +++ b/src/client/ui/main-window.vala @@ -12,21 +12,21 @@ public class MainWindow : Gtk.Window { public MainWindow owner; public Geary.Folder folder; public Geary.EmailIdentifier email_id; - public int index; + public Geary.Conversation conversation; public FetchPreviewOperation(MainWindow owner, Geary.Folder folder, - Geary.EmailIdentifier email_id, int index) { + Geary.EmailIdentifier email_id, Geary.Conversation conversation) { this.owner = owner; this.folder = folder; this.email_id = email_id; - this.index = index; + this.conversation = conversation; } public override async Object? execute_async(Cancellable? cancellable) throws Error { Geary.Email? preview = yield folder.fetch_email_async(email_id, MessageListStore.WITH_PREVIEW_FIELDS, cancellable); - - owner.message_list_store.set_preview_at_index(index, preview); + if (preview != null) + owner.message_list_store.set_preview_for_conversation(conversation, preview); return null; } @@ -339,9 +339,10 @@ public class MainWindow : Gtk.Window { int count = message_list_store.get_count(); for (int ctr = 0; ctr < count; ctr++) { - Geary.Email? email = message_list_store.get_newest_message_at_index(ctr); + Geary.Conversation? conversation; + Geary.Email? email = message_list_store.get_newest_message_at_index(ctr, out conversation); if (email != null) - batch.add(new FetchPreviewOperation(this, current_folder, email.id, ctr)); + batch.add(new FetchPreviewOperation(this, current_folder, email.id, conversation)); } debug("Fetching %d previews", count); diff --git a/src/client/ui/message-list-store.vala b/src/client/ui/message-list-store.vala index 8ed0d939..b8978e62 100644 --- a/src/client/ui/message-list-store.vala +++ b/src/client/ui/message-list-store.vala @@ -115,33 +115,25 @@ public class MessageListStore : Gtk.TreeStore { return get_conversation_at(new Gtk.TreePath.from_indices(index, -1)); } - public Geary.Email? get_newest_message_at_index(int index) { - Geary.Conversation? c = get_conversation_at_index(index); - if (c == null) + public Geary.Email? get_newest_message_at_index(int index, out Geary.Conversation? conversation) { + conversation = get_conversation_at_index(index); + if (conversation == null) return null; - Gee.SortedSet? pool = c.get_pool_sorted(compare_email); + Gee.SortedSet? pool = conversation.get_pool_sorted(compare_email); return pool != null ? pool.first() : null; } - public void set_preview_at_index(int index, Geary.Email email) { + public void set_preview_for_conversation(Geary.Conversation conversation, Geary.Email email) { Gtk.TreeIter iter; - if (!get_iter(out iter, new Gtk.TreePath.from_indices(index, -1))) { - warning("Unable to get tree path from position: %d".printf(index)); + if (!find_conversation(conversation, out iter)) { + debug("Unable to find conversation for preview %s", email.id.to_string()); return; } - int num_emails = 1; - Geary.Conversation? c = get_conversation_at_index(index); - if (c != null) { - Gee.Set? list = c.get_pool(); - if (list != null) - num_emails = list.size; - } - - set(iter, Column.MESSAGE_DATA, new FormattedMessageData.from_email(email, num_emails)); + set(iter, Column.MESSAGE_DATA, new FormattedMessageData.from_email(email, conversation.get_count())); } public int get_count() { diff --git a/src/engine/imap/api/imap-folder.vala b/src/engine/imap/api/imap-folder.vala index 6068910f..890f7b47 100644 --- a/src/engine/imap/api/imap-folder.vala +++ b/src/engine/imap/api/imap-folder.vala @@ -88,7 +88,7 @@ private class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder { notify_messages_appended(new_exists); } - private void on_flags_altered(FetchResults flags) { + private void on_flags_altered(MailboxAttributes flags) { assert(mailbox != null); // TODO: Notify of changes } diff --git a/src/engine/imap/command/imap-command-response.vala b/src/engine/imap/command/imap-command-response.vala index 5aaf32d4..f4f7d2f7 100644 --- a/src/engine/imap/command/imap-command-response.vala +++ b/src/engine/imap/command/imap-command-response.vala @@ -18,6 +18,14 @@ public class Geary.Imap.CommandResponse : Object { server_data.add(data); } + public bool remove_server_data(ServerData data) { + return server_data.remove(data); + } + + public bool remove_many_server_data(Gee.Collection data) { + return server_data.remove_all(data); + } + public void seal(StatusResponse status_response) { assert(!is_sealed()); diff --git a/src/engine/imap/decoders/imap-noop-results.vala b/src/engine/imap/decoders/imap-noop-results.vala index 02c509db..01033ad5 100644 --- a/src/engine/imap/decoders/imap-noop-results.vala +++ b/src/engine/imap/decoders/imap-noop-results.vala @@ -10,14 +10,14 @@ public class Geary.Imap.NoopResults : Geary.Imap.CommandResults { * -1 if "exists" result not returned by server. */ public int exists { get; private set; } - public Gee.List? flags { get; private set; } + public MailboxAttributes? flags { get; private set; } /** * -1 if "recent" result not returned by server. */ public int recent { get; private set; } public NoopResults(StatusResponse status_response, Gee.List? expunged, int exists, - Gee.List? flags, int recent) { + MailboxAttributes? flags, int recent) { base (status_response); this.expunged = expunged; @@ -30,43 +30,33 @@ public class Geary.Imap.NoopResults : Geary.Imap.CommandResults { assert(response.is_sealed()); Gee.List expunged = new Gee.ArrayList(); - Gee.List flags = new Gee.ArrayList(); + MailboxAttributes? flags = null; int exists = -1; int recent = -1; foreach (ServerData data in response.server_data) { - try { - int ordinal = data.get_as_string(1).as_int(-1, int.MAX); - ServerDataType type = ServerDataType.from_parameter(data.get_as_string(2)); + UnsolicitedServerData? unsolicited = UnsolicitedServerData.from_server_data(data); + if (unsolicited == null) { + message("NOOP server data \"%s\" unrecognized", data.to_string()); - switch (type) { - case ServerDataType.EXPUNGE: - expunged.add(new MessageNumber(ordinal)); - break; - - case ServerDataType.EXISTS: - exists = ordinal; - break; - - case ServerDataType.RECENT: - recent = ordinal; - break; - - case ServerDataType.FETCH: - flags.add(FetchResults.decode_data(response.status_response, data)); - break; - - default: - message("NOOP server data type \"%s\" unrecognized", type.to_string()); - break; - } - } catch (ImapError ierr) { - message("NOOP decode error for \"%s\": %s", data.to_string(), ierr.message); + continue; } + + if (unsolicited.exists >= 0) + exists = unsolicited.exists; + + if (unsolicited.recent >= 0) + recent = unsolicited.recent; + + if (unsolicited.flags != null) + flags = unsolicited.flags; + + if (unsolicited.expunge != null) + expunged.add(unsolicited.expunge); } return new NoopResults(response.status_response, (expunged.size > 0) ? expunged : null, - exists, (flags.size > 0) ? flags : null, recent); + exists, (flags != null && flags.size > 0) ? flags : null, recent); } public bool has_exists() { diff --git a/src/engine/imap/message/imap-message-data.vala b/src/engine/imap/message/imap-message-data.vala index 01195d41..7eea6bd2 100644 --- a/src/engine/imap/message/imap-message-data.vala +++ b/src/engine/imap/message/imap-message-data.vala @@ -106,15 +106,8 @@ public class Geary.Imap.MessageFlags : Geary.Imap.Flags { public static MessageFlags from_list(ListParameter listp) throws ImapError { Gee.Collection list = new Gee.ArrayList(); - foreach (Parameter param in listp.get_all()) { - StringParameter? stringp = param as StringParameter; - if (stringp == null) { - throw new ImapError.TYPE_ERROR("Flags list contained non-string parameter \"%s\"", - param.to_string()); - } - - list.add(new MessageFlag(stringp.value)); - } + for (int ctr = 0; ctr < listp.get_count(); ctr++) + list.add(new MessageFlag(listp.get_as_string(ctr).value)); return new MessageFlags(list); } @@ -135,6 +128,14 @@ public class Geary.Imap.MailboxAttributes : Geary.Imap.Flags { base (attrs); } + public static MailboxAttributes from_list(ListParameter listp) throws ImapError { + Gee.Collection list = new Gee.ArrayList(); + for (int ctr = 0; ctr < listp.get_count(); ctr++) + list.add(new MailboxAttribute(listp.get_as_string(ctr).value)); + + return new MailboxAttributes(list); + } + public static MailboxAttributes deserialize(string str) { string[] tokens = str.split(" "); diff --git a/src/engine/imap/message/imap-parameter.vala b/src/engine/imap/message/imap-parameter.vala index 57143de3..191787e9 100644 --- a/src/engine/imap/message/imap-parameter.vala +++ b/src/engine/imap/message/imap-parameter.vala @@ -194,6 +194,16 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter { return param; } + public Parameter? get_if(int index, Type type) { + assert(type.is_a(typeof(Parameter))); + + Parameter? param = get(index); + if (param == null || !param.get_type().is_a(type)) + return null; + + return param; + } + public StringParameter get_as_string(int index) throws ImapError { return (StringParameter) get_as(index, typeof(StringParameter)); } @@ -208,6 +218,10 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter { return param ?? new StringParameter(""); } + public StringParameter? get_if_string(int index) { + return (StringParameter?) get_if(index, typeof(StringParameter)); + } + public ListParameter get_as_list(int index) throws ImapError { return (ListParameter) get_as(index, typeof(ListParameter)); } @@ -222,6 +236,10 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter { return param ?? new ListParameter(this); } + public ListParameter? get_if_list(int index) { + return (ListParameter?) get_if(index, typeof(ListParameter)); + } + public LiteralParameter get_as_literal(int index) throws ImapError { return (LiteralParameter) get_as(index, typeof(LiteralParameter)); } @@ -230,6 +248,10 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter { return (LiteralParameter?) get_as_nullable(index, typeof(LiteralParameter)); } + public LiteralParameter? get_if_literal(int index) { + return (LiteralParameter?) get_if(index, typeof(LiteralParameter)); + } + public LiteralParameter get_as_empty_parameter(int index) throws ImapError { LiteralParameter? param = get_as_nullable_literal(index); diff --git a/src/engine/imap/response/imap-unsolicited-server-data.vala b/src/engine/imap/response/imap-unsolicited-server-data.vala new file mode 100755 index 00000000..27c57ffc --- /dev/null +++ b/src/engine/imap/response/imap-unsolicited-server-data.vala @@ -0,0 +1,116 @@ +/* Copyright 2011 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. + */ + +/** + * Some ServerData returned by the server may be unsolicited and not an expected part of the command. + * "Unsolicited" is contextual, since these fields may be returned as a natural part of a command + * (SELECT/EXAMINE or EXPUNGE) or expected (NOOP). In other situations, they must be dealt with + * out-of-band and the unsolicited ServerData not considered as part of the normal CommandResponse. + * + * Note that only one of the fields (exists, recent, expunge, or flags) will be valid for any + * ServerData; it's impossible that more than one will be valid. + */ +public class Geary.Imap.UnsolicitedServerData : Object { + /** + * -1 means not found in ServerData + */ + public int exists { get; private set; } + /** + * -1 means not found in ServerData + */ + public int recent { get; private set; } + /** + * null means not found in ServerData + */ + public MessageNumber? expunge { get; private set; } + /** + * null means not found in ServerData + */ + public MailboxAttributes? flags { get; private set; } + + private UnsolicitedServerData(int exists, int recent, MessageNumber? expunge, MailboxAttributes? flags) { + this.exists = exists; + this.recent = recent; + this.expunge = expunge; + this.flags = flags; + } + + /** + * Returns null if not recognized as unsolicited server data. + */ + public static UnsolicitedServerData? from_server_data(ServerData data) { + // Note that unsolicited server data is formatted the same save for FLAGS: + // + // * 47 EXISTS + // * 3 EXPUNGE + // * FLAGS (\answered \flagged \deleted \seen) + // * 15 RECENT + // + // Also note that these server data are *not* unsolicited if they're associated with their + // "natural" command (i.e. SELECT/EXAMINE, NOOP) although the NOOP decoder uses this object + // to do its decoding. + // + // Also note that the unsolicited data is EXPUNGE while the EXPUNGE command expects + // EXPUNGED (past tense) server data to be returned + + // first unsolicited param is always a string + StringParameter? first_string = data.get_if_string(1); + if (first_string == null) + return null; + + // second might be a string or a list + StringParameter? second_string = data.get_if_string(2); + ListParameter? second_list = data.get_if_list(2); + if (second_string == null && second_list == null) + return null; + + // determine command and value by types + StringParameter? cmdparam = null; + StringParameter? strparam = null; + ListParameter? listparam = null; + if (second_list != null) { + cmdparam = first_string; + listparam = second_list; + } else { + cmdparam = second_string; + strparam = first_string; + } + + try { + switch (cmdparam.value.down()) { + case "exists": + return (strparam != null) + ? new UnsolicitedServerData(strparam.as_int(), -1, null, null) + : null; + + case "recent": + return (strparam != null) + ? new UnsolicitedServerData(-1, strparam.as_int(), null, null) + : null; + + case "expunge": + return (strparam != null) + ? new UnsolicitedServerData(-1, -1, new MessageNumber(strparam.as_int()), null) + : null; + + case "flags": + return (listparam != null) + ? new UnsolicitedServerData(-1, -1, null, MailboxAttributes.from_list(listparam)) + : null; + + default: + // an unrecognized parameter + return null; + } + } catch (ImapError err) { + debug("Unable to decode unsolicited data \"%s\": %s", data.to_string(), err.message); + + return null; + } + } +} + + diff --git a/src/engine/imap/transport/imap-client-session-manager.vala b/src/engine/imap/transport/imap-client-session-manager.vala index f45a6157..1db55198 100644 --- a/src/engine/imap/transport/imap-client-session-manager.vala +++ b/src/engine/imap/transport/imap-client-session-manager.vala @@ -6,7 +6,7 @@ public class Geary.Imap.ClientSessionManager { private const int MIN_POOL_SIZE = 2; - private const int SELECTED_KEEPALIVE_SEC = 60; + private const int SELECTED_KEEPALIVE_SEC = 30; private Endpoint endpoint; private Credentials credentials; diff --git a/src/engine/imap/transport/imap-client-session.vala b/src/engine/imap/transport/imap-client-session.vala index cf9ac1f3..ba274ad1 100644 --- a/src/engine/imap/transport/imap-client-session.vala +++ b/src/engine/imap/transport/imap-client-session.vala @@ -208,7 +208,7 @@ public class Geary.Imap.ClientSession { public virtual signal void unsolicited_recent(int recent) { } - public virtual signal void unsolicited_flags(FetchResults flags) { + public virtual signal void unsolicited_flags(MailboxAttributes attrs) { } public ClientSession(Geary.Endpoint endpoint) { @@ -583,10 +583,8 @@ public class Geary.Imap.ClientSession { if (results.has_recent()) unsolicited_recent(results.recent); - if (results.flags != null) { - foreach (FetchResults flags in results.flags) - unsolicited_flags(flags); - } + if (results.flags != null) + unsolicited_flags(results.flags); } // @@ -614,6 +612,49 @@ public class Geary.Imap.ClientSession { if (params.err != null) throw params.err; + // look for unsolicited server data and signal all that are found ... since SELECT/EXAMINE + // aren't allowed here, don't need to check for them (because their fields aren't considered + // unsolicited) + // + // Note that EXPUNGE returns *EXPUNGED* results, not *EXPUNGE*, which is what the unsolicited + // version is. + Gee.ArrayList? to_remove = null; + foreach (ServerData data in params.cmd_response.server_data) { + UnsolicitedServerData? unsolicited = UnsolicitedServerData.from_server_data(data); + if (unsolicited == null) + continue; + + if (unsolicited.exists >= 0) { + debug("UNSOLICITED EXISTS %d", unsolicited.exists); + unsolicited_exists(unsolicited.exists); + } + + if (unsolicited.recent >= 0) { + debug("UNSOLICITED RECENT %d", unsolicited.recent); + unsolicited_recent(unsolicited.recent); + } + + if (unsolicited.expunge != null) { + debug("UNSOLICITED EXPUNGE %s", unsolicited.expunge.to_string()); + unsolicited_expunged(unsolicited.expunge); + } + + if (unsolicited.flags != null) { + debug("UNSOLICITED FLAGS %s", unsolicited.flags.to_string()); + unsolicited_flags(unsolicited.flags); + } + + if (to_remove == null) + to_remove = new Gee.ArrayList(); + + to_remove.add(data); + } + + if (to_remove != null) { + bool removed = params.cmd_response.remove_many_server_data(to_remove); + assert(removed); + } + return params.cmd_response; } diff --git a/src/engine/imap/transport/imap-mailbox.vala b/src/engine/imap/transport/imap-mailbox.vala index ea2add2c..9db16706 100644 --- a/src/engine/imap/transport/imap-mailbox.vala +++ b/src/engine/imap/transport/imap-mailbox.vala @@ -33,7 +33,7 @@ public class Geary.Imap.Mailbox : Geary.SmartReference { public signal void recent_altered(int recent); - public signal void flags_altered(FetchResults flags); + public signal void flags_altered(MailboxAttributes flags); public signal void expunged(MessageNumber msg_num, int total); @@ -125,7 +125,6 @@ public class Geary.Imap.Mailbox : Geary.SmartReference { msgs.add(email); map.set(plain_res.msg_num, email); - assert(map.get(plain_res.msg_num) != null); } // process preview FETCH results @@ -142,9 +141,8 @@ public class Geary.Imap.Mailbox : Geary.SmartReference { FetchResults[] preview_results = FetchResults.decode(preview_resp); foreach (FetchResults preview_res in preview_results) { Geary.Email? preview_email = map.get(preview_res.msg_num); - assert(preview_email != null); - - preview_email.set_message_preview(new RFC822.Text(preview_res.get_body_data()[0])); + if (preview_email != null) + preview_email.set_message_preview(new RFC822.Text(preview_res.get_body_data()[0])); } } @@ -171,7 +169,7 @@ public class Geary.Imap.Mailbox : Geary.SmartReference { expunged(msg_num, total); } - private void on_flags_altered(FetchResults flags) { + private void on_flags_altered(MailboxAttributes flags) { flags_altered(flags); } @@ -444,7 +442,7 @@ private class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics { public signal void expunged(MessageNumber msg_num, int total); - public signal void flags_altered(FetchResults flags); + public signal void flags_altered(MailboxAttributes flags); public signal void closed(); @@ -514,8 +512,8 @@ private class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics { expunged(msg_num, exists); } - private void on_unsolicited_flags(FetchResults results) { - flags_altered(results); + private void on_unsolicited_flags(MailboxAttributes flags) { + flags_altered(flags); } private void on_session_mailbox_changed(string? old_mailbox, string? new_mailbox, bool readonly) { diff --git a/src/engine/impl/geary-engine-folder.vala b/src/engine/impl/geary-engine-folder.vala index 60290f8c..b525d088 100644 --- a/src/engine/impl/geary-engine-folder.vala +++ b/src/engine/impl/geary-engine-folder.vala @@ -303,10 +303,25 @@ private class Geary.EngineFolder : Geary.AbstractFolder { Geary.EmailIdentifier? owned_id = id; if (owned_id == null) { try { - Gee.List? local = yield local_folder.list_email_async(remote_position, 1, - Geary.Email.Field.NONE, Geary.Folder.ListFlags.NONE, null); - if (local != null && local.size > 0) - owned_id = local[0].id; + // convert remote positional addressing to local positional addressing + int local_count = yield local_folder.get_email_count_async(); + int local_position = remote_position_to_local_position(remote_position, local_count); + + // possible we don't have the remote email locally + if (local_position >= 1) { + // get EmailIdentifier for removed email + Gee.List? local = yield local_folder.list_email_async(local_position, 1, + Geary.Email.Field.NONE, Geary.Folder.ListFlags.NONE, null); + if (local != null && local.size == 1) { + owned_id = local[0].id; + } else { + debug("list_email_async unable to convert position %d into id (count=%d)", + local_position, yield local_folder.get_email_count_async()); + } + } else { + debug("Unable to get local position for remote position %d (local_count=%d remote_count=%d)", + remote_position, local_count, remote_count); + } } catch (Error err) { debug("Unable to determine ID of removed message #%d from %s: %s", remote_position, to_string(), err.message); @@ -408,7 +423,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder { // because the local store caches messages starting from the newest (at the end of the list) // to the earliest fetched by the user, need to adjust the low value to match its offset // and range - local_low = low - (remote_count - local_count); + local_low = remote_position_to_local_position(low, local_count); } else { normalize_span_specifiers(ref low, ref count, local_count); local_low = low.clamp(1, local_count); @@ -708,8 +723,11 @@ private class Geary.EngineFolder : Geary.AbstractFolder { } // normalize the initial position to the remote folder's addressing - initial_position = remote_count - (local_count - initial_position); - assert(initial_position > 0); + initial_position = local_position_to_remote_position(initial_position, local_count); + if (initial_position <= 0) { + throw new EngineError.NOT_FOUND("Cannot map email ID %s in %s to remote folder", + initial_id.to_string(), to_string()); + } // since count can also indicate "to earliest" or "to latest", normalize // (count is exclusive of initial_id, hence adding/substracting one, meaning that a count @@ -732,9 +750,16 @@ private class Geary.EngineFolder : Geary.AbstractFolder { int actual_count = ((high - low) + 1); - // one more check for exclusive listing - if (actual_count == 0 || (excluding_id && actual_count == 1)) + // one more check + if (actual_count == 0) { + debug("do_list_email_by_id_async: no actual count to return (%d) (excluding=%s %s)", + actual_count, excluding_id.to_string(), initial_id.to_string()); + + if (cb != null) + cb(null, null); + return; + } debug("do_list_email_by_id_async: initial_id=%s initial_position=%d count=%d actual_count=%d low=%d high=%d local_count=%d remote_count=%d excl=%s", initial_id.to_string(), initial_position, count, actual_count, low, high, local_count, @@ -847,6 +872,23 @@ private class Geary.EngineFolder : Geary.AbstractFolder { throw new EngineError.READONLY("EngineFolder currently cannot remove email"); } + // Converts a remote position to a local position, assuming that the remote has been completely + // opened. local_count must be supplied because that's not held by EngineFolder (unlike + // remote_count). + // + // Returns a negative value if not available in local folder or remote is not open yet. + private int remote_position_to_local_position(int remote_pos, int local_count) { + return (remote_count >= 0) ? remote_pos - (remote_count - local_count) : -1; + } + + // Converts a local position to a remote position, assuming that the remote has been completely + // opened. See remote_position_to_local_position for more caveats. + // + // Returns a negative value if remote is not open. + private int local_position_to_remote_position(int local_pos, int local_count) { + return (remote_count >= 0) ? remote_count - (local_count - local_pos) : -1; + } + // In order to maintain positions for all messages without storing all of them locally, // the database stores entries for the lowest requested email to the highest (newest), which // means there can be no gaps between the last in the database and the last on the server. @@ -887,8 +929,13 @@ private class Geary.EngineFolder : Geary.AbstractFolder { count, low, to_string()); } + NonblockingBatch batch = new NonblockingBatch(); + foreach (Geary.Email email in list) - yield local_folder.create_email_async(email, cancellable); + batch.add(new CreateEmailOperation(local_folder, email)); + + yield batch.execute_all_async(cancellable); + batch.throw_first_exception(); debug("prefetched %d for %s", prefetch_count, to_string()); } diff --git a/src/wscript b/src/wscript index b9dd50f6..68fa2db3 100644 --- a/src/wscript +++ b/src/wscript @@ -69,6 +69,7 @@ def build(bld): '../engine/imap/response/imap-status-data-type.vala', '../engine/imap/response/imap-status-response.vala', '../engine/imap/response/imap-status.vala', + '../engine/imap/response/imap-unsolicited-server-data.vala', '../engine/imap/transport/imap-client-connection.vala', '../engine/imap/transport/imap-client-session-manager.vala', '../engine/imap/transport/imap-client-session.vala',