Further development building the layers of IMAP decode, connectivity, and session management. First-stab implementation of a preliminary Engine API. Tons of work to go and tons of clean-up to make what's here more robust and more efficient.
This commit is contained in:
parent
e3cab0804b
commit
6433ebfa5b
24 changed files with 997 additions and 167 deletions
23
Makefile
23
Makefile
|
|
@ -3,18 +3,29 @@ BUILD_ROOT = 1
|
|||
|
||||
VALAC := valac
|
||||
|
||||
APPS := console syntax
|
||||
APPS := console syntax lsmbox
|
||||
|
||||
ENGINE_SRC := \
|
||||
src/engine/Engine.vala \
|
||||
src/engine/Interfaces.vala \
|
||||
src/engine/Message.vala \
|
||||
src/engine/state/Machine.vala \
|
||||
src/engine/state/MachineDescriptor.vala \
|
||||
src/engine/state/Mapping.vala \
|
||||
src/engine/imap/ClientConnection.vala \
|
||||
src/engine/imap/ClientSession.vala \
|
||||
src/engine/imap/Mailbox.vala \
|
||||
src/engine/imap/Parameter.vala \
|
||||
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/Response.vala \
|
||||
src/engine/imap/StatusResponse.vala \
|
||||
src/engine/imap/ServerData.vala \
|
||||
src/engine/imap/Status.vala \
|
||||
src/engine/imap/CommandResponse.vala \
|
||||
src/engine/imap/Serializable.vala \
|
||||
src/engine/imap/Serializer.vala \
|
||||
src/engine/imap/Deserializer.vala \
|
||||
|
|
@ -27,7 +38,10 @@ CONSOLE_SRC := \
|
|||
SYNTAX_SRC := \
|
||||
src/tests/syntax.vala
|
||||
|
||||
ALL_SRC := $(ENGINE_SRC) $(CONSOLE_SRC) $(SYNTAX_SRC)
|
||||
LSMBOX_SRC := \
|
||||
src/tests/lsmbox.vala
|
||||
|
||||
ALL_SRC := $(ENGINE_SRC) $(CONSOLE_SRC) $(SYNTAX_SRC) $(LSMBOX_SRC)
|
||||
|
||||
EXTERNAL_PKGS := \
|
||||
gio-2.0 \
|
||||
|
|
@ -52,3 +66,8 @@ syntax: $(ENGINE_SRC) $(SYNTAX_SRC) Makefile
|
|||
$(ENGINE_SRC) $(SYNTAX_SRC) \
|
||||
-o $@
|
||||
|
||||
lsmbox: $(ENGINE_SRC) $(LSMBOX_SRC) Makefile
|
||||
$(VALAC) --save-temps -g $(foreach pkg,$(EXTERNAL_PKGS),--pkg=$(pkg)) \
|
||||
$(ENGINE_SRC) $(LSMBOX_SRC) \
|
||||
-o $@
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ class ImapConsole : Gtk.Window {
|
|||
private uint statusbar_ctx = 0;
|
||||
private uint statusbar_msg_id = 0;
|
||||
|
||||
private Geary.Imap.ClientSession session = new Geary.Imap.ClientSession();
|
||||
private Geary.Imap.ClientConnection? cx = null;
|
||||
|
||||
public ImapConsole() {
|
||||
|
|
@ -72,6 +71,25 @@ class ImapConsole : Gtk.Window {
|
|||
status(err.message);
|
||||
}
|
||||
|
||||
private static string[] cmdnames = {
|
||||
"noop",
|
||||
"nop",
|
||||
"capabililties",
|
||||
"caps",
|
||||
"connect",
|
||||
"disconnect",
|
||||
"login",
|
||||
"logout",
|
||||
"bye",
|
||||
"list",
|
||||
"examine",
|
||||
"fetch",
|
||||
"help",
|
||||
"exit",
|
||||
"quit",
|
||||
"gmail"
|
||||
};
|
||||
|
||||
private void exec(string input) {
|
||||
string[] lines = input.strip().split(";");
|
||||
foreach (string line in lines) {
|
||||
|
|
@ -90,6 +108,8 @@ class ImapConsole : Gtk.Window {
|
|||
|
||||
clear_status();
|
||||
|
||||
// TODO: Need to break out the command delegates into their own objects with the
|
||||
// human command-names and usage and exec()'s and such; this isn't a long-term approach
|
||||
try {
|
||||
switch (cmd) {
|
||||
case "noop":
|
||||
|
|
@ -128,6 +148,15 @@ class ImapConsole : Gtk.Window {
|
|||
examine(cmd, args);
|
||||
break;
|
||||
|
||||
case "fetch":
|
||||
fetch(cmd, args);
|
||||
break;
|
||||
|
||||
case "help":
|
||||
foreach (string cmdname in cmdnames)
|
||||
print_console_line(cmdname);
|
||||
break;
|
||||
|
||||
case "exit":
|
||||
case "quit":
|
||||
quit(cmd, args);
|
||||
|
|
@ -164,7 +193,7 @@ class ImapConsole : Gtk.Window {
|
|||
private void capabilities(string cmd, string[] args) throws Error {
|
||||
check_connected(cmd, args, 0, null);
|
||||
|
||||
cx.send_async.begin(new Geary.Imap.CapabilityCommand(session), Priority.DEFAULT, null,
|
||||
cx.send_async.begin(new Geary.Imap.CapabilityCommand(cx.generate_tag()), Priority.DEFAULT, null,
|
||||
on_capabilities);
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +209,7 @@ class ImapConsole : Gtk.Window {
|
|||
private void noop(string cmd, string[] args) throws Error {
|
||||
check_connected(cmd, args, 0, null);
|
||||
|
||||
cx.send_async.begin(new Geary.Imap.NoopCommand(session), Priority.DEFAULT, null,
|
||||
cx.send_async.begin(new Geary.Imap.NoopCommand(cx.generate_tag()), Priority.DEFAULT, null,
|
||||
on_noop);
|
||||
}
|
||||
|
||||
|
|
@ -211,9 +240,12 @@ class ImapConsole : Gtk.Window {
|
|||
status("Connected");
|
||||
|
||||
cx.sent_command.connect(on_sent_command);
|
||||
cx.received_response.connect(on_received_response);
|
||||
cx.xon();
|
||||
cx.received_status_response.connect(on_received_status_response);
|
||||
cx.received_server_data.connect(on_received_server_data);
|
||||
cx.received_bad_response.connect(on_received_bad_response);
|
||||
|
||||
// start transmission and reception
|
||||
cx.xon();
|
||||
} catch (Error err) {
|
||||
cx = null;
|
||||
|
||||
|
|
@ -234,6 +266,10 @@ class ImapConsole : Gtk.Window {
|
|||
status("Disconnected");
|
||||
|
||||
cx.sent_command.disconnect(on_sent_command);
|
||||
cx.received_status_response.disconnect(on_received_status_response);
|
||||
cx.received_server_data.connect(on_received_server_data);
|
||||
cx.received_bad_response.disconnect(on_received_bad_response);
|
||||
|
||||
cx = null;
|
||||
} catch (Error err) {
|
||||
exception(err);
|
||||
|
|
@ -244,12 +280,13 @@ class ImapConsole : Gtk.Window {
|
|||
check_connected(cmd, args, 2, "user pass");
|
||||
|
||||
status("Logging in...");
|
||||
cx.post(new Geary.Imap.LoginCommand(session, args[0], args[1]), on_logged_in);
|
||||
cx.post(new Geary.Imap.LoginCommand(cx.generate_tag(), args[0], args[1]), on_logged_in);
|
||||
}
|
||||
|
||||
private void on_logged_in(Object? source, AsyncResult result) {
|
||||
try {
|
||||
cx.finish_post(result);
|
||||
status("Login completed");
|
||||
} catch (Error err) {
|
||||
exception(err);
|
||||
}
|
||||
|
|
@ -259,7 +296,7 @@ class ImapConsole : Gtk.Window {
|
|||
check_connected(cmd, args, 0, null);
|
||||
|
||||
status("Logging out...");
|
||||
cx.post(new Geary.Imap.LogoutCommand(session), on_logout);
|
||||
cx.post(new Geary.Imap.LogoutCommand(cx.generate_tag()), on_logout);
|
||||
}
|
||||
|
||||
private void on_logout(Object? source, AsyncResult result) {
|
||||
|
|
@ -275,7 +312,7 @@ class ImapConsole : Gtk.Window {
|
|||
check_connected(cmd, args, 2, "<reference> <mailbox>");
|
||||
|
||||
status("Listing...");
|
||||
cx.post(new Geary.Imap.ListCommand.wildcarded(session, args[0], args[1]), on_list);
|
||||
cx.post(new Geary.Imap.ListCommand.wildcarded(cx.generate_tag(), args[0], args[1]), on_list);
|
||||
}
|
||||
|
||||
private void on_list(Object? source, AsyncResult result) {
|
||||
|
|
@ -291,7 +328,7 @@ class ImapConsole : Gtk.Window {
|
|||
check_connected(cmd, args, 1, "<mailbox>");
|
||||
|
||||
status("Opening %s read-only".printf(args[0]));
|
||||
cx.post(new Geary.Imap.ExamineCommand(session, args[0]), on_examine);
|
||||
cx.post(new Geary.Imap.ExamineCommand(cx.generate_tag(), args[0]), on_examine);
|
||||
}
|
||||
|
||||
private void on_examine(Object? source, AsyncResult result) {
|
||||
|
|
@ -303,19 +340,56 @@ class ImapConsole : Gtk.Window {
|
|||
}
|
||||
}
|
||||
|
||||
private void fetch(string cmd, string[] args) throws Error {
|
||||
check_connected(cmd, args, 2, "<message-span> <data-item>");
|
||||
|
||||
status("Fetching %s".printf(args[0]));
|
||||
cx.post(new Geary.Imap.FetchCommand(cx.generate_tag(), args[0],
|
||||
{ Geary.Imap.FetchDataItem.decode(args[1]) }), on_fetch);
|
||||
}
|
||||
|
||||
private void on_fetch(Object? source, AsyncResult result) {
|
||||
try {
|
||||
cx.finish_post(result);
|
||||
status("Fetched");
|
||||
} catch (Error err) {
|
||||
exception(err);
|
||||
}
|
||||
}
|
||||
|
||||
private void quit(string cmd, string[] args) throws Error {
|
||||
Gtk.main_quit();
|
||||
}
|
||||
|
||||
private void print_console_line(string text) {
|
||||
append_to_console("[C] ");
|
||||
append_to_console(text);
|
||||
append_to_console("\n");
|
||||
}
|
||||
|
||||
private void on_sent_command(Geary.Imap.Command cmd) {
|
||||
append_to_console("[L] ");
|
||||
append_to_console(cmd.to_string());
|
||||
append_to_console("\n");
|
||||
}
|
||||
|
||||
private void on_received_response(Geary.Imap.RootParameters params) {
|
||||
private void on_received_status_response(Geary.Imap.StatusResponse status_response) {
|
||||
append_to_console("[R] ");
|
||||
append_to_console(params.to_string());
|
||||
append_to_console(status_response.to_string());
|
||||
append_to_console("\n");
|
||||
}
|
||||
|
||||
private void on_received_server_data(Geary.Imap.ServerData server_data) {
|
||||
append_to_console("[D] ");
|
||||
append_to_console(server_data.to_string());
|
||||
append_to_console("\n");
|
||||
}
|
||||
|
||||
private void on_received_bad_response(Geary.Imap.RootParameters root, Geary.ImapError err) {
|
||||
append_to_console("[E] ");
|
||||
append_to_console(err.message);
|
||||
append_to_console(": ");
|
||||
append_to_console(root.to_string());
|
||||
append_to_console("\n");
|
||||
}
|
||||
|
||||
|
|
|
|||
15
src/engine/Engine.vala
Normal file
15
src/engine/Engine.vala
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* 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.Engine : Object {
|
||||
public static async Account? login(string server, string user, string pass) throws Error {
|
||||
Imap.ClientSession account = new Imap.ClientSession(server, Imap.ClientConnection.DEFAULT_PORT_TLS);
|
||||
yield account.connect_async(user, pass);
|
||||
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
18
src/engine/Interfaces.vala
Normal file
18
src/engine/Interfaces.vala
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* 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 interface Geary.Account : Object {
|
||||
public abstract async Folder open(string name, Cancellable? cancellable = null) throws Error;
|
||||
}
|
||||
|
||||
public interface Geary.Folder : Object {
|
||||
public abstract MessageStream? read(int low, int count);
|
||||
}
|
||||
|
||||
public interface Geary.MessageStream : Object {
|
||||
public abstract async Gee.List<Message>? read(Cancellable? cancellable = null) throws Error;
|
||||
}
|
||||
|
||||
24
src/engine/Message.vala
Normal file
24
src/engine/Message.vala
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* 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.Message {
|
||||
public int msg_num { get; private set; }
|
||||
public string from { get; private set; }
|
||||
public string subject { get; private set; }
|
||||
public string sent { get; private set; }
|
||||
|
||||
public Message(int msg_num, string from, string subject, string sent) {
|
||||
this.msg_num = msg_num;
|
||||
this.from = from;
|
||||
this.subject = subject;
|
||||
this.sent = sent;
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return "[%d] %s: %s (%s)".printf(msg_num, from, subject, sent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -18,6 +18,8 @@ public class Geary.Imap.ClientConnection {
|
|||
private bool flow_controlled = true;
|
||||
private Deserializer des = new Deserializer();
|
||||
private uint8[] block_buffer = new uint8[4096];
|
||||
private int tag_counter = 0;
|
||||
private char tag_prefix = 'a';
|
||||
|
||||
public virtual signal void connected() {
|
||||
}
|
||||
|
|
@ -31,7 +33,13 @@ public class Geary.Imap.ClientConnection {
|
|||
public virtual signal void sent_command(Command cmd) {
|
||||
}
|
||||
|
||||
public virtual signal void received_response(RootParameters params) {
|
||||
public virtual signal void received_status_response(StatusResponse status_response) {
|
||||
}
|
||||
|
||||
public virtual signal void received_server_data(ServerData server_data) {
|
||||
}
|
||||
|
||||
public virtual signal void received_bad_response(RootParameters root, ImapError err) {
|
||||
}
|
||||
|
||||
public virtual signal void receive_failed(Error err) {
|
||||
|
|
@ -47,23 +55,24 @@ public class Geary.Imap.ClientConnection {
|
|||
des.parameters_ready.connect(on_parameters_ready);
|
||||
}
|
||||
|
||||
private void on_parameters_ready(RootParameters params) {
|
||||
received_response(params);
|
||||
~ClientConnection() {
|
||||
// TODO: Close connection as gracefully as possible
|
||||
}
|
||||
|
||||
/*
|
||||
public void connect(Cancellable? cancellable = null) throws Error {
|
||||
if (cx != null)
|
||||
throw new IOError.EXISTS("Already connected to %s", to_string());
|
||||
// Generates a unique tag for the IMAP connection in the form of "<a-z><000-999>".
|
||||
public Tag generate_tag() {
|
||||
// watch for odometer rollover
|
||||
if (++tag_counter >= 1000) {
|
||||
tag_counter = 0;
|
||||
if (tag_prefix == 'z')
|
||||
tag_prefix = 'a';
|
||||
else
|
||||
tag_prefix++;
|
||||
}
|
||||
|
||||
cx = socket_client.connect_to_host(host_specifier, default_port, cancellable);
|
||||
iouts = new Imap.OutputStream(cx.output_stream);
|
||||
dins = new DataInputStream(cx.input_stream);
|
||||
dins.set_newline_type(DataStreamNewlineType.CR_LF);
|
||||
|
||||
connected();
|
||||
// TODO This could be optimized, but we'll leave it for now.
|
||||
return new Tag("%c%03d".printf(tag_prefix, tag_counter));
|
||||
}
|
||||
*/
|
||||
|
||||
public async void connect_async(Cancellable? cancellable = null) throws Error {
|
||||
if (cx != null)
|
||||
|
|
@ -76,23 +85,6 @@ public class Geary.Imap.ClientConnection {
|
|||
connected();
|
||||
}
|
||||
|
||||
/*
|
||||
public void disconnect(Cancellable? cancellable = null) throws Error {
|
||||
if (cx == null)
|
||||
return;
|
||||
|
||||
dins.close(cancellable);
|
||||
iouts.close(cancellable);
|
||||
cx.close(cancellable);
|
||||
|
||||
dins = null;
|
||||
iouts = null;
|
||||
cx = null;
|
||||
|
||||
disconnected();
|
||||
}
|
||||
*/
|
||||
|
||||
public async void disconnect_async(Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (cx == null)
|
||||
|
|
@ -167,6 +159,20 @@ public class Geary.Imap.ClientConnection {
|
|||
next_deserialize_step();
|
||||
}
|
||||
|
||||
private void on_parameters_ready(RootParameters root) {
|
||||
try {
|
||||
bool is_status_response;
|
||||
ServerResponse response = ServerResponse.from_server(root, out is_status_response);
|
||||
|
||||
if (is_status_response)
|
||||
received_status_response((StatusResponse) response);
|
||||
else
|
||||
received_server_data((ServerData) response);
|
||||
} catch (ImapError err) {
|
||||
received_bad_response(root, err);
|
||||
}
|
||||
}
|
||||
|
||||
public void xoff() throws Error {
|
||||
check_for_connection();
|
||||
|
||||
|
|
@ -180,17 +186,6 @@ public class Geary.Imap.ClientConnection {
|
|||
ins_cancellable = new Cancellable();
|
||||
}
|
||||
|
||||
/*
|
||||
public void send(Command command, Cancellable? cancellable = null) throws Error {
|
||||
if (cx == null)
|
||||
throw new IOError.CLOSED("Not connected to %s", to_string());
|
||||
|
||||
command.serialize(iouts, cancellable);
|
||||
|
||||
sent_command(command);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convenience method for send_async.begin().
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,22 +4,164 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Imap.ClientSession {
|
||||
private int tag_counter = 0;
|
||||
private char tag_prefix = 'a';
|
||||
public class Geary.Imap.ClientSession : Object, Geary.Account {
|
||||
// Need this because delegates with targets cannot be stored in ADTs.
|
||||
private class CommandCallback {
|
||||
public SourceFunc callback;
|
||||
|
||||
public CommandCallback(SourceFunc callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a unique tag for the IMAP session in the form of "<a-z><000-999>".
|
||||
public string generate_tag_value() {
|
||||
// watch for odometer rollover
|
||||
if (++tag_counter >= 1000) {
|
||||
tag_counter = 0;
|
||||
if (tag_prefix == 'z')
|
||||
tag_prefix = 'a';
|
||||
else
|
||||
tag_prefix++;
|
||||
private string server;
|
||||
private uint default_port;
|
||||
private ClientConnection? cx = null;
|
||||
private Mailbox? current_mailbox = null;
|
||||
private Gee.Queue<CommandCallback> cb_queue = new Gee.LinkedList<CommandCallback>();
|
||||
private Gee.Queue<CommandResponse> cmd_response_queue = new Gee.LinkedList<CommandResponse>();
|
||||
private CommandResponse current_cmd_response = new CommandResponse();
|
||||
private bool awaiting_connect_response = false;
|
||||
private ServerData? connect_response = null;
|
||||
|
||||
public ClientSession(string server, uint default_port) {
|
||||
this.server = server;
|
||||
this.default_port = default_port;
|
||||
}
|
||||
|
||||
public Tag? generate_tag() {
|
||||
return (cx != null) ? cx.generate_tag() : null;
|
||||
}
|
||||
|
||||
public async void connect_async(string user, string pass, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (cx != null)
|
||||
return;
|
||||
|
||||
cx = new ClientConnection(server, ClientConnection.DEFAULT_PORT_TLS);
|
||||
cx.connected.connect(on_connected);
|
||||
cx.disconnected.connect(on_disconnected);
|
||||
cx.sent_command.connect(on_sent_command);
|
||||
cx.received_status_response.connect(on_received_status_response);
|
||||
cx.received_server_data.connect(on_received_server_data);
|
||||
cx.received_bad_response.connect(on_received_bad_response);
|
||||
cx.receive_failed.connect(on_receive_failed);
|
||||
|
||||
yield cx.connect_async(cancellable);
|
||||
|
||||
// start receiving traffic from the server
|
||||
cx.xon();
|
||||
|
||||
// wait for the initial OK response from the server
|
||||
cb_queue.offer(new CommandCallback(connect_async.callback));
|
||||
awaiting_connect_response = true;
|
||||
yield;
|
||||
|
||||
assert(connect_response != null);
|
||||
Status status = Status.from_parameter(
|
||||
(StringParameter) connect_response.get_as(1, typeof(StringParameter)));
|
||||
if (status != Status.OK)
|
||||
throw new ImapError.SERVER_ERROR("Unable to connect: %s", connect_response.to_string());
|
||||
|
||||
// issue login command
|
||||
yield send_command_async(new LoginCommand(cx.generate_tag(), user, pass), cancellable);
|
||||
}
|
||||
|
||||
public async void disconnect_async(string user, string pass, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (cx == null)
|
||||
return;
|
||||
|
||||
CommandResponse response = yield send_command_async(new LogoutCommand(cx.generate_tag()),
|
||||
cancellable);
|
||||
if (response.status_response.status != Status.OK)
|
||||
message("Logout to %s failed: %s", server, response.status_response.to_string());
|
||||
|
||||
yield cx.disconnect_async(cancellable);
|
||||
|
||||
cx = null;
|
||||
}
|
||||
|
||||
public async CommandResponse send_command_async(Command cmd, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (cx == null)
|
||||
throw new ImapError.NOT_CONNECTED("Not connected to %s", server);
|
||||
|
||||
yield cx.send_async(cmd, Priority.DEFAULT, cancellable);
|
||||
|
||||
cb_queue.offer(new CommandCallback(send_command_async.callback));
|
||||
yield;
|
||||
|
||||
CommandResponse? cmd_response = cmd_response_queue.poll();
|
||||
assert(cmd_response != null);
|
||||
assert(cmd_response.is_sealed());
|
||||
assert(cmd_response.status_response.tag.equals(cmd.tag));
|
||||
|
||||
return cmd_response;
|
||||
}
|
||||
|
||||
public async Geary.Folder open(string name, Cancellable? cancellable = null) throws Error {
|
||||
if (cx == null)
|
||||
throw new ImapError.NOT_CONNECTED("Not connected to %s", server);
|
||||
|
||||
assert(current_mailbox == null);
|
||||
|
||||
yield send_command_async(new ExamineCommand(cx.generate_tag(), name), cancellable);
|
||||
current_mailbox = new Mailbox(name, this);
|
||||
|
||||
return current_mailbox;
|
||||
}
|
||||
|
||||
private void on_connected() {
|
||||
debug("Connected to %s", server);
|
||||
}
|
||||
|
||||
private void on_disconnected() {
|
||||
debug("Disconnected from %s", server);
|
||||
}
|
||||
|
||||
private void on_sent_command(Command cmd) {
|
||||
debug("Sent command %s", cmd.to_string());
|
||||
}
|
||||
|
||||
private void on_received_status_response(StatusResponse status_response) {
|
||||
assert(!current_cmd_response.is_sealed());
|
||||
current_cmd_response.seal(status_response);
|
||||
assert(current_cmd_response.is_sealed());
|
||||
|
||||
cmd_response_queue.offer(current_cmd_response);
|
||||
current_cmd_response = new CommandResponse();
|
||||
|
||||
CommandCallback? cmd_callback = cb_queue.poll();
|
||||
assert(cmd_callback != null);
|
||||
|
||||
Idle.add(cmd_callback.callback);
|
||||
}
|
||||
|
||||
private void on_received_server_data(ServerData server_data) {
|
||||
// The first response from the server is an untagged status response, which is considered
|
||||
// ServerData in our model. This captures that and treats it as such.
|
||||
if (awaiting_connect_response) {
|
||||
awaiting_connect_response = false;
|
||||
connect_response = server_data;
|
||||
|
||||
CommandCallback? cmd_callback = cb_queue.poll();
|
||||
assert(cmd_callback != null);
|
||||
|
||||
Idle.add(cmd_callback.callback);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return "%c%03d".printf(tag_prefix, tag_counter);
|
||||
current_cmd_response.add_server_data(server_data);
|
||||
}
|
||||
|
||||
private void on_received_bad_response(RootParameters root, ImapError err) {
|
||||
debug("Received bad response %s: %s", root.to_string(), err.message);
|
||||
}
|
||||
|
||||
private void on_receive_failed(Error err) {
|
||||
debug("Receive failed: %s", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public class Geary.Imap.Command : RootParameters {
|
|||
public string name { get; private set; }
|
||||
public string[]? args { get; private set; }
|
||||
|
||||
public Command(Tag tag, string name, string[]? args = null) requires (!tag.is_untagged()) {
|
||||
public Command(Tag tag, string name, string[]? args = null) requires (tag.is_tagged()) {
|
||||
this.tag = tag;
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
|
|
|
|||
46
src/engine/imap/CommandResponse.vala
Normal file
46
src/engine/imap/CommandResponse.vala
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* 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.CommandResponse {
|
||||
public Gee.List<ServerData> server_data { get; private set; }
|
||||
public StatusResponse? status_response { get; private set; }
|
||||
|
||||
public CommandResponse() {
|
||||
server_data = new Gee.ArrayList<ServerData>();
|
||||
}
|
||||
|
||||
public void add_server_data(ServerData data) {
|
||||
assert(!is_sealed());
|
||||
|
||||
server_data.add(data);
|
||||
}
|
||||
|
||||
public void seal(StatusResponse status_response) {
|
||||
assert(!is_sealed());
|
||||
|
||||
this.status_response = status_response;
|
||||
}
|
||||
|
||||
public bool is_sealed() {
|
||||
return (status_response != null);
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
foreach (ServerData data in server_data)
|
||||
builder.append("%s\n".printf(data.to_string()));
|
||||
|
||||
if (status_response != null)
|
||||
builder.append(status_response.to_string());
|
||||
|
||||
if (!is_sealed())
|
||||
builder.append("(incomplete command response)");
|
||||
|
||||
return builder.str;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7,24 +7,24 @@
|
|||
public class Geary.Imap.CapabilityCommand : Command {
|
||||
public const string NAME = "capability";
|
||||
|
||||
public CapabilityCommand(ClientSession session) {
|
||||
base (new Tag.generated(session), NAME);
|
||||
public CapabilityCommand(Tag tag) {
|
||||
base (tag, NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.NoopCommand : Command {
|
||||
public const string NAME = "noop";
|
||||
|
||||
public NoopCommand(ClientSession session) {
|
||||
base (new Tag.generated(session), NAME);
|
||||
public NoopCommand(Tag tag) {
|
||||
base (tag, NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.LoginCommand : Command {
|
||||
public const string NAME = "login";
|
||||
|
||||
public LoginCommand(ClientSession session, string user, string pass) {
|
||||
base (new Tag.generated(session), NAME, { user, pass });
|
||||
public LoginCommand(Tag tag, string user, string pass) {
|
||||
base (tag, NAME, { user, pass });
|
||||
}
|
||||
|
||||
public override string to_string() {
|
||||
|
|
@ -35,28 +35,28 @@ public class Geary.Imap.LoginCommand : Command {
|
|||
public class Geary.Imap.LogoutCommand : Command {
|
||||
public const string NAME = "logout";
|
||||
|
||||
public LogoutCommand(ClientSession session) {
|
||||
base (new Tag.generated(session), NAME);
|
||||
public LogoutCommand(Tag tag) {
|
||||
base (tag, NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.ListCommand : Command {
|
||||
public const string NAME = "list";
|
||||
|
||||
public ListCommand(ClientSession session, string mailbox) {
|
||||
base (new Tag.generated(session), NAME, { "", mailbox });
|
||||
public ListCommand(Tag tag, string mailbox) {
|
||||
base (tag, NAME, { "", mailbox });
|
||||
}
|
||||
|
||||
public ListCommand.wildcarded(ClientSession session, string reference, string mailbox) {
|
||||
base (new Tag.generated(session), NAME, { reference, mailbox });
|
||||
public ListCommand.wildcarded(Tag tag, string reference, string mailbox) {
|
||||
base (tag, NAME, { reference, mailbox });
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.ExamineCommand : Command {
|
||||
public const string NAME = "examine";
|
||||
|
||||
public ExamineCommand(ClientSession session, string mailbox) {
|
||||
base (new Tag.generated(session), NAME, { mailbox });
|
||||
public ExamineCommand(Tag tag, string mailbox) {
|
||||
base (tag, NAME, { mailbox });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ public class Geary.Imap.Deserializer {
|
|||
public Mode get_mode() {
|
||||
switch (fsm.get_state()) {
|
||||
case State.LITERAL_DATA:
|
||||
return Mode.LINE;
|
||||
return Mode.BLOCK;
|
||||
|
||||
case State.FAILED:
|
||||
return Mode.FAILED;
|
||||
|
|
@ -184,6 +184,10 @@ public class Geary.Imap.Deserializer {
|
|||
current_string = null;
|
||||
}
|
||||
|
||||
private void clear_string_parameter() {
|
||||
current_string = null;
|
||||
}
|
||||
|
||||
private void save_literal_parameter() {
|
||||
if (current_literal == null)
|
||||
return;
|
||||
|
|
@ -196,8 +200,9 @@ public class Geary.Imap.Deserializer {
|
|||
current.add(param);
|
||||
}
|
||||
|
||||
private void push() {
|
||||
ListParameter child = new ListParameter(current);
|
||||
// ListParameter's parent *must* be current
|
||||
private void push(ListParameter child) {
|
||||
assert(child.get_parent() == current);
|
||||
current.add(child);
|
||||
|
||||
current = child;
|
||||
|
|
@ -206,7 +211,7 @@ public class Geary.Imap.Deserializer {
|
|||
private State pop() {
|
||||
ListParameter? parent = current.get_parent();
|
||||
if (parent == null) {
|
||||
warning("Attempt to close unopened list");
|
||||
warning("Attempt to close unopened list/response code");
|
||||
|
||||
return State.FAILED;
|
||||
}
|
||||
|
|
@ -224,7 +229,9 @@ public class Geary.Imap.Deserializer {
|
|||
}
|
||||
|
||||
if (!is_current_string_empty() || current_literal != null || literal_length_remaining > 0) {
|
||||
warning("Unfinished parameter");
|
||||
warning("Unfinished parameter: string=%s literal=%s %ld remaining",
|
||||
(!is_current_string_empty()).to_string(), (current_literal != null).to_string(),
|
||||
literal_length_remaining);
|
||||
|
||||
return State.FAILED;
|
||||
}
|
||||
|
|
@ -245,6 +252,13 @@ public class Geary.Imap.Deserializer {
|
|||
// look for opening characters to special parameter formats, otherwise jump to atom
|
||||
// handler (i.e. don't drop this character in the case of atoms)
|
||||
switch (*((unichar *) user)) {
|
||||
case '[':
|
||||
// open response code
|
||||
ResponseCode response_code = new ResponseCode(current);
|
||||
push(response_code);
|
||||
|
||||
return State.START_PARAM;
|
||||
|
||||
case '{':
|
||||
return State.LITERAL;
|
||||
|
||||
|
|
@ -253,12 +267,14 @@ public class Geary.Imap.Deserializer {
|
|||
|
||||
case '(':
|
||||
// open list
|
||||
push();
|
||||
ListParameter list = new ListParameter(current);
|
||||
push(list);
|
||||
|
||||
return State.START_PARAM;
|
||||
|
||||
case ')':
|
||||
// close list
|
||||
case ']':
|
||||
// close list or response code
|
||||
return pop();
|
||||
|
||||
default:
|
||||
|
|
@ -275,12 +291,8 @@ public class Geary.Imap.Deserializer {
|
|||
|
||||
unichar ch = *((unichar *) user);
|
||||
|
||||
// drop everything above 0x7F
|
||||
if (ch > 0x7F)
|
||||
return state;
|
||||
|
||||
// drop control characters
|
||||
if (ch.iscntrl())
|
||||
// drop everything above 0x7F and control characters
|
||||
if (ch > 0x7F || ch.iscntrl())
|
||||
return state;
|
||||
|
||||
// tags and atoms have different special characters
|
||||
|
|
@ -300,8 +312,9 @@ public class Geary.Imap.Deserializer {
|
|||
return State.START_PARAM;
|
||||
}
|
||||
|
||||
// close-parens after an atom indicates end-of-list
|
||||
if (state == State.ATOM && ch == ')') {
|
||||
// close-parens/close-square-bracket after an atom indicates end-of-list/end-of-response
|
||||
// code
|
||||
if (state == State.ATOM && (ch == ')' || ch == ']')) {
|
||||
save_string_parameter();
|
||||
|
||||
return pop();
|
||||
|
|
@ -322,12 +335,8 @@ public class Geary.Imap.Deserializer {
|
|||
private uint on_quoted_char(uint state, uint event, void *user) {
|
||||
unichar ch = *((unichar *) user);
|
||||
|
||||
// drop anything above 0x7F
|
||||
if (ch > 0x7F)
|
||||
return State.QUOTED;
|
||||
|
||||
// drop NUL, CR, and LF
|
||||
if (ch == '\0' || ch == '\r' || ch == '\n')
|
||||
// drop anything above 0x7F, NUL, CR, and LF
|
||||
if (ch > 0x7F || ch == '\0' || ch == '\r' || ch == '\n')
|
||||
return State.QUOTED;
|
||||
|
||||
// look for escaped characters
|
||||
|
|
@ -377,6 +386,8 @@ public class Geary.Imap.Deserializer {
|
|||
return State.FAILED;
|
||||
}
|
||||
|
||||
clear_string_parameter();
|
||||
|
||||
return State.LITERAL_DATA_BEGIN;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
errordomain Geary.ImapError {
|
||||
PARSE_ERROR;
|
||||
public errordomain Geary.ImapError {
|
||||
PARSE_ERROR,
|
||||
TYPE_ERROR,
|
||||
SERVER_ERROR,
|
||||
NOT_CONNECTED
|
||||
}
|
||||
|
||||
|
|
|
|||
143
src/engine/imap/FetchCommand.vala
Normal file
143
src/engine/imap/FetchCommand.vala
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/* 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.FetchDataItem {
|
||||
UID,
|
||||
FLAGS,
|
||||
INTERNALDATE,
|
||||
ENVELOPE,
|
||||
BODYSTRUCTURE,
|
||||
BODY,
|
||||
RFC822,
|
||||
RFC822_HEADER,
|
||||
RFC822_SIZE,
|
||||
RFC822_TEXT,
|
||||
FAST,
|
||||
ALL,
|
||||
FULL;
|
||||
|
||||
public string to_string() {
|
||||
switch (this) {
|
||||
case UID:
|
||||
return "uid";
|
||||
|
||||
case FLAGS:
|
||||
return "flags";
|
||||
|
||||
case INTERNALDATE:
|
||||
return "internaldate";
|
||||
|
||||
case ENVELOPE:
|
||||
return "envelope";
|
||||
|
||||
case BODYSTRUCTURE:
|
||||
return "bodystructure";
|
||||
|
||||
case BODY:
|
||||
return "body";
|
||||
|
||||
case RFC822:
|
||||
return "rfc822";
|
||||
|
||||
case RFC822_HEADER:
|
||||
return "rfc822.header";
|
||||
|
||||
case RFC822_SIZE:
|
||||
return "rfc822.size";
|
||||
|
||||
case RFC822_TEXT:
|
||||
return "rfc822.text";
|
||||
|
||||
case FAST:
|
||||
return "fast";
|
||||
|
||||
case ALL:
|
||||
return "all";
|
||||
|
||||
case FULL:
|
||||
return "full";
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public static FetchDataItem decode(string value) throws ImapError {
|
||||
switch (value.down()) {
|
||||
case "uid":
|
||||
return UID;
|
||||
|
||||
case "flags":
|
||||
return FLAGS;
|
||||
|
||||
case "internaldate":
|
||||
return INTERNALDATE;
|
||||
|
||||
case "envelope":
|
||||
return ENVELOPE;
|
||||
|
||||
case "bodystructure":
|
||||
return BODYSTRUCTURE;
|
||||
|
||||
case "body":
|
||||
return BODY;
|
||||
|
||||
case "rfc822":
|
||||
return RFC822;
|
||||
|
||||
case "rfc822.header":
|
||||
return RFC822_HEADER;
|
||||
|
||||
case "rfc822.size":
|
||||
return RFC822_SIZE;
|
||||
|
||||
case "rfc822.text":
|
||||
return RFC822_TEXT;
|
||||
|
||||
case "fast":
|
||||
return FAST;
|
||||
|
||||
case "all":
|
||||
return ALL;
|
||||
|
||||
case "full":
|
||||
return FULL;
|
||||
|
||||
default:
|
||||
throw new ImapError.PARSE_ERROR("\"%s\" is not a valid fetch-command data item", value);
|
||||
}
|
||||
}
|
||||
|
||||
public StringParameter to_parameter() {
|
||||
return new StringParameter(to_string());
|
||||
}
|
||||
|
||||
public static FetchDataItem from_parameter(StringParameter strparam) throws ImapError {
|
||||
return decode(strparam.value);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
70
src/engine/imap/Mailbox.vala
Normal file
70
src/engine/imap/Mailbox.vala
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* 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.Mailbox : Object, Geary.Folder {
|
||||
private string name;
|
||||
private ClientSession sess;
|
||||
|
||||
internal Mailbox(string name, ClientSession sess) {
|
||||
this.name = name;
|
||||
this.sess = sess;
|
||||
}
|
||||
|
||||
public MessageStream? read(int low, int count) {
|
||||
return new MessageStreamImpl(sess, low, count);
|
||||
}
|
||||
}
|
||||
|
||||
private class Geary.Imap.MessageStreamImpl : Object, Geary.MessageStream {
|
||||
private ClientSession sess;
|
||||
private string span;
|
||||
|
||||
public MessageStreamImpl(ClientSession sess, int low, int count) {
|
||||
assert(count > 0);
|
||||
|
||||
this.sess = sess;
|
||||
span = (count > 1) ? "%d:%d".printf(low, low + count - 1) : "%d".printf(low);
|
||||
}
|
||||
|
||||
public async Gee.List<Message>? read(Cancellable? cancellable = null) throws Error {
|
||||
CommandResponse resp = yield sess.send_command_async(new FetchCommand(sess.generate_tag(),
|
||||
span, { FetchDataItem.ENVELOPE }), cancellable);
|
||||
|
||||
if (resp.status_response.status != Status.OK)
|
||||
throw new ImapError.SERVER_ERROR(resp.status_response.text);
|
||||
|
||||
Gee.List<Message> msgs = new Gee.ArrayList<Message>();
|
||||
foreach (ServerData data in resp.server_data) {
|
||||
StringParameter? label = data.get(2) as StringParameter;
|
||||
if (label == null || label.value.down() != "fetch") {
|
||||
debug("Not fetch data: %s", (label == null) ? "(null)" : label.value);
|
||||
continue;
|
||||
}
|
||||
|
||||
StringParameter msg_num = (StringParameter) data.get_as(1, typeof(StringParameter));
|
||||
ListParameter envelope = (ListParameter) data.get_as(3, typeof(ListParameter));
|
||||
|
||||
ListParameter fields = (ListParameter) envelope.get_as(1, typeof(ListParameter));
|
||||
|
||||
StringParameter date = (StringParameter) fields.get_as(0, typeof(StringParameter));
|
||||
StringParameter subject = (StringParameter) fields.get_as(1, typeof(StringParameter));
|
||||
|
||||
ListParameter from_fields = (ListParameter) fields.get_as(3, typeof(ListParameter));
|
||||
ListParameter first_from = (ListParameter) from_fields.get_as(0, typeof(ListParameter));
|
||||
StringParameter from_name = (StringParameter) first_from.get_as(0, typeof(StringParameter));
|
||||
StringParameter from_mailbox = (StringParameter) first_from.get_as(2, typeof(StringParameter));
|
||||
StringParameter from_domain = (StringParameter) first_from.get_as(3, typeof(StringParameter));
|
||||
|
||||
Message msg = new Message(int.parse(msg_num.value),
|
||||
"%s <%s@%s>".printf(from_name.value, from_mailbox.value, from_domain.value),
|
||||
subject.value, date.value);
|
||||
msgs.add(msg);
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,9 +4,11 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public abstract class Geary.Imap.Parameter : Serializable {
|
||||
public abstract class Geary.Imap.Parameter : Object, Serializable {
|
||||
public abstract void serialize(Serializer ser) throws Error;
|
||||
|
||||
// to_string() returns a representation of the Parameter suitable for logging and debugging,
|
||||
// but should not be relied upon for wire or persistent representation.
|
||||
public abstract string to_string();
|
||||
}
|
||||
|
||||
|
|
@ -93,50 +95,48 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter {
|
|||
return list.size;
|
||||
}
|
||||
|
||||
public new Parameter? get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
public Parameter get_as(int index, Type type) throws ImapError {
|
||||
assert(type.is_a(typeof(Parameter)));
|
||||
|
||||
if (index >= list.size)
|
||||
throw new ImapError.TYPE_ERROR("No parameter at index %d", index);
|
||||
|
||||
Parameter param = list.get(index);
|
||||
if (!param.get_type().is_a(type))
|
||||
throw new ImapError.TYPE_ERROR("Parameter %d is not of type %s", index, type.name());
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
public Gee.List<Parameter> get_all() {
|
||||
return list.read_only_view;
|
||||
}
|
||||
|
||||
/*
|
||||
public Parameter? get_next(ref int index, Type type, bool optional) throws ImapError {
|
||||
assert(type.is_a(Parameter));
|
||||
|
||||
if (index >= list.size) {
|
||||
if (!optional)
|
||||
throw new ImapError.PARSE_ERROR;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Parameter param = list.get(index);
|
||||
if (!(typeof(param).is_a(type)) {
|
||||
if (!optional)
|
||||
throw new ImapError.PARSE_ERROR;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
return param;
|
||||
// This replaces all existing parameters with those from the supplied list
|
||||
public void copy(ListParameter src) {
|
||||
list.clear();
|
||||
list.add_all(src.get_all());
|
||||
}
|
||||
*/
|
||||
|
||||
protected string stringize_list() {
|
||||
string str = "";
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
int length = list.size;
|
||||
for (int ctr = 0; ctr < length; ctr++) {
|
||||
str += list[ctr].to_string();
|
||||
builder.append(list[ctr].to_string());
|
||||
if (ctr < (length - 1))
|
||||
str += " ";
|
||||
builder.append_c(' ');
|
||||
}
|
||||
|
||||
return str;
|
||||
return builder.str;
|
||||
}
|
||||
|
||||
public override string to_string() {
|
||||
return "%d:(%s)".printf(list.size, stringize_list());
|
||||
return "(%s)".printf(stringize_list());
|
||||
}
|
||||
|
||||
protected void serialize_list(Serializer ser) throws Error {
|
||||
|
|
@ -160,24 +160,11 @@ public class Geary.Imap.RootParameters : Geary.Imap.ListParameter {
|
|||
base (null, initial);
|
||||
}
|
||||
|
||||
/*
|
||||
public bool is_status_response() {
|
||||
if (get_count() < 2)
|
||||
return false;
|
||||
public RootParameters.clone(RootParameters root) {
|
||||
base (null);
|
||||
|
||||
StringParameter? strparam = get_all().get(1) as StringParameter;
|
||||
if (strparam == null)
|
||||
return false;
|
||||
|
||||
try {
|
||||
Status.decode(strparam.value);
|
||||
} catch (Error err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
base.copy(root);
|
||||
}
|
||||
*/
|
||||
|
||||
public override string to_string() {
|
||||
return stringize_list();
|
||||
|
|
|
|||
49
src/engine/imap/Response.vala
Normal file
49
src/engine/imap/Response.vala
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* 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.ServerResponse : RootParameters {
|
||||
public Tag tag { get; private set; }
|
||||
|
||||
public ServerResponse(Tag tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public ServerResponse.reconstitute(RootParameters root) throws ImapError {
|
||||
base.clone(root);
|
||||
|
||||
tag = new Tag.from_parameter((StringParameter) get_as(0, typeof(StringParameter)));
|
||||
}
|
||||
|
||||
// Returns true if the RootParameters represents a StatusResponse, otherwise they should be
|
||||
// treated as ServerData.
|
||||
public static ServerResponse from_server(RootParameters root, out bool is_status_response)
|
||||
throws ImapError {
|
||||
// must be at least two parameters: a tag and a status or a value
|
||||
if (root.get_count() < 2) {
|
||||
throw new ImapError.TYPE_ERROR("Too few parameters (%d) for server response",
|
||||
root.get_count());
|
||||
}
|
||||
|
||||
Tag tag = new Tag.from_parameter((StringParameter) root.get_as(0, typeof(StringParameter)));
|
||||
if (tag.is_tagged()) {
|
||||
// Attempt to decode second parameter for predefined status codes (piggyback on
|
||||
// Status.decode's exception if this is invalid)
|
||||
StringParameter? statusparam = root.get(1) as StringParameter;
|
||||
if (statusparam != null)
|
||||
Status.decode(statusparam.value);
|
||||
|
||||
// tagged and has proper status, so it's a status response
|
||||
is_status_response = true;
|
||||
|
||||
return new StatusResponse.reconstitute(root);
|
||||
}
|
||||
|
||||
is_status_response = false;
|
||||
|
||||
return new ServerData.reconstitute(root);
|
||||
}
|
||||
}
|
||||
|
||||
22
src/engine/imap/ResponseCode.vala
Normal file
22
src/engine/imap/ResponseCode.vala
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* 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.ResponseCode : Geary.Imap.ListParameter {
|
||||
public ResponseCode(ListParameter parent, Parameter? initial = null) {
|
||||
base (parent, initial);
|
||||
}
|
||||
|
||||
public override string to_string() {
|
||||
return "[%s]".printf(stringize_list());
|
||||
}
|
||||
|
||||
public override void serialize(Serializer ser) throws Error {
|
||||
ser.push_string("[");
|
||||
serialize_list(ser);
|
||||
ser.push_string("]");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -25,16 +25,6 @@ public class Geary.Imap.Serializer {
|
|||
return get_content_length() > 0;
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
public void push_nil() throws Error {
|
||||
douts.put_string("nil", null);
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
public void push_token(string str) throws Error {
|
||||
douts.put_string(str, null);
|
||||
}
|
||||
|
||||
public void push_string(string str) throws Error {
|
||||
douts.put_string(str, null);
|
||||
}
|
||||
|
|
|
|||
16
src/engine/imap/ServerData.vala
Normal file
16
src/engine/imap/ServerData.vala
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* 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.ServerData : ServerResponse {
|
||||
public ServerData(Tag tag) {
|
||||
base (tag);
|
||||
}
|
||||
|
||||
public ServerData.reconstitute(RootParameters root) throws ImapError {
|
||||
base.reconstitute(root);
|
||||
}
|
||||
}
|
||||
|
||||
70
src/engine/imap/Status.vala
Normal file
70
src/engine/imap/Status.vala
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* 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.Status {
|
||||
OK,
|
||||
NO,
|
||||
BAD,
|
||||
PREAUTH,
|
||||
BYE;
|
||||
|
||||
public string to_string() {
|
||||
switch (this) {
|
||||
case OK:
|
||||
return "ok";
|
||||
|
||||
case NO:
|
||||
return "no";
|
||||
|
||||
case BAD:
|
||||
return "bad";
|
||||
|
||||
case PREAUTH:
|
||||
return "preauth";
|
||||
|
||||
case BYE:
|
||||
return "bye";
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public static Status decode(string value) throws ImapError {
|
||||
switch (value.down()) {
|
||||
case "ok":
|
||||
return OK;
|
||||
|
||||
case "no":
|
||||
return NO;
|
||||
|
||||
case "bad":
|
||||
return BAD;
|
||||
|
||||
case "preauth":
|
||||
return PREAUTH;
|
||||
|
||||
case "bye":
|
||||
return BYE;
|
||||
|
||||
default:
|
||||
throw new ImapError.PARSE_ERROR("Unrecognized status response \"%s\"", value);
|
||||
}
|
||||
}
|
||||
|
||||
public static Status from_parameter(StringParameter strparam) throws ImapError {
|
||||
return decode(strparam.value);
|
||||
}
|
||||
|
||||
public Parameter to_parameter() {
|
||||
return new StringParameter(to_string());
|
||||
}
|
||||
|
||||
public void serialize(Serializer ser) throws Error {
|
||||
ser.push_string(to_string());
|
||||
}
|
||||
}
|
||||
|
||||
51
src/engine/imap/StatusResponse.vala
Normal file
51
src/engine/imap/StatusResponse.vala
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* 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.StatusResponse : ServerResponse {
|
||||
public Status status { get; private set; }
|
||||
public ResponseCode? response_code { get; private set; }
|
||||
public string? text { get; private set; }
|
||||
|
||||
public StatusResponse(Tag tag, Status status, ResponseCode? response_code, string? text) {
|
||||
base (tag);
|
||||
|
||||
this.status = status;
|
||||
this.response_code = response_code;
|
||||
this.text = text;
|
||||
|
||||
add(status.to_parameter());
|
||||
if (response_code != null)
|
||||
add(response_code);
|
||||
if (text != null)
|
||||
add(new StringParameter(text));
|
||||
}
|
||||
|
||||
public StatusResponse.reconstitute(RootParameters root) throws ImapError {
|
||||
base.reconstitute(root);
|
||||
|
||||
status = Status.from_parameter((StringParameter) get_as(1, typeof(StringParameter)));
|
||||
response_code = get(2) as ResponseCode;
|
||||
text = (response_code != null) ? flatten_to_text(3) : flatten_to_text(2);
|
||||
}
|
||||
|
||||
private string? flatten_to_text(int start_index) throws ImapError {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
while (start_index < get_count()) {
|
||||
StringParameter? strparam = get(start_index) as StringParameter;
|
||||
if (strparam != null) {
|
||||
builder.append(strparam.value);
|
||||
if (start_index < (get_count() - 1))
|
||||
builder.append_c(' ');
|
||||
}
|
||||
|
||||
start_index++;
|
||||
}
|
||||
|
||||
return !is_empty_string(builder.str) ? builder.str : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5,16 +5,37 @@
|
|||
*/
|
||||
|
||||
public class Geary.Imap.Tag : StringParameter {
|
||||
public Tag.generated(ClientSession session) {
|
||||
base (session.generate_tag_value());
|
||||
}
|
||||
public const string UNTAGGED_VALUE = "*";
|
||||
|
||||
private static Tag? untagged = null;
|
||||
|
||||
public Tag(string value) {
|
||||
base (value);
|
||||
}
|
||||
|
||||
public bool is_untagged() {
|
||||
return value == "*";
|
||||
public Tag.from_parameter(StringParameter strparam) {
|
||||
base (strparam.value);
|
||||
}
|
||||
|
||||
public static Tag get_untagged() {
|
||||
if (untagged == null)
|
||||
untagged = new Tag(UNTAGGED_VALUE);
|
||||
|
||||
return untagged;
|
||||
}
|
||||
|
||||
public bool is_tagged() {
|
||||
return value != UNTAGGED_VALUE;
|
||||
}
|
||||
|
||||
public bool equals(Tag? tag) {
|
||||
if (this == tag)
|
||||
return true;
|
||||
|
||||
if (tag == null)
|
||||
return false;
|
||||
|
||||
return (this.value == tag.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class Geary.State.Machine {
|
|||
assert(event < descriptor.event_count);
|
||||
assert(state < descriptor.state_count);
|
||||
|
||||
Mapping? mapping = transitions[state, event];
|
||||
unowned Mapping? mapping = transitions[state, event];
|
||||
|
||||
Transition transition = (mapping != null) ? mapping.transition : default_transition;
|
||||
if (transition == null) {
|
||||
|
|
|
|||
64
src/tests/lsmbox.vala
Normal file
64
src/tests/lsmbox.vala
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/* 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;
|
||||
int start = 0;
|
||||
int count = 0;
|
||||
|
||||
async void async_start() {
|
||||
try {
|
||||
yield sess.connect_async(user, pass);
|
||||
|
||||
Geary.Folder folder = yield sess.open(mailbox);
|
||||
Geary.MessageStream? mstream = folder.read(start, count);
|
||||
|
||||
bool ok = false;
|
||||
if (mstream != null) {
|
||||
Gee.List<Geary.Message>? msgs = yield mstream.read();
|
||||
if (msgs != null && msgs.size > 0) {
|
||||
foreach (Geary.Message msg in msgs)
|
||||
stdout.printf("%s\n", msg.to_string());
|
||||
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
debug("Unable to examine mailbox %s", mailbox);
|
||||
} catch (Error err) {
|
||||
debug("Error: %s", err.message);
|
||||
}
|
||||
|
||||
main_loop.quit();
|
||||
}
|
||||
|
||||
int main(string[] args) {
|
||||
if (args.length < 6) {
|
||||
stderr.printf("usage: lsmbox <user> <pass> <mailbox> <start #> <count>\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
main_loop = new MainLoop();
|
||||
|
||||
user = args[1];
|
||||
pass = args[2];
|
||||
mailbox = args[3];
|
||||
start = int.parse(args[4]);
|
||||
count = int.parse(args[5]);
|
||||
|
||||
sess = new Geary.Imap.ClientSession("imap.gmail.com", 993);
|
||||
async_start.begin();
|
||||
|
||||
main_loop.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue