diff --git a/Makefile b/Makefile index 9cb3c199..e7f7e6cf 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ BUILD_ROOT = 1 VALAC := valac -APPS := console syntax lsmbox readmail +APPS := console syntax lsmbox readmail watchmbox ENGINE_SRC := \ src/engine/Engine.vala \ @@ -20,20 +20,22 @@ ENGINE_SRC := \ src/engine/imap/Tag.vala \ src/engine/imap/Command.vala \ src/engine/imap/Commands.vala \ - src/engine/imap/FetchCommand.vala \ src/engine/imap/ResponseCode.vala \ src/engine/imap/ServerResponse.vala \ src/engine/imap/StatusResponse.vala \ src/engine/imap/ServerData.vala \ + src/engine/imap/ServerDataType.vala \ + src/engine/imap/FetchDataType.vala \ src/engine/imap/Status.vala \ src/engine/imap/CommandResponse.vala \ - src/engine/imap/FetchResults.vala \ - src/engine/imap/FetchDataDecoder.vala \ src/engine/imap/MessageData.vala \ src/engine/imap/Serializable.vala \ src/engine/imap/Serializer.vala \ src/engine/imap/Deserializer.vala \ src/engine/imap/Error.vala \ + src/engine/imap/decoders/FetchDataDecoder.vala \ + src/engine/imap/decoders/FetchResults.vala \ + src/engine/imap/decoders/NoopResults.vala \ src/engine/rfc822/MailboxAddress.vala \ src/engine/rfc822/MessageData.vala \ src/engine/util/String.vala \ @@ -51,7 +53,10 @@ LSMBOX_SRC := \ READMAIL_SRC := \ src/tests/readmail.vala -ALL_SRC := $(ENGINE_SRC) $(CONSOLE_SRC) $(SYNTAX_SRC) $(LSMBOX_SRC) $(READMAIL_SRC) +WATCHMBOX_SRC := \ + src/tests/watchmbox.vala + +ALL_SRC := $(ENGINE_SRC) $(CONSOLE_SRC) $(SYNTAX_SRC) $(LSMBOX_SRC) $(READMAIL_SRC) $(WATCHMBOX_SRC) EXTERNAL_PKGS := \ gio-2.0 \ @@ -86,3 +91,8 @@ readmail: $(ENGINE_SRC) $(READMAIL_SRC) Makefile $(ENGINE_SRC) $(READMAIL_SRC) \ -o $@ +watchmbox: $(ENGINE_SRC) $(WATCHMBOX_SRC) Makefile + $(VALAC) --save-temps -g $(foreach pkg,$(EXTERNAL_PKGS),--pkg=$(pkg)) \ + $(ENGINE_SRC) $(WATCHMBOX_SRC) \ + -o $@ + diff --git a/src/console/main.vala b/src/console/main.vala index cbffa8ea..f92eb084 100644 --- a/src/console/main.vala +++ b/src/console/main.vala @@ -354,9 +354,9 @@ class ImapConsole : Gtk.Window { status("Fetching %s".printf(args[0])); - Geary.Imap.FetchDataItem[] data_items = new Geary.Imap.FetchDataItem[0]; + Geary.Imap.FetchDataType[] data_items = new Geary.Imap.FetchDataType[0]; for (int ctr = 1; ctr < args.length; ctr++) - data_items += Geary.Imap.FetchDataItem.decode(args[ctr]); + data_items += Geary.Imap.FetchDataType.decode(args[ctr]); cx.post(new Geary.Imap.FetchCommand(cx.generate_tag(), args[0], data_items), on_fetch); } diff --git a/src/engine/imap/ClientSession.vala b/src/engine/imap/ClientSession.vala index b3b66565..e439ccb7 100644 --- a/src/engine/imap/ClientSession.vala +++ b/src/engine/imap/ClientSession.vala @@ -5,6 +5,10 @@ */ public class Geary.Imap.ClientSession : Object, Geary.Account { + // 30 min keepalive required to maintain session; back off by 30 sec for breathing room + public const int MIN_KEEPALIVE_SEC = (30 * 60) - 30; + public const int DEFAULT_KEEPALIVE_SEC = 60; + // Need this because delegates with targets cannot be stored in ADTs. private class CommandCallback { public SourceFunc callback; @@ -149,13 +153,26 @@ public class Geary.Imap.ClientSession : Object, Geary.Account { private Gee.Queue cb_queue = new Gee.LinkedList(); private Gee.Queue cmd_response_queue = new Gee.LinkedList(); private CommandResponse current_cmd_response = new CommandResponse(); - + private uint keepalive_id = 0; + // state used only during connect and disconnect private bool awaiting_connect_response = false; private ServerData? connect_response = null; private AsyncParams? connect_params = null; private AsyncParams? disconnect_params = null; + public virtual signal void unsolicited_expunged(MessageNumber msg) { + } + + public virtual signal void unsolicited_exists(int exists) { + } + + public virtual signal void unsolicitied_recent(int recent) { + } + + public virtual signal void unsolicited_flags(FetchResults flags) { + } + public ClientSession(string server, uint default_port) { this.server = server; this.default_port = default_port; @@ -422,6 +439,75 @@ public class Geary.Imap.ClientSession : Object, Geary.Account { return State.NOAUTH; } + // + // keepalives (nop idling to keep the session alive and to periodically receive notifications + // of changes) + // + + /** + * Returns true if keepalives are activated, false if already enabled. + */ + public bool enable_keepalives(int seconds = DEFAULT_KEEPALIVE_SEC) { + if (keepalive_id != 0) + return false; + + keepalive_id = Timeout.add_seconds(seconds, on_keepalive); + + return true; + } + + /** + * Returns true if keepalives are disactivated, false if already disabled. + */ + public bool disable_keepalives() { + if (keepalive_id == 0) + return false; + + Source.remove(keepalive_id); + keepalive_id = 0; + + return true; + } + + private bool on_keepalive() { + send_command_async.begin(new NoopCommand(generate_tag()), null, on_keepalive_completed); + + return true; + } + + private void on_keepalive_completed(Object? source, AsyncResult result) { + NoopResults results; + try { + results = NoopResults.decode(send_command_async.end(result)); + } catch (Error err) { + message("Keepalive error: %s", err.message); + + return; + } + + if (results.status_response.status != Status.OK) { + debug("Keepalive failed: %s", results.status_response.to_string()); + + return; + } + + if (results.expunged != null) { + foreach (MessageNumber msg in results.expunged) + unsolicited_expunged(msg); + } + + if (results.has_exists()) + unsolicited_exists(results.exists); + + if (results.has_recent()) + unsolicitied_recent(results.recent); + + if (results.flags != null) { + foreach (FetchResults flags in results.flags) + unsolicited_flags(flags); + } + } + // // send commands // @@ -739,7 +825,9 @@ public class Geary.Imap.ClientSession : Object, Geary.Account { } private uint on_ignored_transition(uint state, uint event) { +#if VERBOSE_SESSION debug("Ignored transition: %s@%s", fsm.get_event_string(event), fsm.get_state_string(state)); +#endif return state; } @@ -813,15 +901,21 @@ public class Geary.Imap.ClientSession : Object, Geary.Account { // private void on_network_connected() { +#if VERBOSE_SESSION debug("Connected to %s", server); +#endif } private void on_network_disconnected() { +#if VERBOSE_SESSION debug("Disconnected from %s", server); +#endif } private void on_network_sent_command(Command cmd) { +#if VERBOSE_SESSION debug("Sent command %s", cmd.to_string()); +#endif } private void on_received_status_response(StatusResponse status_response) { diff --git a/src/engine/imap/Commands.vala b/src/engine/imap/Commands.vala index b620f15c..27c331a1 100644 --- a/src/engine/imap/Commands.vala +++ b/src/engine/imap/Commands.vala @@ -76,3 +76,24 @@ public class Geary.Imap.CloseCommand : Command { } } +public class Geary.Imap.FetchCommand : Command { + public const string NAME = "fetch"; + + public FetchCommand(Tag tag, string msg_span, FetchDataType[] data_items) { + base (tag, NAME); + + add(new StringParameter(msg_span)); + + assert(data_items.length > 0); + if (data_items.length == 1) { + add(data_items[0].to_parameter()); + } else { + ListParameter data_item_list = new ListParameter(this); + foreach (FetchDataType data_item in data_items) + data_item_list.add(data_item.to_parameter()); + + add(data_item_list); + } + } +} + diff --git a/src/engine/imap/FetchCommand.vala b/src/engine/imap/FetchDataType.vala similarity index 81% rename from src/engine/imap/FetchCommand.vala rename to src/engine/imap/FetchDataType.vala index ccf413e1..f140dcc0 100644 --- a/src/engine/imap/FetchCommand.vala +++ b/src/engine/imap/FetchDataType.vala @@ -5,7 +5,7 @@ */ // TODO: Support body[section] and body.peek[section] forms -public enum Geary.Imap.FetchDataItem { +public enum Geary.Imap.FetchDataType { UID, FLAGS, INTERNALDATE, @@ -66,7 +66,7 @@ public enum Geary.Imap.FetchDataItem { } } - public static FetchDataItem decode(string value) throws ImapError { + public static FetchDataType decode(string value) throws ImapError { switch (value.down()) { case "uid": return UID; @@ -116,7 +116,7 @@ public enum Geary.Imap.FetchDataItem { return new StringParameter(to_string()); } - public static FetchDataItem from_parameter(StringParameter strparam) throws ImapError { + public static FetchDataType from_parameter(StringParameter strparam) throws ImapError { return decode(strparam.value); } @@ -152,24 +152,3 @@ public enum Geary.Imap.FetchDataItem { } } -public class Geary.Imap.FetchCommand : Command { - public const string NAME = "fetch"; - - public FetchCommand(Tag tag, string msg_span, FetchDataItem[] data_items) { - base (tag, NAME); - - add(new StringParameter(msg_span)); - - assert(data_items.length > 0); - if (data_items.length == 1) { - add(data_items[0].to_parameter()); - } else { - ListParameter data_item_list = new ListParameter(this); - foreach (FetchDataItem data_item in data_items) - data_item_list.add(data_item.to_parameter()); - - add(data_item_list); - } - } -} - diff --git a/src/engine/imap/Mailbox.vala b/src/engine/imap/Mailbox.vala index 304dabe7..976f06f1 100644 --- a/src/engine/imap/Mailbox.vala +++ b/src/engine/imap/Mailbox.vala @@ -32,7 +32,7 @@ private class Geary.Imap.MessageStreamImpl : Object, Geary.MessageStream { public async Gee.List? read(Cancellable? cancellable = null) throws Error { CommandResponse resp = yield sess.send_command_async(new FetchCommand(sess.generate_tag(), - span, { FetchDataItem.ENVELOPE }), cancellable); + span, { FetchDataType.ENVELOPE }), cancellable); if (resp.status_response.status != Status.OK) throw new ImapError.SERVER_ERROR(resp.status_response.text); @@ -41,7 +41,7 @@ private class Geary.Imap.MessageStreamImpl : Object, Geary.MessageStream { FetchResults[] results = FetchResults.decode(resp); foreach (FetchResults res in results) { - Envelope envelope = (Envelope) res.get_data(FetchDataItem.ENVELOPE); + Envelope envelope = (Envelope) res.get_data(FetchDataType.ENVELOPE); msgs.add(new Message(res.msg_num, envelope.from, envelope.subject, envelope.sent)); } diff --git a/src/engine/imap/MessageData.vala b/src/engine/imap/MessageData.vala index 7798c74f..2f5bc4d5 100644 --- a/src/engine/imap/MessageData.vala +++ b/src/engine/imap/MessageData.vala @@ -26,6 +26,12 @@ public class Geary.Imap.UID : Geary.Common.IntMessageData, Geary.Imap.MessageDat } } +public class Geary.Imap.MessageNumber : Geary.Common.IntMessageData, Geary.Imap.MessageData { + public MessageNumber(int value) { + base (value); + } +} + public class Geary.Imap.Flag { public static Flag ANSWERED = new Flag("\\answered"); public static Flag DELETED = new Flag("\\deleted"); diff --git a/src/engine/imap/ServerDataType.vala b/src/engine/imap/ServerDataType.vala new file mode 100644 index 00000000..eeaf38e6 --- /dev/null +++ b/src/engine/imap/ServerDataType.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.ServerDataType { + CAPABILITY, + EXISTS, + EXPUNGE, + FETCH, + FLAGS, + LIST, + LSUB, + RECENT, + SEARCH, + STATUS; + + public string to_string() { + switch (this) { + case CAPABILITY: + return "capability"; + + case EXISTS: + return "exists"; + + case EXPUNGE: + return "expunge"; + + case FETCH: + return "fetch"; + + case FLAGS: + return "flags"; + + case LIST: + return "list"; + + case LSUB: + return "lsub"; + + case RECENT: + return "recent"; + + case SEARCH: + return "search"; + + case STATUS: + return "status"; + + default: + assert_not_reached(); + } + } + + public static ServerDataType decode(string value) throws ImapError { + switch (value.down()) { + case "capability": + return CAPABILITY; + + case "exists": + return EXISTS; + + case "expunge": + return EXPUNGE; + + case "fetch": + return FETCH; + + case "flags": + return FLAGS; + + case "list": + return LIST; + + case "lsub": + return LSUB; + + case "recent": + return RECENT; + + case "search": + return SEARCH; + + case "status": + return STATUS; + + default: + throw new ImapError.PARSE_ERROR("\"%s\" is not a valid server data type", value); + } + } + + public StringParameter to_parameter() { + return new StringParameter(to_string()); + } + + public static ServerDataType from_parameter(StringParameter param) throws ImapError { + return decode(param.value); + } +} + diff --git a/src/engine/imap/FetchDataDecoder.vala b/src/engine/imap/decoders/FetchDataDecoder.vala similarity index 92% rename from src/engine/imap/FetchDataDecoder.vala rename to src/engine/imap/decoders/FetchDataDecoder.vala index d8284d36..2bf0cd72 100644 --- a/src/engine/imap/FetchDataDecoder.vala +++ b/src/engine/imap/decoders/FetchDataDecoder.vala @@ -9,17 +9,17 @@ * While they can be used standalone, they're intended to be used by FetchResults to process * a CommandResponse. * - * Note that FetchDataDecoders are keyed off of FetchDataItem; new implementations should add - * themselves to FetchDataItem.get_decoder(). + * Note that FetchDataDecoders are keyed off of FetchDataType; new implementations should add + * themselves to FetchDataType.get_decoder(). * * In the future FetchDataDecoder may be used to decode MessageData stored in other formats, such * as in a database. */ public abstract class Geary.Imap.FetchDataDecoder { - public FetchDataItem data_item { get; private set; } + public FetchDataType data_item { get; private set; } - public FetchDataDecoder(FetchDataItem data_item) { + public FetchDataDecoder(FetchDataType data_item) { this.data_item = data_item; } @@ -57,7 +57,7 @@ public abstract class Geary.Imap.FetchDataDecoder { public class Geary.Imap.UIDDecoder : Geary.Imap.FetchDataDecoder { public UIDDecoder() { - base (FetchDataItem.UID); + base (FetchDataType.UID); } protected override MessageData decode_string(StringParameter stringp) throws ImapError { @@ -67,7 +67,7 @@ public class Geary.Imap.UIDDecoder : Geary.Imap.FetchDataDecoder { public class Geary.Imap.FlagsDecoder : Geary.Imap.FetchDataDecoder { public FlagsDecoder() { - base (FetchDataItem.FLAGS); + base (FetchDataType.FLAGS); } protected override MessageData decode_list(ListParameter listp) throws ImapError { @@ -81,7 +81,7 @@ public class Geary.Imap.FlagsDecoder : Geary.Imap.FetchDataDecoder { public class Geary.Imap.InternalDateDecoder : Geary.Imap.FetchDataDecoder { public InternalDateDecoder() { - base (FetchDataItem.INTERNALDATE); + base (FetchDataType.INTERNALDATE); } protected override MessageData decode_string(StringParameter stringp) throws ImapError { @@ -91,7 +91,7 @@ public class Geary.Imap.InternalDateDecoder : Geary.Imap.FetchDataDecoder { public class Geary.Imap.RFC822SizeDecoder : Geary.Imap.FetchDataDecoder { public RFC822SizeDecoder() { - base (FetchDataItem.RFC822_SIZE); + base (FetchDataType.RFC822_SIZE); } protected override MessageData decode_string(StringParameter stringp) throws ImapError { @@ -101,7 +101,7 @@ public class Geary.Imap.RFC822SizeDecoder : Geary.Imap.FetchDataDecoder { public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder { public EnvelopeDecoder() { - base (FetchDataItem.ENVELOPE); + base (FetchDataType.ENVELOPE); } // TODO: This doesn't handle group lists (see Johnson, p.268) @@ -149,7 +149,7 @@ public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder { public class Geary.Imap.RFC822HeaderDecoder : Geary.Imap.FetchDataDecoder { public RFC822HeaderDecoder() { - base (FetchDataItem.RFC822_HEADER); + base (FetchDataType.RFC822_HEADER); } protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError { @@ -159,7 +159,7 @@ public class Geary.Imap.RFC822HeaderDecoder : Geary.Imap.FetchDataDecoder { public class Geary.Imap.RFC822TextDecoder : Geary.Imap.FetchDataDecoder { public RFC822TextDecoder() { - base (FetchDataItem.RFC822_TEXT); + base (FetchDataType.RFC822_TEXT); } protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError { @@ -169,7 +169,7 @@ public class Geary.Imap.RFC822TextDecoder : Geary.Imap.FetchDataDecoder { public class Geary.Imap.RFC822FullDecoder : Geary.Imap.FetchDataDecoder { public RFC822FullDecoder() { - base (FetchDataItem.RFC822); + base (FetchDataType.RFC822); } protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError { diff --git a/src/engine/imap/FetchResults.vala b/src/engine/imap/decoders/FetchResults.vala similarity index 85% rename from src/engine/imap/FetchResults.vala rename to src/engine/imap/decoders/FetchResults.vala index 184d2deb..4631559e 100644 --- a/src/engine/imap/FetchResults.vala +++ b/src/engine/imap/decoders/FetchResults.vala @@ -15,13 +15,13 @@ public class Geary.Imap.FetchResults { public int msg_num { get; private set; } - private Gee.Map map = new Gee.HashMap(); + private Gee.Map map = new Gee.HashMap(); public FetchResults(int msg_num) { this.msg_num = msg_num; } - private static FetchResults decode_data(ServerData data) throws ImapError { + public static FetchResults decode_data(ServerData data) throws ImapError { StringParameter msg_num = (StringParameter) data.get_as(1, typeof(StringParameter)); StringParameter cmd = (StringParameter) data.get_as(2, typeof(StringParameter)); ListParameter list = (ListParameter) data.get_as(3, typeof(ListParameter)); @@ -38,7 +38,7 @@ public class Geary.Imap.FetchResults { // and the structured data itself for (int ctr = 0; ctr < list.get_count(); ctr += 2) { StringParameter data_item_param = (StringParameter) list.get_as(ctr, typeof(StringParameter)); - FetchDataItem data_item = FetchDataItem.decode(data_item_param.value); + FetchDataType data_item = FetchDataType.decode(data_item_param.value); FetchDataDecoder? decoder = data_item.get_decoder(); if (decoder == null) { debug("Unable to decode fetch response for \"%s\": No decoder available", @@ -54,6 +54,8 @@ public class Geary.Imap.FetchResults { } public static FetchResults[] decode(CommandResponse response) throws ImapError { + assert(response.is_sealed()); + FetchResults[] array = new FetchResults[0]; foreach (ServerData data in response.server_data) array += decode_data(data); @@ -61,11 +63,11 @@ public class Geary.Imap.FetchResults { return array; } - public void set_data(FetchDataItem data_item, MessageData primitive) { + public void set_data(FetchDataType data_item, MessageData primitive) { map.set(data_item, primitive); } - public MessageData? get_data(FetchDataItem data_item) { + public MessageData? get_data(FetchDataType data_item) { return map.get(data_item); } diff --git a/src/engine/imap/decoders/NoopResults.vala b/src/engine/imap/decoders/NoopResults.vala new file mode 100644 index 00000000..babab9c4 --- /dev/null +++ b/src/engine/imap/decoders/NoopResults.vala @@ -0,0 +1,80 @@ +/* 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.NoopResults { + public StatusResponse status_response { get; private set; } + public Gee.List? expunged { get; private set; } + /** + * -1 if "exists" result not returned by server. + */ + public int exists { get; private set; } + public Gee.List? 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) { + this.status_response = status_response; + this.expunged = expunged; + this.exists = exists; + this.flags = flags; + this.recent = recent; + } + + public static NoopResults decode(CommandResponse response) throws ImapError { + assert(response.is_sealed()); + + Gee.List expunged = new Gee.ArrayList(); + Gee.List flags = new Gee.ArrayList(); + int exists = -1; + int recent = -1; + + foreach (ServerData data in response.server_data) { + try { + int ordinal = data.get_as_string(1).as_int().clamp(-1, int.MAX); + ServerDataType type = ServerDataType.from_parameter(data.get_as_string(2)); + + 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(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); + } + } + + return new NoopResults(response.status_response, (expunged.size > 0) ? expunged : null, + exists, (flags.size > 0) ? flags : null, recent); + } + + public bool has_exists() { + return exists >= 0; + } + + public bool has_recent() { + return recent >= 0; + } +} + diff --git a/src/tests/readmail.vala b/src/tests/readmail.vala index d89804b8..5545d3b8 100644 --- a/src/tests/readmail.vala +++ b/src/tests/readmail.vala @@ -18,13 +18,13 @@ async void async_start() { yield sess.examine_async(mailbox); Geary.Imap.FetchCommand fetch = new Geary.Imap.FetchCommand(sess.generate_tag(), - "%d".printf(msg_num), { Geary.Imap.FetchDataItem.RFC822 }); + "%d".printf(msg_num), { Geary.Imap.FetchDataType.RFC822 }); Geary.Imap.CommandResponse resp = yield sess.send_command_async(fetch); Geary.Imap.FetchResults[] results = Geary.Imap.FetchResults.decode(resp); assert(results.length == 1); Geary.RFC822.Full? full = - results[0].get_data(Geary.Imap.FetchDataItem.RFC822) as Geary.RFC822.Full; + results[0].get_data(Geary.Imap.FetchDataType.RFC822) as Geary.RFC822.Full; assert(full != null); DataInputStream dins = new DataInputStream(full.buffer.get_input_stream()); diff --git a/src/tests/watchmbox.vala b/src/tests/watchmbox.vala new file mode 100644 index 00000000..c5dffb76 --- /dev/null +++ b/src/tests/watchmbox.vala @@ -0,0 +1,62 @@ +/* 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. + */ + +MainLoop? main_loop = null; +Geary.Imap.ClientSession? sess = null; +string? user = null; +string? pass = null; +string? mailbox = null; + +void on_exists(int exists) { + stdout.printf("EXISTS: %d\n", exists); +} + +void on_expunged(Geary.Imap.MessageNumber expunged) { + stdout.printf("EXPUNGED: %d\n", expunged.value); +} + +void on_recent(int recent) { + stdout.printf("RECENT: %d\n", recent); +} + +async void async_start() { + try { + yield sess.connect_async(); + yield sess.login_async(user, pass); + yield sess.examine_async(mailbox); + + sess.unsolicited_exists.connect(on_exists); + sess.unsolicited_expunged.connect(on_expunged); + sess.unsolicitied_recent.connect(on_recent); + + sess.enable_keepalives(5); + } catch (Error err) { + debug("Error: %s", err.message); + } +} + +int main(string[] args) { + if (args.length < 4) { + stderr.printf("usage: watchmbox \n"); + + return 1; + } + + main_loop = new MainLoop(); + + user = args[1]; + pass = args[2]; + mailbox = args[3]; + + sess = new Geary.Imap.ClientSession("imap.gmail.com", 993); + async_start.begin(); + + stdout.printf("Watching %s, Ctrl+C to exit...\n", mailbox); + main_loop.run(); + + return 0; +} +