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',