From 9ac3fd0b68b3b72a787d299cdd5666af60e73641 Mon Sep 17 00:00:00 2001 From: Jim Nelson Date: Tue, 31 May 2011 15:40:39 -0700 Subject: [PATCH] Added SelectExamineResults decoder. The result of a SELECT or EXAMINE command is now parsed and returned to the caller. This information is boiled down to the Geary.Folder interface, which adds information about the folder to the object. --- Makefile | 3 + src/client/ui/FolderListStore.vala | 8 +- src/client/ui/MainWindow.vala | 3 +- src/engine/Interfaces.vala | 14 +- src/engine/imap/ClientSession.vala | 12 +- src/engine/imap/ClientSessionManager.vala | 28 ++-- src/engine/imap/Flag.vala | 2 + src/engine/imap/Mailbox.vala | 13 +- src/engine/imap/MessageData.vala | 15 ++ src/engine/imap/Parameter.vala | 8 +- src/engine/imap/ResponseCode.vala | 4 + src/engine/imap/ResponseCodeType.vala | 101 ++++++++++++++ src/engine/imap/decoders/CommandResults.vala | 14 ++ src/engine/imap/decoders/FetchResults.vala | 12 +- src/engine/imap/decoders/ListResults.vala | 12 +- src/engine/imap/decoders/NoopResults.vala | 10 +- .../imap/decoders/SelectExamineResults.vala | 131 ++++++++++++++++++ 17 files changed, 343 insertions(+), 47 deletions(-) create mode 100644 src/engine/imap/ResponseCodeType.vala create mode 100644 src/engine/imap/decoders/CommandResults.vala create mode 100644 src/engine/imap/decoders/SelectExamineResults.vala diff --git a/Makefile b/Makefile index cb2f9752..f4af17bd 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ ENGINE_SRC := \ src/engine/imap/Command.vala \ src/engine/imap/Commands.vala \ src/engine/imap/ResponseCode.vala \ + src/engine/imap/ResponseCodeType.vala \ src/engine/imap/ServerResponse.vala \ src/engine/imap/StatusResponse.vala \ src/engine/imap/ServerData.vala \ @@ -39,10 +40,12 @@ ENGINE_SRC := \ src/engine/imap/Deserializer.vala \ src/engine/imap/Error.vala \ src/engine/imap/Flag.vala \ + src/engine/imap/decoders/CommandResults.vala \ src/engine/imap/decoders/FetchDataDecoder.vala \ src/engine/imap/decoders/FetchResults.vala \ src/engine/imap/decoders/NoopResults.vala \ src/engine/imap/decoders/ListResults.vala \ + src/engine/imap/decoders/SelectExamineResults.vala \ src/engine/rfc822/MailboxAddress.vala \ src/engine/rfc822/MessageData.vala \ src/engine/util/String.vala \ diff --git a/src/client/ui/FolderListStore.vala b/src/client/ui/FolderListStore.vala index c3a2d30d..b838c553 100644 --- a/src/client/ui/FolderListStore.vala +++ b/src/client/ui/FolderListStore.vala @@ -34,15 +34,15 @@ public class FolderListStore : Gtk.TreeStore { set_column_types(Column.get_types()); } - public void add_folder(string folder) { + public void add_folder(Geary.FolderDetail folder) { Gtk.TreeIter iter; append(out iter, null); - set(iter, Column.NAME, folder); + set(iter, Column.NAME, folder.name); } - public void add_folders(Gee.Collection folders) { - foreach (string folder in folders) + public void add_folders(Gee.Collection folders) { + foreach (Geary.FolderDetail folder in folders) add_folder(folder); } diff --git a/src/client/ui/MainWindow.vala b/src/client/ui/MainWindow.vala index a6499f12..220b1b07 100644 --- a/src/client/ui/MainWindow.vala +++ b/src/client/ui/MainWindow.vala @@ -63,7 +63,8 @@ public class MainWindow : Gtk.Window { if (account == null) error("Unable to login"); - Gee.Collection? folders = yield account.list("/"); + // pull down the root-level folders + Gee.Collection folders = yield account.list(null); if (folders != null) { debug("%d folders found", folders.size); folder_list_store.add_folders(folders); diff --git a/src/engine/Interfaces.vala b/src/engine/Interfaces.vala index 248dcb00..8cf4d144 100644 --- a/src/engine/Interfaces.vala +++ b/src/engine/Interfaces.vala @@ -4,9 +4,13 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ +public interface Geary.FolderDetail : Object { + public abstract string name { get; protected set; } +} + public interface Geary.Account : Object { - public abstract async Gee.Collection list(string parent, Cancellable? cancellable = null) - throws Error; + public abstract async Gee.Collection list(FolderDetail? parent, + Cancellable? cancellable = null) throws Error; public abstract async Folder open(string folder, Cancellable? cancellable = null) throws Error; } @@ -18,6 +22,12 @@ public interface Geary.Folder : Object { FOLDER_CLOSED } + public abstract string name { get; protected set; } + + public abstract int count { get; protected set; } + + public abstract bool is_readonly { get; protected set; } + public signal void closed(CloseReason reason); public abstract async Gee.List? read(int low, int count, Cancellable? cancellable = null) diff --git a/src/engine/imap/ClientSession.vala b/src/engine/imap/ClientSession.vala index 5c64739d..fb866c91 100644 --- a/src/engine/imap/ClientSession.vala +++ b/src/engine/imap/ClientSession.vala @@ -645,16 +645,18 @@ public class Geary.Imap.ClientSession { // select/examine // - public async string select_async(string mailbox, Cancellable? cancellable = null) throws Error { + public async SelectExamineResults select_async(string mailbox, Cancellable? cancellable = null) + throws Error { return yield select_examine_async(mailbox, true, cancellable); } - public async string examine_async(string mailbox, Cancellable? cancellable = null) throws Error { + public async SelectExamineResults examine_async(string mailbox, Cancellable? cancellable = null) + throws Error { return yield select_examine_async(mailbox, false, cancellable); } - public async string select_examine_async(string mailbox, bool is_select, Cancellable? cancellable) - throws Error { + public async SelectExamineResults select_examine_async(string mailbox, bool is_select, + Cancellable? cancellable) throws Error { string? old_mailbox = current_mailbox; SelectParams params = new SelectParams(mailbox, is_select, cancellable, @@ -673,7 +675,7 @@ public class Geary.Imap.ClientSession { assert(current_mailbox != null); current_mailbox_changed(old_mailbox, current_mailbox, current_mailbox_readonly); - return current_mailbox; + return SelectExamineResults.decode(params.cmd_response); } private uint on_select(uint state, uint event, void *user, Object? object) { diff --git a/src/engine/imap/ClientSessionManager.vala b/src/engine/imap/ClientSessionManager.vala index 73e4c5fc..bfc2d990 100644 --- a/src/engine/imap/ClientSessionManager.vala +++ b/src/engine/imap/ClientSessionManager.vala @@ -31,8 +31,9 @@ public class Geary.Imap.ClientSessionManager : Object, Geary.Account { session.enable_keepalives(keepalive_sec); } - public async Gee.Collection list(string parent, Cancellable? cancellable = null) throws Error { - string specifier = String.is_empty(parent) ? "/" : parent; + public async Gee.Collection list(Geary.FolderDetail? parent, + Cancellable? cancellable = null) throws Error { + string specifier = (parent != null) ? parent.name : "/"; specifier += (specifier.has_suffix("/")) ? "%" : "/%"; ClientSession session = yield get_authorized_session(cancellable); @@ -40,11 +41,14 @@ public class Geary.Imap.ClientSessionManager : Object, Geary.Account { ListResults results = ListResults.decode(yield session.send_command_async( new ListCommand(session.generate_tag(), specifier), cancellable)); - return results.get_names(); + return results.get_all(); } public async Geary.Folder open(string folder, Cancellable? cancellable = null) throws Error { - return new Mailbox(yield examine_async(folder, cancellable), on_destroying_mailbox); + SelectExamineResults results; + ClientSession session = yield examine_async(folder, out results, cancellable); + + return new Mailbox(session, results, on_destroying_mailbox); } private async ClientSession get_authorized_session(Cancellable? cancellable = null) throws Error { @@ -67,18 +71,18 @@ public class Geary.Imap.ClientSessionManager : Object, Geary.Account { return new_session; } - public async ClientSession select_async(string folder, Cancellable? cancellable = null) - throws Error { - return yield select_examine_async(folder, true, cancellable); + public async ClientSession select_async(string folder, out SelectExamineResults results, + Cancellable? cancellable = null) throws Error { + return yield select_examine_async(folder, true, out results, cancellable); } - public async ClientSession examine_async(string folder, Cancellable? cancellable = null) - throws Error { - return yield select_examine_async(folder, false, cancellable); + public async ClientSession examine_async(string folder, out SelectExamineResults results, + Cancellable? cancellable = null) throws Error { + return yield select_examine_async(folder, false, out results, cancellable); } public async ClientSession select_examine_async(string folder, bool is_select, - Cancellable? cancellable = null) throws Error { + out SelectExamineResults results, Cancellable? cancellable = null) throws Error { ClientSession.Context needed_context = (is_select) ? ClientSession.Context.SELECTED : ClientSession.Context.EXAMINED; foreach (ClientSession session in sessions) { @@ -89,7 +93,7 @@ public class Geary.Imap.ClientSessionManager : Object, Geary.Account { ClientSession authd = yield get_authorized_session(cancellable); - yield authd.select_examine_async(folder, is_select, cancellable); + results = yield authd.select_examine_async(folder, is_select, cancellable); return authd; } diff --git a/src/engine/imap/Flag.vala b/src/engine/imap/Flag.vala index e99ee4ff..fc002091 100644 --- a/src/engine/imap/Flag.vala +++ b/src/engine/imap/Flag.vala @@ -43,6 +43,7 @@ public class Geary.Imap.MessageFlag : Geary.Imap.Flag { public static MessageFlag FLAGGED = new MessageFlag("\\flagged"); public static MessageFlag RECENT = new MessageFlag("\\recent"); public static MessageFlag SEEN = new MessageFlag("\\seen"); + public static MessageFlag ALLOWS_NEW = new MessageFlag("\\*"); public MessageFlag(string value) { base (value); @@ -55,6 +56,7 @@ public class Geary.Imap.MailboxAttribute : Geary.Imap.Flag { public static MailboxAttribute MARKED = new MailboxAttribute("\\marked"); public static MailboxAttribute UNMARKED = new MailboxAttribute("\\unmarked"); public static MailboxAttribute HAS_NO_CHILDREN = new MailboxAttribute("\\hasnochildren"); + public static MailboxAttribute ALLOWS_NEW = new MailboxAttribute("\\*"); public MailboxAttribute(string value) { base (value); diff --git a/src/engine/imap/Mailbox.vala b/src/engine/imap/Mailbox.vala index 7d54feaa..bab66a6c 100644 --- a/src/engine/imap/Mailbox.vala +++ b/src/engine/imap/Mailbox.vala @@ -5,18 +5,23 @@ */ public class Geary.Imap.Mailbox : Object, Geary.Folder { - public string name { get; private set; } - public bool is_readonly { get; private set; } + public string name { get; protected set; } + public int count { get; protected set; } + public bool is_readonly { get; protected set; } private ClientSession? session; + private SelectExamineResults select_results; private Geary.Delegate.DestructorNotifier? dtor_notifier; - internal Mailbox(ClientSession session, Geary.Delegate.DestructorNotifier? dtor_notifier) { + internal Mailbox(ClientSession session, SelectExamineResults results, + Geary.Delegate.DestructorNotifier? dtor_notifier) { this.session = session; + this.select_results = results; this.dtor_notifier = dtor_notifier; name = session.get_current_mailbox(); - is_readonly = session.is_current_mailbox_readonly(); + is_readonly = results.readonly; + count = results.exists; session.current_mailbox_changed.connect(on_session_mailbox_changed); session.logged_out.connect(on_session_logged_out); diff --git a/src/engine/imap/MessageData.vala b/src/engine/imap/MessageData.vala index 76e8d9e5..d51e23fb 100644 --- a/src/engine/imap/MessageData.vala +++ b/src/engine/imap/MessageData.vala @@ -65,6 +65,21 @@ public class Geary.Imap.MessageFlags : Geary.Imap.Flags { public MessageFlags(Gee.Collection flags) { base (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)); + } + + return new MessageFlags(list); + } } public class Geary.Imap.MailboxAttributes : Geary.Imap.Flags { diff --git a/src/engine/imap/Parameter.vala b/src/engine/imap/Parameter.vala index e8407e7f..540fd020 100644 --- a/src/engine/imap/Parameter.vala +++ b/src/engine/imap/Parameter.vala @@ -64,13 +64,13 @@ public class Geary.Imap.StringParameter : Geary.Imap.Parameter { // TODO: This does not check that the value is a properly-formed integer. This should be // added later. - public int as_int() throws ImapError { - return int.parse(value); + public int as_int(int clamp_min = int.MIN, int clamp_max = int.MAX) throws ImapError { + return int.parse(value).clamp(clamp_min, clamp_max); } // TODO: This does not check that the value is a properly-formed long. - public long as_long() throws ImapError { - return long.parse(value); + public long as_long(int clamp_min = int.MIN, int clamp_max = int.MAX) throws ImapError { + return long.parse(value).clamp(clamp_min, clamp_max); } public override string to_string() { diff --git a/src/engine/imap/ResponseCode.vala b/src/engine/imap/ResponseCode.vala index 41d695fc..b4a834a7 100644 --- a/src/engine/imap/ResponseCode.vala +++ b/src/engine/imap/ResponseCode.vala @@ -9,6 +9,10 @@ public class Geary.Imap.ResponseCode : Geary.Imap.ListParameter { base (parent, initial); } + public ResponseCodeType get_code_type() throws ImapError { + return ResponseCodeType.from_parameter(get_as_string(0)); + } + public override string to_string() { return "[%s]".printf(stringize_list()); } diff --git a/src/engine/imap/ResponseCodeType.vala b/src/engine/imap/ResponseCodeType.vala new file mode 100644 index 00000000..0f86dd1c --- /dev/null +++ b/src/engine/imap/ResponseCodeType.vala @@ -0,0 +1,101 @@ +/* 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. + */ + +public enum Geary.Imap.ResponseCodeType { + ALERT, + NEWNAME, + PARSE, + PERMANENT_FLAGS, + READONLY, + READWRITE, + TRY_CREATE, + UIDVALIDITY, + UIDNEXT, + UNSEEN; + + public string to_string() { + switch (this) { + case ALERT: + return "alert"; + + case NEWNAME: + return "newname"; + + case PARSE: + return "parse"; + + case PERMANENT_FLAGS: + return "permanentflags"; + + case READONLY: + return "read-only"; + + case READWRITE: + return "read-write"; + + case TRY_CREATE: + return "trycreate"; + + case UIDVALIDITY: + return "uidvalidity"; + + case UIDNEXT: + return "uidnext"; + + case UNSEEN: + return "unseen"; + + default: + assert_not_reached(); + } + } + + public static ResponseCodeType decode(string value) throws ImapError { + switch (value.down()) { + case "alert": + return ALERT; + + case "newname": + return NEWNAME; + + case "parse": + return PARSE; + + case "permanentflags": + return PERMANENT_FLAGS; + + case "read-only": + return READONLY; + + case "read-write": + return READWRITE; + + case "trycreate": + return TRY_CREATE; + + case "uidvalidity": + return UIDVALIDITY; + + case "uidnext": + return UIDNEXT; + + case "unseen": + return UNSEEN; + + default: + throw new ImapError.PARSE_ERROR("Unknown response code \"%s\"", value); + } + } + + public static ResponseCodeType from_parameter(StringParameter stringp) throws ImapError { + return decode(stringp.value); + } + + public StringParameter to_parameter() { + return new StringParameter(to_string()); + } +} + diff --git a/src/engine/imap/decoders/CommandResults.vala b/src/engine/imap/decoders/CommandResults.vala new file mode 100644 index 00000000..5e6d859b --- /dev/null +++ b/src/engine/imap/decoders/CommandResults.vala @@ -0,0 +1,14 @@ +/* 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. + */ + +public abstract class Geary.Imap.CommandResults { + public StatusResponse status_response { get; private set; } + + public CommandResults(StatusResponse status_response) { + this.status_response = status_response; + } +} + diff --git a/src/engine/imap/decoders/FetchResults.vala b/src/engine/imap/decoders/FetchResults.vala index 82ec776c..06083bee 100644 --- a/src/engine/imap/decoders/FetchResults.vala +++ b/src/engine/imap/decoders/FetchResults.vala @@ -12,16 +12,18 @@ * results for all messages specified. */ -public class Geary.Imap.FetchResults { +public class Geary.Imap.FetchResults : Geary.Imap.CommandResults { public int msg_num { get; private set; } private Gee.Map map = new Gee.HashMap(); - public FetchResults(int msg_num) { + public FetchResults(StatusResponse status_response, int msg_num) { + base (status_response); + this.msg_num = msg_num; } - public static FetchResults decode_data(ServerData data) throws ImapError { + public static FetchResults decode_data(StatusResponse status_response, ServerData data) throws ImapError { StringParameter msg_num = data.get_as_string(1); StringParameter cmd = data.get_as_string(2); ListParameter list = data.get_as_list(3); @@ -32,7 +34,7 @@ public class Geary.Imap.FetchResults { data.to_string()); } - FetchResults results = new FetchResults(msg_num.as_int()); + FetchResults results = new FetchResults(status_response, msg_num.as_int()); // walk the list for each returned fetch data item, which is paired by its data item name // and the structured data itself @@ -59,7 +61,7 @@ public class Geary.Imap.FetchResults { FetchResults[] array = new FetchResults[0]; foreach (ServerData data in response.server_data) { try { - array += decode_data(data); + array += decode_data(response.status_response, data); } catch (ImapError ierr) { // drop bad data on the ground debug("Dropping FETCH data \"%s\": %s", data.to_string(), ierr.message); diff --git a/src/engine/imap/decoders/ListResults.vala b/src/engine/imap/decoders/ListResults.vala index 5660c1e0..1bd7f19e 100644 --- a/src/engine/imap/decoders/ListResults.vala +++ b/src/engine/imap/decoders/ListResults.vala @@ -4,8 +4,8 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -public class Geary.Imap.FolderDetail { - public string name { get; private set; } +public class Geary.Imap.FolderDetail : Object, Geary.FolderDetail { + public string name { get; protected set; } public string delim { get; private set; } public MailboxAttributes attrs { get; private set; } @@ -16,10 +16,12 @@ public class Geary.Imap.FolderDetail { } } -public class Geary.Imap.ListResults { +public class Geary.Imap.ListResults : Geary.Imap.CommandResults { private Gee.HashMap map = new Gee.HashMap(); - private ListResults(Gee.Collection details) { + public ListResults(StatusResponse status_response, Gee.Collection details) { + base (status_response); + foreach (FolderDetail detail in details) map.set(detail.name, detail); } @@ -61,7 +63,7 @@ public class Geary.Imap.ListResults { } } - return new ListResults(details); + return new ListResults(response.status_response, details); } public Gee.Collection get_names() { diff --git a/src/engine/imap/decoders/NoopResults.vala b/src/engine/imap/decoders/NoopResults.vala index babab9c4..ffdbd34f 100644 --- a/src/engine/imap/decoders/NoopResults.vala +++ b/src/engine/imap/decoders/NoopResults.vala @@ -4,8 +4,7 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -public class Geary.Imap.NoopResults { - public StatusResponse status_response { get; private set; } +public class Geary.Imap.NoopResults : Geary.Imap.CommandResults { public Gee.List? expunged { get; private set; } /** * -1 if "exists" result not returned by server. @@ -19,14 +18,15 @@ public class Geary.Imap.NoopResults { public NoopResults(StatusResponse status_response, Gee.List? expunged, int exists, Gee.List? flags, int recent) { - this.status_response = status_response; + base (status_response); + this.expunged = expunged; this.exists = exists; this.flags = flags; this.recent = recent; } - public static NoopResults decode(CommandResponse response) throws ImapError { + public static NoopResults decode(CommandResponse response) { assert(response.is_sealed()); Gee.List expunged = new Gee.ArrayList(); @@ -53,7 +53,7 @@ public class Geary.Imap.NoopResults { break; case ServerDataType.FETCH: - flags.add(FetchResults.decode_data(data)); + flags.add(FetchResults.decode_data(response.status_response, data)); break; default: diff --git a/src/engine/imap/decoders/SelectExamineResults.vala b/src/engine/imap/decoders/SelectExamineResults.vala new file mode 100644 index 00000000..2fdaa07e --- /dev/null +++ b/src/engine/imap/decoders/SelectExamineResults.vala @@ -0,0 +1,131 @@ +/* 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. + */ + +public class Geary.Imap.SelectExamineResults : Geary.Imap.CommandResults { + /** + * -1 if not specified. + */ + public int exists { get; private set; } + /** + * -1 if not specified. + */ + public int recent { get; private set; } + /** + * -1 if not specified. + */ + public int unseen { get; private set; } + public UID? uidvalidity { get; private set; } + public Flags? flags { get; private set; } + public Flags? permanentflags { get; private set; } + public bool readonly { get; private set; } + + private SelectExamineResults(StatusResponse status_response, int exists, int recent, int unseen, + UID? uidvalidity, Flags? flags, Flags? permanentflags, bool readonly) { + base (status_response); + + this.exists = exists; + this.recent = recent; + this.unseen = unseen; + this.uidvalidity = uidvalidity; + this.flags = flags; + this.permanentflags = permanentflags; + this.readonly = readonly; + } + + public static SelectExamineResults decode(CommandResponse response) throws ImapError { + assert(response.is_sealed()); + + int exists = -1; + int recent = -1; + int unseen = -1; + UID? uidvalidity = null; + UID? uidnext = null; + MessageFlags? flags = null; + MessageFlags? permanentflags = null; + + bool readonly = true; + try { + readonly = response.status_response.response_code.get_as_string(0).value.down() != "read-write"; + } catch (ImapError ierr) { + message("Invalid SELECT/EXAMINE read-write indicator: %s", + response.status_response.to_string()); + } + + foreach (ServerData data in response.server_data) { + try { + StringParameter stringp = data.get_as_string(1); + switch (stringp.value.down()) { + case "ok": + // ok lines are structured like StatusResponses + StatusResponse ok_response = new StatusResponse.reconstitute(data); + if (ok_response.response_code == null) { + message("Invalid SELECT/EXAMINE response \"%s\": no response code", + data.to_string()); + + break; + } + + // the ResponseCode is what we're interested in + switch (ok_response.response_code.get_code_type()) { + case ResponseCodeType.UNSEEN: + unseen = ok_response.response_code.get_as_string(1).as_int(0, int.MAX); + break; + + case ResponseCodeType.UIDVALIDITY: + uidvalidity = new UID( + ok_response.response_code.get_as_string(1).as_int()); + break; + + case ResponseCodeType.UIDNEXT: + uidnext = new UID(ok_response.response_code.get_as_string(1).as_int()); + break; + + case ResponseCodeType.PERMANENT_FLAGS: + permanentflags = MessageFlags.from_list( + ok_response.response_code.get_as_list(1)); + break; + + default: + message("Unknown line in SELECT/EXAMINE response: \"%s\"", data.to_string()); + break; + } + break; + + case "flags": + flags = MessageFlags.from_list(data.get_as_list(2)); + break; + + default: + // if second parameter is a type descriptor, stringp is an ordinal + switch (ServerDataType.from_parameter(data.get_as_string(2))) { + case ServerDataType.EXISTS: + exists = stringp.as_int(0, int.MAX); + break; + + case ServerDataType.RECENT: + recent = stringp.as_int(0, int.MAX); + break; + + default: + message("Unknown line in SELECT/EXAMINE response: \"%s\"", data.to_string()); + break; + } + break; + } + } catch (ImapError ierr) { + message("SELECT/EXAMINE decode error for \"%s\": %s", data.to_string(), ierr.message); + } + } + + // flags, exists, and recent are required + if (flags == null || exists < 0 || recent < 0) + throw new ImapError.PARSE_ERROR("Incomplete SELECT/EXAMINE Response: \"%s\"", response.to_string()); + + return new SelectExamineResults(response.status_response, exists, recent, unseen, + uidvalidity, flags, permanentflags, readonly); + } +} +