Merge branch 'mjog/imap-connection-fixes' into 'mainline'
IMAP connection fixes See merge request GNOME/geary!479
This commit is contained in:
commit
297a59ca80
28 changed files with 1440 additions and 757 deletions
|
|
@ -214,6 +214,7 @@ src/engine/db/db-versioned-database.vala
|
|||
src/engine/imap/imap.vala
|
||||
src/engine/imap/imap-error.vala
|
||||
src/engine/imap/api/imap-account-session.vala
|
||||
src/engine/imap/api/imap-capabilities.vala
|
||||
src/engine/imap/api/imap-client-service.vala
|
||||
src/engine/imap/api/imap-email-flags.vala
|
||||
src/engine/imap/api/imap-email-properties.vala
|
||||
|
|
@ -325,7 +326,6 @@ src/engine/imap/parameter/imap-quoted-string-parameter.vala
|
|||
src/engine/imap/parameter/imap-root-parameters.vala
|
||||
src/engine/imap/parameter/imap-string-parameter.vala
|
||||
src/engine/imap/parameter/imap-unquoted-string-parameter.vala
|
||||
src/engine/imap/response/imap-capabilities.vala
|
||||
src/engine/imap/response/imap-continuation-response.vala
|
||||
src/engine/imap/response/imap-fetch-data-decoder.vala
|
||||
src/engine/imap/response/imap-fetched-data.vala
|
||||
|
|
|
|||
|
|
@ -284,10 +284,12 @@ public class Geary.Engine : BaseObject {
|
|||
(security, cx) => account.untrusted_host(service, security, cx)
|
||||
);
|
||||
|
||||
Geary.Imap.ClientSession client = new Imap.ClientSession(endpoint);
|
||||
var client = new Imap.ClientSession(endpoint);
|
||||
GLib.Error? imap_err = null;
|
||||
try {
|
||||
yield client.connect_async(cancellable);
|
||||
yield client.connect_async(
|
||||
Imap.ClientSession.DEFAULT_GREETING_TIMEOUT_SEC, cancellable
|
||||
);
|
||||
} catch (GLib.Error err) {
|
||||
imap_err = err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -770,7 +770,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
this.remote_wait_semaphore.reset();
|
||||
}
|
||||
|
||||
Imap.FolderSession session = this.remote_session;
|
||||
Imap.FolderSession? session = this.remote_session;
|
||||
this.remote_session = null;
|
||||
if (session != null) {
|
||||
session.appended.disconnect(on_remote_appended);
|
||||
|
|
|
|||
|
|
@ -46,11 +46,12 @@ internal class Geary.Imap.AccountSession : Geary.Imap.SessionObject {
|
|||
public async FolderPath get_default_personal_namespace(Cancellable? cancellable)
|
||||
throws Error {
|
||||
ClientSession session = claim_session();
|
||||
if (session.personal_namespaces.is_empty) {
|
||||
Gee.List<Namespace> personal = session.get_personal_namespaces();
|
||||
if (personal.is_empty) {
|
||||
throw new ImapError.INVALID("No personal namespace found");
|
||||
}
|
||||
|
||||
Namespace ns = session.personal_namespaces[0];
|
||||
Namespace ns = personal[0];
|
||||
string prefix = ns.prefix;
|
||||
string? delim = ns.delim;
|
||||
if (delim != null && prefix.has_suffix(delim)) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Imap.Capabilities : Geary.GenericCapabilities {
|
||||
|
|
@ -13,6 +15,7 @@ public class Geary.Imap.Capabilities : Geary.GenericCapabilities {
|
|||
public const string COMPRESS = "COMPRESS";
|
||||
public const string DEFLATE_SETTING = "DEFLATE";
|
||||
public const string IDLE = "IDLE";
|
||||
public const string IMAP4REV1 = "IMAP4rev1";
|
||||
public const string NAMESPACE = "NAMESPACE";
|
||||
public const string SPECIAL_USE = "SPECIAL-USE";
|
||||
public const string STARTTLS = "STARTTLS";
|
||||
|
|
@ -22,29 +25,49 @@ public class Geary.Imap.Capabilities : Geary.GenericCapabilities {
|
|||
public const string NAME_SEPARATOR = "=";
|
||||
public const string? VALUE_SEPARATOR = null;
|
||||
|
||||
|
||||
/**
|
||||
* The version of this set of capabilities for an IMAP session.
|
||||
*
|
||||
* The capabilities that an IMAP session offers changes over time,
|
||||
* for example after login or STARTTLS. This property supports
|
||||
* detecting these changes.
|
||||
*
|
||||
* @see ClientSession.capabilities
|
||||
*/
|
||||
public int revision { get; private set; }
|
||||
|
||||
|
||||
/**
|
||||
* Creates an empty set of capabilities. revision represents the different variations of
|
||||
* capabilities that an IMAP session might offer (i.e. changes after login or STARTTLS, for
|
||||
* example).
|
||||
* Creates an empty set of capabilities.
|
||||
*/
|
||||
public Capabilities(int revision) {
|
||||
base (NAME_SEPARATOR, VALUE_SEPARATOR);
|
||||
|
||||
this.revision = revision;
|
||||
public Capabilities(StringParameter[] capabilities, int revision) {
|
||||
this.empty(revision);
|
||||
foreach (var cap in capabilities) {
|
||||
parse_and_add_capability(cap.ascii);
|
||||
}
|
||||
}
|
||||
|
||||
public bool add_parameter(StringParameter stringp) {
|
||||
return parse_and_add_capability(stringp.ascii);
|
||||
/**
|
||||
* Creates an empty set of capabilities.
|
||||
*/
|
||||
public Capabilities.empty(int revision) {
|
||||
base(NAME_SEPARATOR, VALUE_SEPARATOR);
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public override string to_string() {
|
||||
return "#%d: %s".printf(revision, base.to_string());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates an IMAP session reported support for IMAP 4rev1.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2177]]
|
||||
*/
|
||||
public bool supports_imap4rev1() {
|
||||
return has_capability(IMAP4REV1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the {@link ClientSession} reported support for IDLE.
|
||||
*
|
||||
|
|
@ -222,14 +222,17 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
|
|||
this.all_sessions.size > this.min_pool_size
|
||||
);
|
||||
|
||||
if (!this.is_running || this.discard_returned_sessions || too_many_free) {
|
||||
yield disconnect_session(session);
|
||||
} else if (yield check_session(session, false)) {
|
||||
bool free = true;
|
||||
MailboxSpecifier? mailbox = null;
|
||||
ClientSession.ProtocolState proto = session.get_protocol_state(out mailbox);
|
||||
bool disconnect = (
|
||||
too_many_free ||
|
||||
this.discard_returned_sessions ||
|
||||
!this.is_running ||
|
||||
!yield check_session(session, false)
|
||||
);
|
||||
|
||||
if (!disconnect) {
|
||||
// If the session has a mailbox selected, close it before
|
||||
// adding it back to the pool
|
||||
ClientSession.ProtocolState proto = session.get_protocol_state();
|
||||
if (proto == ClientSession.ProtocolState.SELECTED ||
|
||||
proto == ClientSession.ProtocolState.SELECTING) {
|
||||
// always close mailbox to return to authorized state
|
||||
|
|
@ -238,32 +241,20 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
|
|||
} catch (ImapError imap_error) {
|
||||
debug("Error attempting to close released session %s: %s",
|
||||
session.to_string(), imap_error.message);
|
||||
free = false;
|
||||
disconnect = true;
|
||||
}
|
||||
|
||||
// Double check the session after closing it
|
||||
switch (session.get_protocol_state(null)) {
|
||||
case AUTHORIZED:
|
||||
// This is the desired state, so all good
|
||||
break;
|
||||
|
||||
case NOT_CONNECTED:
|
||||
// No longer connected, so just drop it
|
||||
free = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (session.get_protocol_state() != AUTHORIZED) {
|
||||
// Closing it didn't leave it in the desired
|
||||
// state, so log out and drop it
|
||||
yield disconnect_session(session);
|
||||
free = false;
|
||||
break;
|
||||
// state, so drop it
|
||||
disconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (free) {
|
||||
if (!disconnect) {
|
||||
debug("Unreserving session %s", session.to_string());
|
||||
this.free_queue.send(session);
|
||||
} else {
|
||||
yield disconnect_session(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -381,7 +372,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
|
|||
/** Determines if a session is valid, disposing of it if not. */
|
||||
private async bool check_session(ClientSession target, bool claiming) {
|
||||
bool valid = false;
|
||||
switch (target.get_protocol_state(null)) {
|
||||
switch (target.get_protocol_state()) {
|
||||
case ClientSession.ProtocolState.AUTHORIZED:
|
||||
case ClientSession.ProtocolState.CLOSING_MAILBOX:
|
||||
valid = true;
|
||||
|
|
@ -396,15 +387,6 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
|
|||
}
|
||||
break;
|
||||
|
||||
case ClientSession.ProtocolState.NOT_CONNECTED:
|
||||
// Already disconnected, so drop it on the floor
|
||||
try {
|
||||
yield remove_session_async(target);
|
||||
} catch (Error err) {
|
||||
debug("Error removing unconnected session: %s", err.message);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
yield disconnect_session(target);
|
||||
break;
|
||||
|
|
@ -447,11 +429,13 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
|
|||
|
||||
ClientSession new_session = new ClientSession(remote);
|
||||
new_session.set_logging_parent(this);
|
||||
yield new_session.connect_async(cancellable);
|
||||
yield new_session.connect_async(
|
||||
ClientSession.DEFAULT_GREETING_TIMEOUT_SEC, cancellable
|
||||
);
|
||||
|
||||
try {
|
||||
yield new_session.initiate_session_async(login, cancellable);
|
||||
} catch (Error err) {
|
||||
} catch (GLib.Error err) {
|
||||
// need to disconnect before throwing error ... don't
|
||||
// honor Cancellable here, it's important to disconnect
|
||||
// the client before dropping the ref
|
||||
|
|
@ -504,44 +488,43 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
|
|||
}
|
||||
|
||||
private async void disconnect_session(ClientSession session) {
|
||||
debug("Logging out session: %s", session.to_string());
|
||||
|
||||
// Log out before removing the session since close() only
|
||||
// hangs around until all sessions have been removed before
|
||||
// exiting.
|
||||
try {
|
||||
yield session.logout_async(this.close_cancellable);
|
||||
if (session.get_protocol_state() != NOT_CONNECTED) {
|
||||
debug("Logging out session: %s", session.to_string());
|
||||
// No need to remove it after logging out, the
|
||||
// disconnected handler will do that for us.
|
||||
try {
|
||||
yield session.logout_async(this.close_cancellable);
|
||||
} catch (GLib.Error err) {
|
||||
debug("Error logging out of session: %s", err.message);
|
||||
yield force_disconnect_session(session);
|
||||
}
|
||||
} else {
|
||||
yield remove_session_async(session);
|
||||
} catch (GLib.Error err) {
|
||||
debug("Error logging out of session: %s", err.message);
|
||||
yield force_disconnect_session(session);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async void force_disconnect_session(ClientSession session) {
|
||||
debug("Dropping session: %s", session.to_string());
|
||||
|
||||
try {
|
||||
yield remove_session_async(session);
|
||||
} catch (Error err) {
|
||||
debug("Error removing session: %s", err.message);
|
||||
}
|
||||
yield remove_session_async(session);
|
||||
|
||||
// Don't wait for this to finish because we don't want to
|
||||
// block claiming a new session, shutdown, etc.
|
||||
session.disconnect_async.begin(null);
|
||||
}
|
||||
|
||||
private async bool remove_session_async(ClientSession session) throws Error {
|
||||
private async bool remove_session_async(ClientSession session) {
|
||||
// Ensure the session isn't held on to, anywhere
|
||||
|
||||
this.free_queue.revoke(session);
|
||||
|
||||
bool removed = false;
|
||||
yield this.sessions_mutex.execute_locked(() => {
|
||||
removed = this.all_sessions.remove(session);
|
||||
});
|
||||
try {
|
||||
yield this.sessions_mutex.execute_locked(() => {
|
||||
removed = this.all_sessions.remove(session);
|
||||
});
|
||||
} catch (GLib.Error err) {
|
||||
debug("Error removing session: %s", err.message);
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
session.disconnected.disconnect(on_disconnected);
|
||||
|
|
@ -549,21 +532,15 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
|
|||
return removed;
|
||||
}
|
||||
|
||||
private void on_disconnected(ClientSession session, ClientSession.DisconnectReason reason) {
|
||||
private void on_disconnected(ClientSession session,
|
||||
ClientSession.DisconnectReason reason) {
|
||||
debug(
|
||||
"Session unexpected disconnect: %s: %s",
|
||||
"Session disconnected: %s: %s",
|
||||
session.to_string(), reason.to_string()
|
||||
);
|
||||
this.remove_session_async.begin(
|
||||
session,
|
||||
(obj, res) => {
|
||||
try {
|
||||
this.remove_session_async.end(res);
|
||||
} catch (Error err) {
|
||||
debug("Error removing disconnected session: %s",
|
||||
err.message);
|
||||
}
|
||||
}
|
||||
(obj, res) => { this.remove_session_async.end(res); }
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ public abstract class Geary.Imap.SessionObject : BaseObject, Logging.Source {
|
|||
}
|
||||
|
||||
private void on_disconnected(ClientSession.DisconnectReason reason) {
|
||||
debug("DISCONNECTED %s", reason.to_string());
|
||||
debug("Disconnected %s", reason.to_string());
|
||||
|
||||
close();
|
||||
disconnected(reason);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
*
|
||||
* See [[http://tools.ietf.org/html/rfc3501#section-6]]
|
||||
*/
|
||||
public class Geary.Imap.Command : BaseObject {
|
||||
public abstract class Geary.Imap.Command : BaseObject {
|
||||
|
||||
/**
|
||||
* Default timeout to wait for a server response for a command.
|
||||
|
|
@ -97,7 +97,7 @@ public class Geary.Imap.Command : BaseObject {
|
|||
*
|
||||
* @see Tag
|
||||
*/
|
||||
public Command(string name, string[]? args = null) {
|
||||
protected Command(string name, string[]? args = null) {
|
||||
this.tag = Tag.get_unassigned();
|
||||
this.name = name;
|
||||
if (args != null) {
|
||||
|
|
@ -250,7 +250,7 @@ public class Geary.Imap.Command : BaseObject {
|
|||
* cancelled, if the command timed out, or if the command's
|
||||
* response was bad.
|
||||
*/
|
||||
public async void wait_until_complete(GLib.Cancellable cancellable)
|
||||
public async void wait_until_complete(GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
yield this.complete_lock.wait_async(cancellable);
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@ public errordomain Geary.ImapError {
|
|||
|
||||
/**
|
||||
* The remote IMAP server not currently available.
|
||||
*
|
||||
* This does not indicate a network error, rather it indicates a
|
||||
* connection to the server was established but the server
|
||||
* indicated it is not currently servicing the connection.
|
||||
*/
|
||||
UNAVAILABLE
|
||||
UNAVAILABLE;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,24 +69,22 @@ public class Geary.Imap.ResponseCode : Geary.Imap.ListParameter {
|
|||
/**
|
||||
* Parses the {@link ResponseCode} into {@link Capabilities}, if possible.
|
||||
*
|
||||
* Since Capabilities are revised with various {@link ClientSession} states, this method accepts
|
||||
* a ref to an int that will be incremented after handed to the Capabilities constructor. This
|
||||
* can be used to track the revision of capabilities seen on the connection.
|
||||
*
|
||||
* @throws ImapError.INVALID if Capability was not specified.
|
||||
*/
|
||||
public Capabilities get_capabilities(ref int next_revision) throws ImapError {
|
||||
public Capabilities get_capabilities(int revision) throws ImapError {
|
||||
if (!get_response_code_type().is_value(ResponseCodeType.CAPABILITY))
|
||||
throw new ImapError.INVALID("Not CAPABILITY response code: %s", to_string());
|
||||
|
||||
Capabilities capabilities = new Capabilities(next_revision++);
|
||||
var params = new StringParameter[this.size];
|
||||
int count = 0;
|
||||
for (int ctr = 1; ctr < size; ctr++) {
|
||||
StringParameter? param = get_if_string(ctr);
|
||||
if (param != null)
|
||||
capabilities.add_parameter(param);
|
||||
if (param != null) {
|
||||
params[count++] = param;
|
||||
}
|
||||
}
|
||||
|
||||
return capabilities;
|
||||
return new Capabilities(params[0:count], revision);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -50,24 +50,22 @@ public class Geary.Imap.ServerData : ServerResponse {
|
|||
/**
|
||||
* Parses the {@link ServerData} into {@link Capabilities}, if possible.
|
||||
*
|
||||
* Since Capabilities are revised with various {@link ClientSession} states, this method accepts
|
||||
* a ref to an int that will be incremented after handed to the Capabilities constructor. This
|
||||
* can be used to track the revision of capabilities seen on the connection.
|
||||
*
|
||||
* @throws ImapError.INVALID if not a Capability.
|
||||
*/
|
||||
public Capabilities get_capabilities(ref int next_revision) throws ImapError {
|
||||
if (server_data_type != ServerDataType.CAPABILITY)
|
||||
public Capabilities get_capabilities(int revision) throws ImapError {
|
||||
if (this.server_data_type != ServerDataType.CAPABILITY)
|
||||
throw new ImapError.INVALID("Not CAPABILITY data: %s", to_string());
|
||||
|
||||
Capabilities capabilities = new Capabilities(next_revision++);
|
||||
for (int ctr = 2; ctr < size; ctr++) {
|
||||
var params = new StringParameter[this.size];
|
||||
int count = 0;
|
||||
for (int ctr = 1; ctr < size; ctr++) {
|
||||
StringParameter? param = get_if_string(ctr);
|
||||
if (param != null)
|
||||
capabilities.add_parameter(param);
|
||||
if (param != null) {
|
||||
params[count++] = param;
|
||||
}
|
||||
}
|
||||
|
||||
return capabilities;
|
||||
return new Capabilities(params[0:count], revision);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -40,12 +40,6 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
private static int next_cx_id = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This identifier is used only for debugging, to differentiate connections from one another
|
||||
* in logs and debug output.
|
||||
*/
|
||||
public int cx_id { get; private set; }
|
||||
|
||||
/**
|
||||
* Determines if the connection will use IMAP IDLE when idle.
|
||||
*
|
||||
|
|
@ -69,11 +63,10 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
private weak Logging.Source? _logging_parent = null;
|
||||
|
||||
private Geary.Endpoint endpoint;
|
||||
private SocketConnection? cx = null;
|
||||
private IOStream? ios = null;
|
||||
private Serializer? ser = null;
|
||||
private BufferedOutputStream? ser_buffer = null;
|
||||
private Deserializer? des = null;
|
||||
private int cx_id;
|
||||
private IOStream? cx = null;
|
||||
private Deserializer? deserializer = null;
|
||||
private Serializer? serializer = null;
|
||||
|
||||
private int tag_counter = 0;
|
||||
private char tag_prefix = 'a';
|
||||
|
|
@ -89,14 +82,6 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
private GLib.Cancellable? open_cancellable = null;
|
||||
|
||||
|
||||
public virtual signal void connected() {
|
||||
debug("Connected to %s", endpoint.to_string());
|
||||
}
|
||||
|
||||
public virtual signal void disconnected() {
|
||||
debug("Disconnected from %s", endpoint.to_string());
|
||||
}
|
||||
|
||||
public virtual signal void sent_command(Command cmd) {
|
||||
debug("SEND: %s", cmd.to_string());
|
||||
}
|
||||
|
|
@ -113,34 +98,14 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
debug("RECV: %s", continuation_response.to_string());
|
||||
}
|
||||
|
||||
public virtual signal void received_bytes(size_t bytes) {
|
||||
// this generates a *lot* of debug logging if one was placed here, so it's not
|
||||
}
|
||||
public signal void received_bytes(size_t bytes);
|
||||
|
||||
public virtual signal void received_bad_response(RootParameters root,
|
||||
ImapError err) {
|
||||
warning("Received bad response: %s", err.message);
|
||||
}
|
||||
public signal void received_bad_response(RootParameters root,
|
||||
ImapError err);
|
||||
|
||||
public virtual signal void received_eos() {
|
||||
debug("Received eos");
|
||||
}
|
||||
public signal void send_failure(Error err);
|
||||
|
||||
public virtual signal void send_failure(Error err) {
|
||||
warning("Send failure: %s", err.message);
|
||||
}
|
||||
|
||||
public virtual signal void receive_failure(Error err) {
|
||||
warning("Receive failure: %s", err.message);
|
||||
}
|
||||
|
||||
public virtual signal void deserialize_failure(Error err) {
|
||||
warning("Deserialize failure: %s", err.message);
|
||||
}
|
||||
|
||||
public virtual signal void close_error(Error err) {
|
||||
warning("Close error: %s", err.message);
|
||||
}
|
||||
public signal void receive_failure(GLib.Error err);
|
||||
|
||||
|
||||
public ClientConnection(
|
||||
|
|
@ -158,8 +123,9 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
/** Returns the remote address of this connection, if any. */
|
||||
public GLib.SocketAddress? get_remote_address() throws GLib.Error {
|
||||
GLib.SocketAddress? addr = null;
|
||||
if (cx != null) {
|
||||
addr = cx.get_remote_address();
|
||||
var tcp_cx = getTcpConnection();
|
||||
if (tcp_cx != null) {
|
||||
addr = tcp_cx.get_remote_address();
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
|
@ -167,8 +133,9 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
/** Returns the local address of this connection, if any. */
|
||||
public SocketAddress? get_local_address() throws GLib.Error {
|
||||
GLib.SocketAddress? addr = null;
|
||||
if (cx != null) {
|
||||
addr = cx.get_local_address();
|
||||
var tcp_cx = getTcpConnection();
|
||||
if (tcp_cx != null) {
|
||||
addr = tcp_cx.get_local_address();
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
|
@ -209,30 +176,22 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
if (this.cx != null) {
|
||||
throw new ImapError.ALREADY_CONNECTED("Client already connected");
|
||||
}
|
||||
|
||||
this.cx = yield endpoint.connect_async(cancellable);
|
||||
this.ios = cx;
|
||||
this.cx = yield this.endpoint.connect_async(cancellable);
|
||||
|
||||
this.pending_queue.clear();
|
||||
this.sent_queue.clear();
|
||||
|
||||
connected();
|
||||
|
||||
try {
|
||||
yield open_channels_async();
|
||||
} catch (Error err) {
|
||||
// if this fails, need to close connection because the caller will not call
|
||||
// disconnect_async()
|
||||
} catch (GLib.Error err) {
|
||||
// if this fails, need to close connection because the
|
||||
// caller will not call disconnect_async()
|
||||
try {
|
||||
yield cx.close_async();
|
||||
} catch (Error close_err) {
|
||||
} catch (GLib.Error close_err) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
this.cx = null;
|
||||
this.ios = null;
|
||||
|
||||
receive_failure(err);
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
|
@ -243,17 +202,14 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
}
|
||||
|
||||
public async void disconnect_async(Cancellable? cancellable = null) throws Error {
|
||||
if (cx == null)
|
||||
if (this.cx == null)
|
||||
return;
|
||||
|
||||
this.idle_timer.reset();
|
||||
|
||||
// To guard against reentrancy
|
||||
SocketConnection close_cx = cx;
|
||||
cx = null;
|
||||
|
||||
// close the Serializer and Deserializer
|
||||
yield close_channels_async(cancellable);
|
||||
GLib.IOStream old_cx = this.cx;
|
||||
this.cx = null;
|
||||
|
||||
// Cancel any pending commands
|
||||
foreach (Command pending in this.pending_queue.get_all()) {
|
||||
|
|
@ -263,20 +219,14 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
this.pending_queue.clear();
|
||||
|
||||
// close the actual streams and the connection itself
|
||||
Error? close_err = null;
|
||||
try {
|
||||
yield ios.close_async(Priority.DEFAULT, cancellable);
|
||||
yield close_cx.close_async(Priority.DEFAULT, cancellable);
|
||||
} catch (Error err) {
|
||||
close_err = err;
|
||||
} finally {
|
||||
ios = null;
|
||||
yield close_channels_async(cancellable);
|
||||
yield old_cx.close_async(Priority.DEFAULT, cancellable);
|
||||
|
||||
if (close_err != null) {
|
||||
close_error(close_err);
|
||||
}
|
||||
|
||||
disconnected();
|
||||
var tls_cx = old_cx as GLib.TlsConnection;
|
||||
if (tls_cx != null && !tls_cx.base_io_stream.is_closed()) {
|
||||
yield tls_cx.base_io_stream.close_async(
|
||||
Priority.DEFAULT, cancellable
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -300,9 +250,7 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
yield close_channels_async(cancellable);
|
||||
|
||||
// wrap connection with TLS connection
|
||||
TlsClientConnection tls_cx = yield endpoint.starttls_handshake_async(cx, cancellable);
|
||||
|
||||
ios = tls_cx;
|
||||
this.cx = yield endpoint.starttls_handshake_async(this.cx, cancellable);
|
||||
|
||||
// re-open Serializer/Deserializer with the new streams
|
||||
yield open_channels_async();
|
||||
|
|
@ -352,33 +300,39 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
this._logging_parent = parent;
|
||||
}
|
||||
|
||||
private async void open_channels_async() throws Error {
|
||||
assert(ios != null);
|
||||
assert(ser == null);
|
||||
assert(des == null);
|
||||
private GLib.TcpConnection? getTcpConnection() {
|
||||
var cx = this.cx;
|
||||
var tls_cx = cx as GLib.TlsConnection;
|
||||
if (tls_cx != null) {
|
||||
cx = tls_cx.base_io_stream;
|
||||
}
|
||||
return cx as TcpConnection;
|
||||
}
|
||||
|
||||
private async void open_channels_async() throws Error {
|
||||
this.open_cancellable = new GLib.Cancellable();
|
||||
|
||||
// Not buffering the Deserializer because it uses a DataInputStream, which is buffered
|
||||
ser_buffer = new BufferedOutputStream(ios.output_stream);
|
||||
ser_buffer.set_close_base_stream(false);
|
||||
|
||||
// Use ClientConnection cx_id for debugging aid with Serializer/Deserializer
|
||||
string id = "%04d".printf(cx_id);
|
||||
ser = new Serializer(id, ser_buffer);
|
||||
des = new Deserializer(id, ios.input_stream);
|
||||
|
||||
des.parameters_ready.connect(on_parameters_ready);
|
||||
des.bytes_received.connect(on_bytes_received);
|
||||
des.receive_failure.connect(on_receive_failure);
|
||||
des.deserialize_failure.connect(on_deserialize_failure);
|
||||
des.eos.connect(on_eos);
|
||||
var serializer_buffer = new GLib.BufferedOutputStream(
|
||||
this.cx.output_stream
|
||||
);
|
||||
serializer_buffer.set_close_base_stream(false);
|
||||
this.serializer = new Serializer(serializer_buffer);
|
||||
|
||||
// Not buffering the Deserializer because it uses a
|
||||
// DataInputStream, which is already buffered
|
||||
this.deserializer = new Deserializer(id, this.cx.input_stream);
|
||||
this.deserializer.bytes_received.connect(on_bytes_received);
|
||||
this.deserializer.deserialize_failure.connect(on_deserialize_failure);
|
||||
this.deserializer.end_of_stream.connect(on_eos);
|
||||
this.deserializer.parameters_ready.connect(on_parameters_ready);
|
||||
this.deserializer.receive_failure.connect(on_receive_failure);
|
||||
yield this.deserializer.start_async();
|
||||
|
||||
// Start this running in the "background", it will stop when
|
||||
// open_cancellable is cancelled
|
||||
this.send_loop.begin();
|
||||
|
||||
yield des.start_async();
|
||||
}
|
||||
|
||||
/** Disconnect and deallocates the Serializer and Deserializer. */
|
||||
|
|
@ -392,26 +346,21 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
}
|
||||
this.sent_queue.clear();
|
||||
|
||||
// disconnect from Deserializer before yielding to stop it
|
||||
if (des != null) {
|
||||
des.parameters_ready.disconnect(on_parameters_ready);
|
||||
des.bytes_received.disconnect(on_bytes_received);
|
||||
des.receive_failure.disconnect(on_receive_failure);
|
||||
des.deserialize_failure.disconnect(on_deserialize_failure);
|
||||
des.eos.disconnect(on_eos);
|
||||
|
||||
yield des.stop_async();
|
||||
if (this.serializer != null) {
|
||||
yield this.serializer.close_stream(cancellable);
|
||||
this.serializer = null;
|
||||
}
|
||||
des = null;
|
||||
ser = null;
|
||||
// Close the Serializer's buffered stream after it as been
|
||||
// deallocated so it can't possibly write to the stream again,
|
||||
// and so the stream's async thread doesn't attempt to flush
|
||||
// its buffers from its finaliser at some later unspecified
|
||||
// point, possibly writing to an invalid underlying stream.
|
||||
if (ser_buffer != null) {
|
||||
yield ser_buffer.close_async(GLib.Priority.DEFAULT, cancellable);
|
||||
ser_buffer = null;
|
||||
|
||||
var deserializer = this.deserializer;
|
||||
if (deserializer != null) {
|
||||
deserializer.bytes_received.disconnect(on_bytes_received);
|
||||
deserializer.deserialize_failure.disconnect(on_deserialize_failure);
|
||||
deserializer.end_of_stream.disconnect(on_eos);
|
||||
deserializer.parameters_ready.disconnect(on_parameters_ready);
|
||||
deserializer.receive_failure.disconnect(on_receive_failure);
|
||||
|
||||
yield deserializer.stop_async();
|
||||
this.deserializer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -454,7 +403,7 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
// Check the queue is still empty after sending the
|
||||
// command, since that might have changed.
|
||||
if (this.pending_queue.is_empty) {
|
||||
yield this.ser.flush_stream(cancellable);
|
||||
yield this.serializer.flush_stream(cancellable);
|
||||
}
|
||||
} catch (GLib.Error err) {
|
||||
if (!(err is GLib.IOError.CANCELLED)) {
|
||||
|
|
@ -482,12 +431,13 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
|
||||
// Set timeout per session policy
|
||||
command.response_timeout = this.command_timeout;
|
||||
command.response_timed_out.connect(on_command_timeout);
|
||||
|
||||
this.current_command = command;
|
||||
this.sent_queue.add(command);
|
||||
yield command.send(this.ser, cancellable);
|
||||
yield command.send(this.serializer, cancellable);
|
||||
sent_command(command);
|
||||
yield command.send_wait(this.ser, cancellable);
|
||||
yield command.send_wait(this.serializer, cancellable);
|
||||
} catch (GLib.Error err) {
|
||||
ser_error = err;
|
||||
}
|
||||
|
|
@ -586,32 +536,29 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source {
|
|||
received_bytes(bytes);
|
||||
}
|
||||
|
||||
private void on_eos() {
|
||||
receive_failure(
|
||||
new ImapError.NOT_CONNECTED(
|
||||
"End of stream reading from %s", to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void on_receive_failure(Error err) {
|
||||
receive_failure(err);
|
||||
}
|
||||
|
||||
private void on_deserialize_failure() {
|
||||
deserialize_failure(
|
||||
receive_failure(
|
||||
new ImapError.PARSE_ERROR(
|
||||
"Unable to deserialize from %s", to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void on_eos() {
|
||||
received_eos();
|
||||
}
|
||||
|
||||
private void on_command_timeout(Command command) {
|
||||
this.sent_queue.remove(command);
|
||||
command.response_timed_out.disconnect(on_command_timeout);
|
||||
|
||||
// turn off graceful disconnect ... if the connection is hung,
|
||||
// don't want to be stalled trying to flush the pipe
|
||||
TcpConnection? tcp_cx = cx as TcpConnection;
|
||||
if (tcp_cx != null)
|
||||
tcp_cx.set_graceful_disconnect(false);
|
||||
|
||||
receive_failure(
|
||||
new ImapError.TIMED_OUT(
|
||||
"No response to command after %u seconds: %s",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,9 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -72,7 +74,7 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
state_to_string, event_to_string);
|
||||
|
||||
private string identifier;
|
||||
private DataInputStream dins;
|
||||
private DataInputStream input;
|
||||
private Geary.State.Machine fsm;
|
||||
|
||||
private ListParameter context;
|
||||
|
|
@ -81,7 +83,6 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
|
||||
private Cancellable? cancellable = null;
|
||||
private Nonblocking.Semaphore closed_semaphore = new Nonblocking.Semaphore();
|
||||
private Geary.Stream.MidstreamConverter midstream = new Geary.Stream.MidstreamConverter("Deserializer");
|
||||
private StringBuilder? current_string = null;
|
||||
private size_t literal_length_remaining = 0;
|
||||
private Geary.Memory.GrowableBuffer? block_buffer = null;
|
||||
|
|
@ -116,37 +117,37 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
*/
|
||||
public signal void bytes_received(size_t bytes);
|
||||
|
||||
/**
|
||||
* Fired when the underlying InputStream is closed, whether due to normal EOS or input error.
|
||||
*
|
||||
* @see receive_failure
|
||||
*/
|
||||
public signal void eos();
|
||||
|
||||
/**
|
||||
* Fired when a syntax error has occurred.
|
||||
*
|
||||
* This generally means the data looks like garbage and further deserialization is unlikely
|
||||
* or impossible.
|
||||
* This generally means the data looks like garbage and further
|
||||
* deserialization is unlikely or impossible.
|
||||
*/
|
||||
public signal void deserialize_failure();
|
||||
|
||||
/**
|
||||
* Fired when an Error is trapped on the input stream.
|
||||
*
|
||||
* This is nonrecoverable and means the stream should be closed and this Deserializer destroyed.
|
||||
* This is nonrecoverable and means the stream should be closed
|
||||
* and this Deserializer destroyed.
|
||||
*/
|
||||
public signal void receive_failure(Error err);
|
||||
public signal void receive_failure(GLib.Error err);
|
||||
|
||||
/**
|
||||
* Fired when the underlying InputStream is closed.
|
||||
*
|
||||
* This is nonrecoverable and means the stream should be closed
|
||||
* and this Deserializer destroyed.
|
||||
*/
|
||||
public signal void end_of_stream();
|
||||
|
||||
|
||||
public Deserializer(string identifier, InputStream ins) {
|
||||
public Deserializer(string identifier, GLib.InputStream input) {
|
||||
this.identifier = identifier;
|
||||
|
||||
ConverterInputStream cins = new ConverterInputStream(ins, midstream);
|
||||
cins.set_close_base_stream(false);
|
||||
dins = new DataInputStream(cins);
|
||||
dins.set_newline_type(DataStreamNewlineType.CR_LF);
|
||||
dins.set_close_base_stream(false);
|
||||
this.input = new GLib.DataInputStream(input);
|
||||
this.input.set_close_base_stream(false);
|
||||
this.input.set_newline_type(CR_LF);
|
||||
|
||||
Geary.State.Mapping[] mappings = {
|
||||
new Geary.State.Mapping(State.TAG, Event.CHAR, on_tag_char),
|
||||
|
|
@ -210,15 +211,6 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
reset_params();
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a custom Converter into the input stream.
|
||||
*
|
||||
* Can be used for decompression, decryption, and so on.
|
||||
*/
|
||||
public bool install_converter(Converter converter) {
|
||||
return midstream.install(converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin deserializing IMAP responses from the input stream.
|
||||
*
|
||||
|
|
@ -252,6 +244,7 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
|
||||
// wait for outstanding I/O to exit
|
||||
yield closed_semaphore.wait_async();
|
||||
yield this.input.close_async(GLib.Priority.DEFAULT, null);
|
||||
Logging.debug(Logging.Flag.DESERIALIZER, "[%s] Deserializer closed", to_string());
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +272,9 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
private void next_deserialize_step() {
|
||||
switch (get_mode()) {
|
||||
case Mode.LINE:
|
||||
dins.read_line_async.begin(ins_priority, cancellable, on_read_line);
|
||||
this.input.read_line_async.begin(
|
||||
ins_priority, cancellable, on_read_line
|
||||
);
|
||||
break;
|
||||
|
||||
case Mode.BLOCK:
|
||||
|
|
@ -293,7 +288,9 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
current_buffer = block_buffer.allocate(
|
||||
size_t.min(MAX_BLOCK_READ_SIZE, literal_length_remaining));
|
||||
|
||||
dins.read_async.begin(current_buffer, ins_priority, cancellable, on_read_block);
|
||||
this.input.read_async.begin(
|
||||
current_buffer, ins_priority, cancellable, on_read_block
|
||||
);
|
||||
break;
|
||||
|
||||
case Mode.FAILED:
|
||||
|
|
@ -309,7 +306,9 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
private void on_read_line(Object? source, AsyncResult result) {
|
||||
try {
|
||||
size_t bytes_read;
|
||||
string? line = dins.read_line_async.end(result, out bytes_read);
|
||||
string? line = this.input.read_line_async.end(
|
||||
result, out bytes_read
|
||||
);
|
||||
if (line == null) {
|
||||
Logging.debug(Logging.Flag.DESERIALIZER, "[%s] line EOS", to_string());
|
||||
|
||||
|
|
@ -333,7 +332,7 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
try {
|
||||
// Zero-byte literals are legal (see note in next_deserialize_step()), so EOS only
|
||||
// happens when actually pulling data
|
||||
size_t bytes_read = dins.read_async.end(result);
|
||||
size_t bytes_read = this.input.read_async.end(result);
|
||||
if (bytes_read == 0 && literal_length_remaining > 0) {
|
||||
Logging.debug(Logging.Flag.DESERIALIZER, "[%s] block EOS", to_string());
|
||||
|
||||
|
|
@ -816,8 +815,8 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
flush_params();
|
||||
|
||||
// always signal as closed and notify subscribers
|
||||
closed_semaphore.blind_notify();
|
||||
eos();
|
||||
this.closed_semaphore.blind_notify();
|
||||
end_of_stream();
|
||||
|
||||
return State.CLOSED;
|
||||
}
|
||||
|
|
@ -833,9 +832,7 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
}
|
||||
|
||||
// always signal as closed and notify
|
||||
closed_semaphore.blind_notify();
|
||||
eos();
|
||||
|
||||
this.closed_semaphore.blind_notify();
|
||||
return State.CLOSED;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +1,35 @@
|
|||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
* Copyright 2018 Michael Gratton <mike@vee.net>
|
||||
* Copyright © 2016 Software Freedom Conservancy Inc.
|
||||
* Copyright © 2018, 2020 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Writes IMAP protocol strings to a supplied output stream.
|
||||
* Writes IMAP protocol strings to the supplied output stream.
|
||||
*
|
||||
* This class uses a {@link GLib.DataOutputStream} for writing strings
|
||||
* to the given stream. Since that does not support asynchronous
|
||||
* writes, it is highly desirable that the stream passed to this class
|
||||
* is a {@link GLib.BufferedOutputStream}, or some other type that
|
||||
* uses a memory buffer large enough to write a typical command
|
||||
* completely without causing disk or network I/O.
|
||||
* Since most IMAP commands are small (with the exception of literal
|
||||
* data) this class writes directly, synchronously to the given
|
||||
* stream. Thus it is highly desirable that the stream passed to the
|
||||
* constructor is buffered, either a {@link
|
||||
* GLib.BufferedOutputStream}, or some other type that uses a memory
|
||||
* buffer large enough to write a typical command completely without
|
||||
* causing disk or network I/O.
|
||||
*
|
||||
* @see Deserializer
|
||||
*/
|
||||
public class Geary.Imap.Serializer : BaseObject {
|
||||
|
||||
private string identifier;
|
||||
private GLib.DataOutputStream output;
|
||||
|
||||
public Serializer(string identifier, GLib.OutputStream output) {
|
||||
this.identifier = identifier;
|
||||
this.output = new GLib.DataOutputStream(output);
|
||||
this.output.set_close_base_stream(false);
|
||||
private const string EOL = "\r\n";
|
||||
private const string SPACE = " ";
|
||||
|
||||
private GLib.OutputStream output;
|
||||
|
||||
|
||||
public Serializer(GLib.OutputStream output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -39,7 +42,7 @@ public class Geary.Imap.Serializer : BaseObject {
|
|||
public void push_unquoted_string(string str,
|
||||
GLib.Cancellable? cancellable = null)
|
||||
throws GLib.Error {
|
||||
this.output.put_string(str, cancellable);
|
||||
this.output.write_all(str.data, null, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -52,17 +55,19 @@ public class Geary.Imap.Serializer : BaseObject {
|
|||
public void push_quoted_string(string str,
|
||||
GLib.Cancellable? cancellable = null)
|
||||
throws GLib.Error {
|
||||
this.output.put_byte('"');
|
||||
StringBuilder buf = new StringBuilder.sized(str.length + 2);
|
||||
buf.append_c('"');
|
||||
int index = 0;
|
||||
char ch = str[index];
|
||||
while (ch != String.EOS) {
|
||||
if (ch == '"' || ch == '\\') {
|
||||
this.output.put_byte('\\');
|
||||
buf.append_c('\\');
|
||||
}
|
||||
this.output.put_byte(ch);
|
||||
buf.append_c(ch);
|
||||
ch = str[++index];
|
||||
}
|
||||
this.output.put_byte('"');
|
||||
buf.append_c('"');
|
||||
this.output.write_all(buf.data, null, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,15 +78,17 @@ public class Geary.Imap.Serializer : BaseObject {
|
|||
*/
|
||||
public void push_ascii(char ch, GLib.Cancellable? cancellable = null)
|
||||
throws GLib.Error {
|
||||
this.output.put_byte(ch, cancellable);
|
||||
// allocate array on the stack to avoid mem alloc overhead
|
||||
uint8 buf[1] = { ch };
|
||||
this.output.write_all(buf, null, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single ASCII space character.
|
||||
* Writes a ASCII space character.
|
||||
*/
|
||||
public void push_space(GLib.Cancellable? cancellable = null)
|
||||
throws GLib.Error {
|
||||
this.output.put_byte(' ', cancellable);
|
||||
this.output.write_all(SPACE.data, null, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -89,7 +96,7 @@ public class Geary.Imap.Serializer : BaseObject {
|
|||
*/
|
||||
public void push_nil(GLib.Cancellable? cancellable = null)
|
||||
throws GLib.Error {
|
||||
this.output.put_string(NilParameter.VALUE, cancellable);
|
||||
this.output.write_all(NilParameter.VALUE.data, null, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -97,7 +104,7 @@ public class Geary.Imap.Serializer : BaseObject {
|
|||
*/
|
||||
public void push_eol(GLib.Cancellable? cancellable = null)
|
||||
throws GLib.Error {
|
||||
this.output.put_string("\r\n", cancellable);
|
||||
this.output.write_all(EOL.data, null, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,14 +128,15 @@ public class Geary.Imap.Serializer : BaseObject {
|
|||
*/
|
||||
public async void flush_stream(GLib.Cancellable? cancellable = null)
|
||||
throws GLib.Error {
|
||||
yield this.output.flush_async(Priority.DEFAULT, cancellable);
|
||||
yield this.output.flush_async(GLib.Priority.DEFAULT, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation for debugging.
|
||||
* Closes the stream, ensuring a command has been sent.
|
||||
*/
|
||||
public string to_string() {
|
||||
return "ser:%s".printf(identifier);
|
||||
public async void close_stream(GLib.Cancellable? cancellable)
|
||||
throws GLib.IOError {
|
||||
yield this.output.close_async(GLib.Priority.DEFAULT, cancellable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ geary_engine_vala_sources = files(
|
|||
'imap/imap.vala',
|
||||
'imap/imap-error.vala',
|
||||
'imap/api/imap-account-session.vala',
|
||||
'imap/api/imap-capabilities.vala',
|
||||
'imap/api/imap-client-service.vala',
|
||||
'imap/api/imap-email-flags.vala',
|
||||
'imap/api/imap-email-properties.vala',
|
||||
|
|
@ -150,7 +151,6 @@ geary_engine_vala_sources = files(
|
|||
'imap/parameter/imap-root-parameters.vala',
|
||||
'imap/parameter/imap-string-parameter.vala',
|
||||
'imap/parameter/imap-unquoted-string-parameter.vala',
|
||||
'imap/response/imap-capabilities.vala',
|
||||
'imap/response/imap-continuation-response.vala',
|
||||
'imap/response/imap-fetch-data-decoder.vala',
|
||||
'imap/response/imap-fetched-data.vala',
|
||||
|
|
|
|||
|
|
@ -32,39 +32,6 @@ public class Geary.GenericCapabilities : BaseObject {
|
|||
return (map.size == 0);
|
||||
}
|
||||
|
||||
public bool parse_and_add_capability(string text) {
|
||||
string[] name_values = text.split(name_separator, 2);
|
||||
switch (name_values.length) {
|
||||
case 1:
|
||||
add_capability(name_values[0]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (value_separator == null) {
|
||||
add_capability(name_values[0], name_values[1]);
|
||||
} else {
|
||||
// break up second token for multiple values
|
||||
string[] values = name_values[1].split(value_separator);
|
||||
if (values.length <= 1) {
|
||||
add_capability(name_values[0], name_values[1]);
|
||||
} else {
|
||||
foreach (string value in values)
|
||||
add_capability(name_values[0], value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void add_capability(string name, string? setting = null) {
|
||||
map.set(name, String.is_empty(setting) ? null : setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true only if the capability was named as available by the server.
|
||||
*/
|
||||
|
|
@ -103,13 +70,6 @@ public class Geary.GenericCapabilities : BaseObject {
|
|||
return (names.size > 0) ? names : null;
|
||||
}
|
||||
|
||||
private void append(StringBuilder builder, string text) {
|
||||
if (!String.is_empty(builder.str))
|
||||
builder.append(String.is_empty(value_separator) ? " " : value_separator);
|
||||
|
||||
builder.append(text);
|
||||
}
|
||||
|
||||
public virtual string to_string() {
|
||||
Gee.Set<string>? names = get_all_names();
|
||||
if (names == null || names.size == 0)
|
||||
|
|
@ -132,5 +92,45 @@ public class Geary.GenericCapabilities : BaseObject {
|
|||
|
||||
return builder.str;
|
||||
}
|
||||
}
|
||||
|
||||
private inline void append(StringBuilder builder, string text) {
|
||||
if (!String.is_empty(builder.str))
|
||||
builder.append(String.is_empty(value_separator) ? " " : value_separator);
|
||||
|
||||
builder.append(text);
|
||||
}
|
||||
|
||||
protected bool parse_and_add_capability(string text) {
|
||||
string[] name_values = text.split(name_separator, 2);
|
||||
switch (name_values.length) {
|
||||
case 1:
|
||||
add_capability(name_values[0]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (value_separator == null) {
|
||||
add_capability(name_values[0], name_values[1]);
|
||||
} else {
|
||||
// break up second token for multiple values
|
||||
string[] values = name_values[1].split(value_separator);
|
||||
if (values.length <= 1) {
|
||||
add_capability(name_values[0], name_values[1]);
|
||||
} else {
|
||||
foreach (string value in values)
|
||||
add_capability(name_values[0], value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private inline void add_capability(string name, string? setting = null) {
|
||||
this.map.set(name, String.is_empty(setting) ? null : setting);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,80 +41,6 @@ namespace Geary.Stream {
|
|||
yield write_all_async(outs, new Memory.StringBuffer(str), cancellable);
|
||||
}
|
||||
|
||||
|
||||
public class MidstreamConverter : BaseObject, Converter {
|
||||
public uint64 total_bytes_read { get; private set; default = 0; }
|
||||
public uint64 total_bytes_written { get; private set; default = 0; }
|
||||
public uint64 converted_bytes_read { get; private set; default = 0; }
|
||||
public uint64 converted_bytes_written { get; private set; default = 0; }
|
||||
|
||||
public bool log_performance { get; set; default = false; }
|
||||
|
||||
private string name;
|
||||
private Converter? converter = null;
|
||||
|
||||
public MidstreamConverter(string name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public bool install(Converter converter) {
|
||||
if (this.converter != null)
|
||||
return false;
|
||||
|
||||
this.converter = converter;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags,
|
||||
out size_t bytes_read, out size_t bytes_written) throws Error {
|
||||
if (converter != null) {
|
||||
ConverterResult result = converter.convert(inbuf, outbuf, flags, out bytes_read, out bytes_written);
|
||||
|
||||
total_bytes_read += bytes_read;
|
||||
total_bytes_written += bytes_written;
|
||||
|
||||
converted_bytes_read += bytes_read;
|
||||
converted_bytes_written += bytes_written;
|
||||
|
||||
if (log_performance && (bytes_read > 0 || bytes_written > 0)) {
|
||||
double pct = (converted_bytes_read > converted_bytes_written)
|
||||
? (double) converted_bytes_written / (double) converted_bytes_read
|
||||
: (double) converted_bytes_read / (double) converted_bytes_written;
|
||||
debug("%s read/written: %s/%s (%lld%%)", name, converted_bytes_read.to_string(),
|
||||
converted_bytes_written.to_string(), (long) (pct * 100.0));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// passthrough
|
||||
size_t copied = size_t.min(inbuf.length, outbuf.length);
|
||||
if (copied > 0)
|
||||
GLib.Memory.copy(outbuf, inbuf, copied);
|
||||
|
||||
bytes_read = copied;
|
||||
bytes_written = copied;
|
||||
|
||||
total_bytes_read += copied;
|
||||
total_bytes_written += copied;
|
||||
|
||||
if ((flags & ConverterFlags.FLUSH) != 0)
|
||||
return ConverterResult.FLUSHED;
|
||||
|
||||
if ((flags & ConverterFlags.INPUT_AT_END) != 0)
|
||||
return ConverterResult.FINISHED;
|
||||
|
||||
return ConverterResult.CONVERTED;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (converter != null)
|
||||
converter.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adaptor from a GMime stream to a GLib OutputStream.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class Geary.ImapDB.AccountTest : TestCase {
|
|||
new Imap.UIDValidity(7),
|
||||
6 //unseen
|
||||
),
|
||||
new Imap.Capabilities(1)
|
||||
new Imap.Capabilities.empty(0)
|
||||
)
|
||||
);
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ class Geary.ImapDB.AccountTest : TestCase {
|
|||
new Imap.UIDValidity(7),
|
||||
6 //unseen
|
||||
),
|
||||
new Imap.Capabilities(1)
|
||||
new Imap.Capabilities.empty(0)
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
|||
160
test/engine/imap/transport/imap-client-connection-test.vala
Normal file
160
test/engine/imap/transport/imap-client-connection-test.vala
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
class Geary.Imap.ClientConnectionTest : TestCase {
|
||||
|
||||
|
||||
private class TestCommand : Command {
|
||||
|
||||
public TestCommand() {
|
||||
base("TEST");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private TestServer? server = null;
|
||||
|
||||
|
||||
public ClientConnectionTest() {
|
||||
base("Geary.Imap.ClientConnectionTest");
|
||||
add_test("connect_disconnect", connect_disconnect);
|
||||
if (GLib.Test.slow()) {
|
||||
add_test("idle", idle);
|
||||
add_test("command_timeout", command_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void set_up() throws GLib.Error {
|
||||
this.server = new TestServer();
|
||||
}
|
||||
|
||||
protected override void tear_down() {
|
||||
this.server.stop();
|
||||
this.server = null;
|
||||
}
|
||||
|
||||
public void connect_disconnect() throws GLib.Error {
|
||||
var test_article = new ClientConnection(new_endpoint());
|
||||
|
||||
test_article.connect_async.begin(null, this.async_complete_full);
|
||||
test_article.connect_async.end(async_result());
|
||||
|
||||
assert_non_null(test_article.get_remote_address());
|
||||
assert_non_null(test_article.get_local_address());
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
|
||||
assert_null(test_article.get_remote_address());
|
||||
assert_null(test_article.get_local_address());
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert(result.succeeded);
|
||||
}
|
||||
|
||||
public void idle() throws GLib.Error {
|
||||
this.server.add_script_line(RECEIVE_LINE, "a001 IDLE");
|
||||
this.server.add_script_line(SEND_LINE, "+ idling");
|
||||
this.server.add_script_line(RECEIVE_LINE, "DONE");
|
||||
this.server.add_script_line(SEND_LINE, "a001 OK Completed");
|
||||
this.server.add_script_line(RECEIVE_LINE, "a002 TEST");
|
||||
this.server.add_script_line(SEND_LINE, "a002 OK Looks good");
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
const int COMMAND_TIMEOUT = 1;
|
||||
const int IDLE_TIMEOUT = 1;
|
||||
|
||||
var test_article = new ClientConnection(
|
||||
new_endpoint(), COMMAND_TIMEOUT, IDLE_TIMEOUT
|
||||
);
|
||||
test_article.connect_async.begin(null, this.async_complete_full);
|
||||
test_article.connect_async.end(async_result());
|
||||
|
||||
assert_false(test_article.is_in_idle(), "Initial idle state");
|
||||
test_article.enable_idle_when_quiet(true);
|
||||
assert_false(test_article.is_in_idle(), "Post-enabled idle state");
|
||||
|
||||
// Wait for idle to kick in
|
||||
GLib.Timer timer = new GLib.Timer();
|
||||
timer.start();
|
||||
while (!test_article.is_in_idle() &&
|
||||
timer.elapsed() < IDLE_TIMEOUT * 2) {
|
||||
this.main_loop.iteration(false);
|
||||
}
|
||||
|
||||
assert_true(test_article.is_in_idle(), "Entered idle");
|
||||
|
||||
// Ensure idle outlives command timeout
|
||||
timer.start();
|
||||
while (timer.elapsed() < COMMAND_TIMEOUT * 2) {
|
||||
this.main_loop.iteration(false);
|
||||
}
|
||||
|
||||
assert_true(test_article.is_in_idle(), "Post idle command timeout");
|
||||
|
||||
var command = new TestCommand();
|
||||
test_article.send_command(command);
|
||||
command.wait_until_complete.begin(null, this.async_complete_full);
|
||||
command.wait_until_complete.end(async_result());
|
||||
|
||||
assert_false(test_article.is_in_idle(), "Post test command");
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert(result.succeeded);
|
||||
}
|
||||
|
||||
public void command_timeout() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(RECEIVE_LINE, "a001 TEST");
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
const int TIMEOUT = 2;
|
||||
|
||||
bool sent = false;
|
||||
bool recv_fail = false;
|
||||
bool timed_out = false;
|
||||
|
||||
var test_article = new ClientConnection(new_endpoint(), TIMEOUT);
|
||||
test_article.sent_command.connect(() => { sent = true; });
|
||||
test_article.receive_failure.connect(() => { recv_fail = true; });
|
||||
test_article.connect_async.begin(null, this.async_complete_full);
|
||||
test_article.connect_async.end(async_result());
|
||||
|
||||
var command = new TestCommand();
|
||||
command.response_timed_out.connect(() => { timed_out = true; });
|
||||
|
||||
test_article.send_command(command);
|
||||
|
||||
GLib.Timer timer = new GLib.Timer();
|
||||
timer.start();
|
||||
while (!timed_out && timer.elapsed() < TIMEOUT * 2) {
|
||||
this.main_loop.iteration(false);
|
||||
}
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
|
||||
assert_true(sent, "connection.sent_command");
|
||||
assert_true(recv_fail, "command.receive_failure");
|
||||
assert_true(timed_out, "command.response_timed_out");
|
||||
|
||||
debug("Waiting for server...");
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(result.succeeded);
|
||||
}
|
||||
|
||||
protected Endpoint new_endpoint() {
|
||||
return new Endpoint(this.server.get_client_address(), NONE, 10);
|
||||
}
|
||||
|
||||
}
|
||||
415
test/engine/imap/transport/imap-client-session-test.vala
Normal file
415
test/engine/imap/transport/imap-client-session-test.vala
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
class Geary.Imap.ClientSessionTest : TestCase {
|
||||
|
||||
private const uint CONNECT_TIMEOUT = 2;
|
||||
|
||||
private TestServer? server = null;
|
||||
|
||||
|
||||
public ClientSessionTest() {
|
||||
base("Geary.Imap.ClientSessionTest");
|
||||
add_test("connect_disconnect", connect_disconnect);
|
||||
add_test("connect_with_capabilities", connect_with_capabilities);
|
||||
if (GLib.Test.slow()) {
|
||||
add_test("connect_timeout", connect_timeout);
|
||||
}
|
||||
add_test("login", login);
|
||||
add_test("login_with_capabilities", login_with_capabilities);
|
||||
add_test("logout", logout);
|
||||
add_test("login_logout", login_logout);
|
||||
add_test("initiate_request_capabilities", initiate_request_capabilities);
|
||||
add_test("initiate_implicit_capabilities", initiate_implicit_capabilities);
|
||||
add_test("initiate_namespace", initiate_namespace);
|
||||
}
|
||||
|
||||
protected override void set_up() throws GLib.Error {
|
||||
this.server = new TestServer();
|
||||
}
|
||||
|
||||
protected override void tear_down() {
|
||||
this.server.stop();
|
||||
this.server = null;
|
||||
}
|
||||
|
||||
public void connect_disconnect() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(
|
||||
result.succeeded,
|
||||
result.error != null ? result.error.message : "Server result failed"
|
||||
);
|
||||
}
|
||||
|
||||
public void connect_with_capabilities() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK [CAPABILITY IMAP4rev1] localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
|
||||
assert_true(test_article.capabilities.supports_imap4rev1());
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(result.succeeded);
|
||||
}
|
||||
|
||||
public void connect_timeout() throws GLib.Error {
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
|
||||
GLib.Timer timer = new GLib.Timer();
|
||||
timer.start();
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
try {
|
||||
test_article.connect_async.end(async_result());
|
||||
assert_not_reached();
|
||||
} catch (GLib.IOError.TIMED_OUT err) {
|
||||
assert_double(timer.elapsed(), CONNECT_TIMEOUT, CONNECT_TIMEOUT * 0.5);
|
||||
}
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(result.succeeded);
|
||||
}
|
||||
|
||||
public void login_with_capabilities() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(RECEIVE_LINE, "a001 login test password");
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "a001 OK [CAPABILITY IMAP4rev1] ohhai"
|
||||
);
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
test_article.login_async.begin(
|
||||
new Credentials(PASSWORD, "test", "password"),
|
||||
null,
|
||||
this.async_complete_full
|
||||
);
|
||||
test_article.login_async.end(async_result());
|
||||
|
||||
assert_true(test_article.capabilities.supports_imap4rev1());
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(
|
||||
result.succeeded,
|
||||
result.error != null ? result.error.message : "Server result failed"
|
||||
);
|
||||
}
|
||||
|
||||
public void login() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(RECEIVE_LINE, "a001 login test password");
|
||||
this.server.add_script_line(SEND_LINE, "a001 OK ohhai");
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
|
||||
|
||||
test_article.login_async.begin(
|
||||
new Credentials(PASSWORD, "test", "password"),
|
||||
null,
|
||||
this.async_complete_full
|
||||
);
|
||||
test_article.login_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == AUTHORIZED);
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(
|
||||
result.succeeded,
|
||||
result.error != null ? result.error.message : "Server result failed"
|
||||
);
|
||||
}
|
||||
|
||||
public void logout() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(RECEIVE_LINE, "a001 logout");
|
||||
this.server.add_script_line(SEND_LINE, "* BYE fine");
|
||||
this.server.add_script_line(SEND_LINE, "a001 OK laters");
|
||||
this.server.add_script_line(DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
|
||||
|
||||
test_article.logout_async.begin(null, this.async_complete_full);
|
||||
test_article.logout_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(
|
||||
result.succeeded,
|
||||
result.error != null ? result.error.message : "Server result failed"
|
||||
);
|
||||
}
|
||||
|
||||
public void login_logout() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(RECEIVE_LINE, "a001 login test password");
|
||||
this.server.add_script_line(SEND_LINE, "a001 OK ohhai");
|
||||
this.server.add_script_line(RECEIVE_LINE, "a002 logout");
|
||||
this.server.add_script_line(SEND_LINE, "* BYE fine");
|
||||
this.server.add_script_line(SEND_LINE, "a002 OK laters");
|
||||
this.server.add_script_line(DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
|
||||
|
||||
test_article.login_async.begin(
|
||||
new Credentials(PASSWORD, "test", "password"),
|
||||
null,
|
||||
this.async_complete_full
|
||||
);
|
||||
test_article.login_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == AUTHORIZED);
|
||||
|
||||
test_article.logout_async.begin(null, this.async_complete_full);
|
||||
test_article.logout_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(
|
||||
result.succeeded,
|
||||
result.error != null ? result.error.message : "Server result failed"
|
||||
);
|
||||
}
|
||||
|
||||
public void initiate_request_capabilities() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(RECEIVE_LINE, "a001 capability");
|
||||
this.server.add_script_line(SEND_LINE, "* CAPABILITY IMAP4rev1 LOGIN");
|
||||
this.server.add_script_line(SEND_LINE, "a001 OK enjoy");
|
||||
this.server.add_script_line(RECEIVE_LINE, "a002 login test password");
|
||||
this.server.add_script_line(SEND_LINE, "a002 OK ohhai");
|
||||
this.server.add_script_line(RECEIVE_LINE, "a003 capability");
|
||||
this.server.add_script_line(SEND_LINE, "* CAPABILITY IMAP4rev1");
|
||||
this.server.add_script_line(SEND_LINE, "a003 OK thanks");
|
||||
this.server.add_script_line(RECEIVE_LINE, "a004 LIST \"\" INBOX");
|
||||
this.server.add_script_line(SEND_LINE, "* LIST (\\HasChildren) \".\" Inbox");
|
||||
this.server.add_script_line(SEND_LINE, "a004 OK there");
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
|
||||
|
||||
test_article.initiate_session_async.begin(
|
||||
new Credentials(PASSWORD, "test", "password"),
|
||||
null,
|
||||
this.async_complete_full
|
||||
);
|
||||
test_article.initiate_session_async.end(async_result());
|
||||
|
||||
assert_true(test_article.capabilities.supports_imap4rev1());
|
||||
assert_false(test_article.capabilities.has_capability("AUTH"));
|
||||
assert_int(2, test_article.capabilities.revision);
|
||||
|
||||
assert_string("Inbox", test_article.inbox.mailbox.name);
|
||||
assert_true(test_article.inbox.mailbox.is_inbox);
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(
|
||||
result.succeeded,
|
||||
result.error != null ? result.error.message : "Server result failed"
|
||||
);
|
||||
}
|
||||
|
||||
public void initiate_implicit_capabilities() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* OK [CAPABILITY IMAP4rev1 LOGIN] localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(RECEIVE_LINE, "a001 login test password");
|
||||
this.server.add_script_line(SEND_LINE, "a001 OK [CAPABILITY IMAP4rev1] ohhai");
|
||||
this.server.add_script_line(RECEIVE_LINE, "a002 LIST \"\" INBOX");
|
||||
this.server.add_script_line(SEND_LINE, "* LIST (\\HasChildren) \".\" Inbox");
|
||||
this.server.add_script_line(SEND_LINE, "a002 OK there");
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
|
||||
|
||||
test_article.initiate_session_async.begin(
|
||||
new Credentials(PASSWORD, "test", "password"),
|
||||
null,
|
||||
this.async_complete_full
|
||||
);
|
||||
test_article.initiate_session_async.end(async_result());
|
||||
|
||||
assert_true(test_article.capabilities.supports_imap4rev1());
|
||||
assert_false(test_article.capabilities.has_capability("AUTH"));
|
||||
assert_int(2, test_article.capabilities.revision);
|
||||
|
||||
assert_string("Inbox", test_article.inbox.mailbox.name);
|
||||
assert_true(test_article.inbox.mailbox.is_inbox);
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(
|
||||
result.succeeded,
|
||||
result.error != null ? result.error.message : "Server result failed"
|
||||
);
|
||||
}
|
||||
|
||||
public void initiate_namespace() throws GLib.Error {
|
||||
this.server.add_script_line(
|
||||
SEND_LINE,
|
||||
"* OK [CAPABILITY IMAP4rev1 LOGIN] localhost test server ready"
|
||||
);
|
||||
this.server.add_script_line(
|
||||
RECEIVE_LINE, "a001 login test password"
|
||||
);
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "a001 OK [CAPABILITY IMAP4rev1 NAMESPACE] ohhai"
|
||||
);
|
||||
this.server.add_script_line(
|
||||
RECEIVE_LINE, "a002 LIST \"\" INBOX"
|
||||
);
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "* LIST (\\HasChildren) \".\" Inbox"
|
||||
);
|
||||
this.server.add_script_line(
|
||||
SEND_LINE, "a002 OK there"
|
||||
);
|
||||
this.server.add_script_line(
|
||||
RECEIVE_LINE, "a003 NAMESPACE"
|
||||
);
|
||||
this.server.add_script_line(
|
||||
SEND_LINE,
|
||||
"""* NAMESPACE (("INBOX." ".")) (("user." ".")) (("shared." "."))"""
|
||||
);
|
||||
this.server.add_script_line(SEND_LINE, "a003 OK there");
|
||||
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
|
||||
|
||||
var test_article = new ClientSession(new_endpoint());
|
||||
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
|
||||
|
||||
test_article.connect_async.begin(
|
||||
CONNECT_TIMEOUT, null, this.async_complete_full
|
||||
);
|
||||
test_article.connect_async.end(async_result());
|
||||
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
|
||||
|
||||
test_article.initiate_session_async.begin(
|
||||
new Credentials(PASSWORD, "test", "password"),
|
||||
null,
|
||||
this.async_complete_full
|
||||
);
|
||||
test_article.initiate_session_async.end(async_result());
|
||||
|
||||
assert_int(1, test_article.get_personal_namespaces().size);
|
||||
assert_string(
|
||||
"INBOX.", test_article.get_personal_namespaces()[0].prefix
|
||||
);
|
||||
|
||||
assert_int(1, test_article.get_shared_namespaces().size);
|
||||
assert_string(
|
||||
"shared.", test_article.get_shared_namespaces()[0].prefix
|
||||
);
|
||||
|
||||
assert_int(1, test_article.get_other_users_namespaces().size);
|
||||
assert_string(
|
||||
"user.", test_article.get_other_users_namespaces()[0].prefix
|
||||
);
|
||||
|
||||
test_article.disconnect_async.begin(null, this.async_complete_full);
|
||||
test_article.disconnect_async.end(async_result());
|
||||
|
||||
TestServer.Result result = this.server.wait_for_script(this.main_loop);
|
||||
assert_true(
|
||||
result.succeeded,
|
||||
result.error != null ? result.error.message : "Server result failed"
|
||||
);
|
||||
}
|
||||
|
||||
protected Endpoint new_endpoint() {
|
||||
return new Endpoint(this.server.get_client_address(), NONE, 10);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -265,7 +265,7 @@ class Geary.Imap.DeserializerTest : TestCase {
|
|||
this.stream.add_data(bye.data);
|
||||
|
||||
bool eos = false;
|
||||
this.deser.eos.connect(() => { eos = true; });
|
||||
this.deser.end_of_stream.connect(() => { eos = true; });
|
||||
|
||||
this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); });
|
||||
RootParameters? message = this.process.end(async_result());
|
||||
|
|
@ -283,7 +283,7 @@ class Geary.Imap.DeserializerTest : TestCase {
|
|||
|
||||
this.deser.parameters_ready.connect((param) => { message = param; });
|
||||
this.deser.bytes_received.connect((count) => { bytes_received += count; });
|
||||
this.deser.eos.connect((param) => { eos = true; });
|
||||
this.deser.end_of_stream.connect((param) => { eos = true; });
|
||||
this.deser.deserialize_failure.connect(() => { deserialize_failure = true; });
|
||||
this.deser.receive_failure.connect((err) => { receive_failure = true;});
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class Geary.TimeoutManagerTest : TestCase {
|
|||
this.main_loop.iteration(true);
|
||||
}
|
||||
|
||||
assert_epsilon(timer.elapsed(), 1.0, SECONDS_EPSILON);
|
||||
assert_double(timer.elapsed(), 1.0, SECONDS_EPSILON);
|
||||
}
|
||||
|
||||
public void milliseconds() throws Error {
|
||||
|
|
@ -101,7 +101,7 @@ class Geary.TimeoutManagerTest : TestCase {
|
|||
this.main_loop.iteration(true);
|
||||
}
|
||||
|
||||
assert_epsilon(timer.elapsed(), 0.1, MILLISECONDS_EPSILON);
|
||||
assert_double(timer.elapsed(), 0.1, MILLISECONDS_EPSILON);
|
||||
}
|
||||
|
||||
public void repeat_forever() throws Error {
|
||||
|
|
@ -118,11 +118,7 @@ class Geary.TimeoutManagerTest : TestCase {
|
|||
}
|
||||
timer.stop();
|
||||
|
||||
assert_epsilon(timer.elapsed(), 2.0, SECONDS_EPSILON * 2);
|
||||
}
|
||||
|
||||
private inline void assert_epsilon(double actual, double expected, double epsilon) {
|
||||
assert(actual + epsilon >= expected && actual - epsilon <= expected);
|
||||
assert_double(timer.elapsed(), 2.0, SECONDS_EPSILON * 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Integration.Imap.ClientSession : TestCase {
|
|||
}
|
||||
|
||||
public override void tear_down() throws GLib.Error {
|
||||
if (this.session.get_protocol_state(null) != NOT_CONNECTED) {
|
||||
if (this.session.get_protocol_state() != NOT_CONNECTED) {
|
||||
this.session.disconnect_async.begin(null, async_complete_full);
|
||||
this.session.disconnect_async.end(async_result());
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ class Integration.Imap.ClientSession : TestCase {
|
|||
}
|
||||
|
||||
public void session_connect() throws GLib.Error {
|
||||
this.session.connect_async.begin(null, async_complete_full);
|
||||
this.session.connect_async.begin(2, null, async_complete_full);
|
||||
this.session.connect_async.end(async_result());
|
||||
|
||||
this.session.disconnect_async.begin(null, async_complete_full);
|
||||
|
|
@ -98,7 +98,7 @@ class Integration.Imap.ClientSession : TestCase {
|
|||
}
|
||||
|
||||
private void do_connect() throws GLib.Error {
|
||||
this.session.connect_async.begin(null, async_complete_full);
|
||||
this.session.connect_async.begin(5, null, async_complete_full);
|
||||
this.session.connect_async.end(async_result());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ subdir('data')
|
|||
geary_test_lib_sources = [
|
||||
'mock-object.vala',
|
||||
'test-case.vala',
|
||||
'test-server.vala',
|
||||
]
|
||||
|
||||
geary_test_engine_sources = [
|
||||
|
|
@ -41,6 +42,8 @@ geary_test_engine_sources = [
|
|||
'engine/imap/message/imap-mailbox-specifier-test.vala',
|
||||
'engine/imap/parameter/imap-list-parameter-test.vala',
|
||||
'engine/imap/response/imap-namespace-response-test.vala',
|
||||
'engine/imap/transport/imap-client-connection-test.vala',
|
||||
'engine/imap/transport/imap-client-session-test.vala',
|
||||
'engine/imap/transport/imap-deserializer-test.vala',
|
||||
'engine/imap-db/imap-db-account-test.vala',
|
||||
'engine/imap-db/imap-db-attachment-test.vala',
|
||||
|
|
|
|||
|
|
@ -96,6 +96,10 @@ public void assert_int64(int64 expected, int64 actual, string? context = null)
|
|||
}
|
||||
}
|
||||
|
||||
public void assert_double(double actual, double expected, double epsilon) {
|
||||
assert(actual + epsilon >= expected && actual - epsilon <= expected);
|
||||
}
|
||||
|
||||
public void assert_uint(uint expected, uint actual, string? context = null)
|
||||
throws GLib.Error {
|
||||
if (expected != actual) {
|
||||
|
|
|
|||
|
|
@ -46,20 +46,28 @@ int main(string[] args) {
|
|||
engine.add_suite(new Geary.Db.DatabaseTest().get_suite());
|
||||
engine.add_suite(new Geary.Db.VersionedDatabaseTest().get_suite());
|
||||
engine.add_suite(new Geary.HTML.UtilTest().get_suite());
|
||||
// Other IMAP tests rely on DataFormat working, so test that first
|
||||
|
||||
// Other IMAP tests rely on these working, so test them first
|
||||
engine.add_suite(new Geary.Imap.DataFormatTest().get_suite());
|
||||
|
||||
engine.add_suite(new Geary.Imap.CreateCommandTest().get_suite());
|
||||
engine.add_suite(new Geary.Imap.DeserializerTest().get_suite());
|
||||
engine.add_suite(new Geary.Imap.FetchCommandTest().get_suite());
|
||||
engine.add_suite(new Geary.Imap.ListParameterTest().get_suite());
|
||||
engine.add_suite(new Geary.Imap.MailboxSpecifierTest().get_suite());
|
||||
engine.add_suite(new Geary.Imap.NamespaceResponseTest().get_suite());
|
||||
|
||||
// Depends on IMAP commands working
|
||||
engine.add_suite(new Geary.Imap.DeserializerTest().get_suite());
|
||||
engine.add_suite(new Geary.Imap.ClientConnectionTest().get_suite());
|
||||
engine.add_suite(new Geary.Imap.ClientSessionTest().get_suite());
|
||||
|
||||
engine.add_suite(new Geary.ImapDB.AccountTest().get_suite());
|
||||
engine.add_suite(new Geary.ImapDB.AttachmentTest().get_suite());
|
||||
engine.add_suite(new Geary.ImapDB.AttachmentIoTest().get_suite());
|
||||
engine.add_suite(new Geary.ImapDB.DatabaseTest().get_suite());
|
||||
engine.add_suite(new Geary.ImapDB.EmailIdentifierTest().get_suite());
|
||||
engine.add_suite(new Geary.ImapDB.FolderTest().get_suite());
|
||||
|
||||
engine.add_suite(new Geary.ImapEngine.AccountProcessorTest().get_suite());
|
||||
engine.add_suite(new Geary.ImapEngine.GenericAccountTest().get_suite());
|
||||
|
||||
|
|
|
|||
222
test/test-server.vala
Normal file
222
test/test-server.vala
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A simple mock server for testing network connections.
|
||||
*
|
||||
* To use it, unit tests should construct an instance as a fixture in
|
||||
* set up, specify a test script by adding lines and then check the
|
||||
* result, before stopping the server in tear down.
|
||||
*/
|
||||
public class TestServer : GLib.Object {
|
||||
|
||||
|
||||
/** Possible actions a script may take. */
|
||||
public enum Action {
|
||||
/**
|
||||
* The implicit first action.
|
||||
*
|
||||
* This does not need to be specified as a script action, it
|
||||
* will always be taken when a client connects.
|
||||
*/
|
||||
CONNECTED,
|
||||
|
||||
/** Send a line to the client. */
|
||||
SEND_LINE,
|
||||
|
||||
/** Receive a line from the client. */
|
||||
RECEIVE_LINE,
|
||||
|
||||
/** Wait for the client to disconnect. */
|
||||
WAIT_FOR_DISCONNECT,
|
||||
|
||||
/** Disconnect immediately. */
|
||||
DISCONNECT;
|
||||
}
|
||||
|
||||
|
||||
/** A line of the server's script. */
|
||||
public struct Line {
|
||||
|
||||
/** The action to take for this line. */
|
||||
public Action action;
|
||||
|
||||
/**
|
||||
* The value for the action.
|
||||
*
|
||||
* If sending, this string will be sent. If receiving, the
|
||||
* expected line.
|
||||
*/
|
||||
public string value;
|
||||
|
||||
}
|
||||
|
||||
/** The result of executing a script line. */
|
||||
public struct Result {
|
||||
|
||||
/** The expected action. */
|
||||
public Line line;
|
||||
|
||||
/** Was the expected action successful. */
|
||||
public bool succeeded;
|
||||
|
||||
/** The actual string sent by a client when not as expected. */
|
||||
public string? actual;
|
||||
|
||||
/** In case of an error being thrown, the error itself. */
|
||||
public GLib.Error? error;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private GLib.DataStreamNewlineType line_ending;
|
||||
private uint16 port;
|
||||
private GLib.ThreadedSocketService service =
|
||||
new GLib.ThreadedSocketService(10);
|
||||
private GLib.Cancellable running = new GLib.Cancellable();
|
||||
private Gee.List<Line?> script = new Gee.ArrayList<Line?>();
|
||||
private GLib.AsyncQueue<Result?> completion_queue =
|
||||
new GLib.AsyncQueue<Result?>();
|
||||
|
||||
|
||||
public TestServer(GLib.DataStreamNewlineType line_ending = CR_LF)
|
||||
throws GLib.Error {
|
||||
this.line_ending = line_ending;
|
||||
this.port = this.service.add_any_inet_port(null);
|
||||
this.service.run.connect((conn) => {
|
||||
handle_connection(conn);
|
||||
return true;
|
||||
});
|
||||
this.service.start();
|
||||
}
|
||||
|
||||
public GLib.SocketConnectable get_client_address() {
|
||||
return new GLib.NetworkAddress("localhost", this.port);
|
||||
}
|
||||
|
||||
public void add_script_line(Action action, string value) {
|
||||
this.script.add({ action, value });
|
||||
}
|
||||
|
||||
public Result wait_for_script(GLib.MainContext loop) {
|
||||
Result? result = null;
|
||||
while (result == null) {
|
||||
loop.iteration(false);
|
||||
result = this.completion_queue.try_pop();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.service.stop();
|
||||
this.running.cancel();
|
||||
}
|
||||
|
||||
private void handle_connection(GLib.SocketConnection connection) {
|
||||
debug("Connected");
|
||||
var input = new GLib.DataInputStream(
|
||||
connection.input_stream
|
||||
);
|
||||
input.set_newline_type(this.line_ending);
|
||||
|
||||
var output = new GLib.DataOutputStream(
|
||||
connection.output_stream
|
||||
);
|
||||
|
||||
Line connected_line = { CONNECTED, "" };
|
||||
Result result = { connected_line, true, null, null };
|
||||
foreach (var line in this.script) {
|
||||
result.line = line;
|
||||
switch (line.action) {
|
||||
case SEND_LINE:
|
||||
debug("Sending: %s", line.value);
|
||||
try {
|
||||
output.put_string(line.value);
|
||||
switch (this.line_ending) {
|
||||
case CR:
|
||||
output.put_byte('\r');
|
||||
break;
|
||||
case LF:
|
||||
output.put_byte('\n');
|
||||
break;
|
||||
default:
|
||||
output.put_byte('\r');
|
||||
output.put_byte('\n');
|
||||
break;
|
||||
}
|
||||
} catch (GLib.Error err) {
|
||||
result.succeeded = false;
|
||||
result.error = err;
|
||||
}
|
||||
break;
|
||||
|
||||
case RECEIVE_LINE:
|
||||
debug("Waiting for: %s", line.value);
|
||||
try {
|
||||
size_t len;
|
||||
string? received = input.read_line(out len, this.running);
|
||||
if (received == null || received != line.value) {
|
||||
result.succeeded = false;
|
||||
result.actual = received;
|
||||
}
|
||||
} catch (GLib.Error err) {
|
||||
result.succeeded = false;
|
||||
result.error = err;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAIT_FOR_DISCONNECT:
|
||||
debug("Waiting for disconnect");
|
||||
var socket = connection.get_socket();
|
||||
try {
|
||||
uint8 buffer[4096];
|
||||
while (socket.receive_with_blocking(buffer, true) > 0) { }
|
||||
} catch (GLib.Error err) {
|
||||
result.succeeded = false;
|
||||
result.error = err;
|
||||
}
|
||||
break;
|
||||
|
||||
case DISCONNECT:
|
||||
debug("Disconnecting");
|
||||
try {
|
||||
connection.close(this.running);
|
||||
} catch (GLib.Error err) {
|
||||
result.succeeded = false;
|
||||
result.error = err;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result.succeeded) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result.succeeded) {
|
||||
debug("Done");
|
||||
} else if (result.error != null) {
|
||||
warning("Error: %s", result.error.message);
|
||||
} else if (result.line.action == RECEIVE_LINE) {
|
||||
warning("Received unexpected line: %s", result.actual ?? "(null)");
|
||||
} else {
|
||||
warning("Failed for unknown reason");
|
||||
}
|
||||
|
||||
if (connection.is_connected()) {
|
||||
try {
|
||||
connection.close(this.running);
|
||||
} catch (GLib.Error err) {
|
||||
warning(
|
||||
"Error closing test server connection: %s", err.message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.completion_queue.push(result);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue