[evolution-data-server] CamelService: Rewrite connect/disconnect API.



commit a182bde249c72e0ac89cc85475712aea96fb8e08
Author: Matthew Barnes <mbarnes redhat com>
Date:   Sat May 19 08:41:15 2012 -0400

    CamelService: Rewrite connect/disconnect API.
    
    CamelService now has proper cancellable asynchronous functions for
    connect and disconnect operations.  The way this works is as follows:
    
    The first service connect request runs asynchronously internally, even
    if camel_service_connect_sync() is called.  Subsequent connect requests
    are queued until the first connect request finishes, then all requests
    finish simultaneously with the same result.  There are no retries; if
    the first request fails, they all fail with the same error.
    
    If a service disconnect is requested with one or more connect requests
    outstanding, the connect requests are all cancelled and the disconnect
    request runs asynchronously and plays by the same queueing rules.
    
    CamelService's "connection-status" property will immediately reflect any
    connect or disconnect requests in progress.  Change notification signals
    for this property, however, are emitted from CamelSession's main context.

 camel/camel-disco-store.c                   |   38 +-
 camel/camel-imapx-folder.c                  |    8 +-
 camel/camel-imapx-store.c                   |    8 +-
 camel/camel-offline-store.c                 |    5 +-
 camel/camel-sasl-popb4smtp.c                |    2 +-
 camel/camel-service.c                       |  948 +++++++++++++++++++++++----
 camel/camel-service.h                       |   54 ++-
 camel/providers/imap/camel-imap-command.c   |   13 +-
 camel/providers/imap/camel-imap-store.c     |   13 +-
 camel/providers/imapx/test-imapx.c          |    2 +-
 camel/providers/nntp/camel-nntp-store.c     |   10 +-
 camel/providers/pop3/camel-pop3-store.c     |    6 +-
 camel/providers/smtp/camel-smtp-transport.c |   45 +-
 docs/reference/camel/camel-sections.txt     |   13 +-
 14 files changed, 959 insertions(+), 206 deletions(-)
---
diff --git a/camel/camel-disco-store.c b/camel/camel-disco-store.c
index 44b0ce6..54df118 100644
--- a/camel/camel-disco-store.c
+++ b/camel/camel-disco-store.c
@@ -41,6 +41,21 @@
 G_DEFINE_TYPE (CamelDiscoStore, camel_disco_store, CAMEL_TYPE_STORE)
 
 static void
+disco_store_update_status (CamelDiscoStore *disco)
+{
+	CamelService *service = CAMEL_SERVICE (disco);
+
+	switch (camel_service_get_connection_status (service)) {
+		case CAMEL_SERVICE_CONNECTED:
+			disco->status = CAMEL_DISCO_STORE_ONLINE;
+			break;
+		default:
+			disco->status = CAMEL_DISCO_STORE_OFFLINE;
+			break;
+	}
+}
+
+static void
 disco_store_constructed (GObject *object)
 {
 	CamelDiscoStore *disco;
@@ -59,16 +74,10 @@ disco_store_constructed (GObject *object)
 		disco->status = CAMEL_DISCO_STORE_ONLINE;
 	else
 		disco->status = CAMEL_DISCO_STORE_OFFLINE;
-}
-
-static void
-disco_store_cancel_connect (CamelService *service)
-{
-	CamelDiscoStore *store = CAMEL_DISCO_STORE (service);
 
-	/* Fall back */
-	store->status = CAMEL_DISCO_STORE_OFFLINE;
-	CAMEL_SERVICE_CLASS (camel_disco_store_parent_class)->cancel_connect (service);
+	g_signal_connect (
+		service, "notify::connection-status",
+		G_CALLBACK (disco_store_update_status), NULL);
 }
 
 static gboolean
@@ -116,9 +125,9 @@ disco_store_connect_sync (CamelService *service,
 			return FALSE;
 		}
 
-		if (!camel_service_disconnect_sync (service, TRUE, error))
+		if (!camel_service_disconnect_sync (service, TRUE, cancellable, error))
 			return FALSE;
-		return camel_service_connect_sync (service, error);
+		return camel_service_connect_sync (service, cancellable, error);
 
 	case CAMEL_DISCO_STORE_OFFLINE:
 		return CAMEL_DISCO_STORE_GET_CLASS (service)->connect_offline (service, cancellable, error);
@@ -319,12 +328,14 @@ disco_store_set_status (CamelDiscoStore *disco_store,
 	}
 
 	if (!camel_service_disconnect_sync (
-		CAMEL_SERVICE (disco_store), network_available, error))
+		CAMEL_SERVICE (disco_store),
+		network_available, cancellable, error))
 		return FALSE;
 
 	disco_store->status = status;
 
-	return camel_service_connect_sync (CAMEL_SERVICE (disco_store), error);
+	return camel_service_connect_sync (
+		CAMEL_SERVICE (disco_store), cancellable, error);
 }
 
 static void
@@ -339,7 +350,6 @@ camel_disco_store_class_init (CamelDiscoStoreClass *class)
 
 	service_class = CAMEL_SERVICE_CLASS (class);
 	service_class->settings_type = CAMEL_TYPE_OFFLINE_SETTINGS;
-	service_class->cancel_connect = disco_store_cancel_connect;
 	service_class->connect_sync = disco_store_connect_sync;
 	service_class->disconnect_sync = disco_store_disconnect_sync;
 
diff --git a/camel/camel-imapx-folder.c b/camel/camel-imapx-folder.c
index a3e05e4..37a54f9 100644
--- a/camel/camel-imapx-folder.c
+++ b/camel/camel-imapx-folder.c
@@ -462,6 +462,7 @@ imapx_fetch_messages_sync (CamelFolder *folder,
                            GCancellable *cancellable,
                            GError **error)
 {
+	CamelService *service;
 	CamelStore *parent_store;
 	CamelIMAPXStore *istore;
 	CamelIMAPXServer *server;
@@ -469,6 +470,7 @@ imapx_fetch_messages_sync (CamelFolder *folder,
 
 	parent_store = camel_folder_get_parent_store (folder);
 	istore = CAMEL_IMAPX_STORE (parent_store);
+	service = CAMEL_SERVICE (parent_store);
 
 	if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
 		g_set_error (
@@ -478,7 +480,7 @@ imapx_fetch_messages_sync (CamelFolder *folder,
 		return FALSE;
 	}
 
-	if (!camel_service_connect_sync ((CamelService *) istore, error))
+	if (!camel_service_connect_sync (service, cancellable, error))
 		return FALSE;
 
 	server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (folder), cancellable, error);
@@ -592,6 +594,7 @@ imapx_refresh_info_sync (CamelFolder *folder,
                          GCancellable *cancellable,
                          GError **error)
 {
+	CamelService *service;
 	CamelStore *parent_store;
 	CamelIMAPXStore *istore;
 	CamelIMAPXServer *server;
@@ -599,6 +602,7 @@ imapx_refresh_info_sync (CamelFolder *folder,
 
 	parent_store = camel_folder_get_parent_store (folder);
 	istore = CAMEL_IMAPX_STORE (parent_store);
+	service = CAMEL_SERVICE (parent_store);
 
 	if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
 		g_set_error (
@@ -608,7 +612,7 @@ imapx_refresh_info_sync (CamelFolder *folder,
 		return FALSE;
 	}
 
-	if (!camel_service_connect_sync ((CamelService *) istore, error))
+	if (!camel_service_connect_sync (service, cancellable, error))
 		return FALSE;
 
 	server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (folder), cancellable, error);
diff --git a/camel/camel-imapx-store.c b/camel/camel-imapx-store.c
index 45a1787..8621f7b 100644
--- a/camel/camel-imapx-store.c
+++ b/camel/camel-imapx-store.c
@@ -104,7 +104,7 @@ imapx_store_dispose (GObject *object)
 	 * after we've cleaned up some stuff. */
 	if (imapx_store->con_man != NULL) {
 		camel_service_disconnect_sync (
-			CAMEL_SERVICE (imapx_store), TRUE, NULL);
+			CAMEL_SERVICE (imapx_store), TRUE, NULL, NULL);
 		g_object_unref (imapx_store->con_man);
 		imapx_store->con_man = NULL;
 	}
@@ -1087,7 +1087,8 @@ imapx_refresh_finfo (CamelSession *session,
 	if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store)))
 		goto exit;
 
-	if (!camel_service_connect_sync (CAMEL_SERVICE (store), error))
+	if (!camel_service_connect_sync (
+		CAMEL_SERVICE (store), cancellable, error))
 		goto exit;
 
 	/* look in all namespaces */
@@ -1244,7 +1245,8 @@ imapx_store_get_folder_info_sync (CamelStore *store,
 		return fi;
 	}
 
-	if (!camel_service_connect_sync ((CamelService *) store, error)) {
+	if (!camel_service_connect_sync (
+		CAMEL_SERVICE (store), cancellable, error)) {
 		g_mutex_unlock (istore->get_finfo_lock);
 		return NULL;
 	}
diff --git a/camel/camel-offline-store.c b/camel/camel-offline-store.c
index 98ee061..a3b94e0 100644
--- a/camel/camel-offline-store.c
+++ b/camel/camel-offline-store.c
@@ -136,7 +136,8 @@ camel_offline_store_set_online_sync (CamelOfflineStore *store,
 	/* Returning to online mode is the simpler case. */
 	if (!store_is_online) {
 		store->priv->online = online;
-		return camel_service_connect_sync (service, error);
+		return camel_service_connect_sync (
+			service, cancellable, error);
 	}
 
 	/* network available -> network unavailable */
@@ -172,7 +173,7 @@ camel_offline_store_set_online_sync (CamelOfflineStore *store,
 	}
 
 	success = camel_service_disconnect_sync (
-		service, network_available, error);
+		service, network_available, cancellable, error);
 
 	store->priv->online = online;
 
diff --git a/camel/camel-sasl-popb4smtp.c b/camel/camel-sasl-popb4smtp.c
index 48c0393..42ceb27 100644
--- a/camel/camel-sasl-popb4smtp.c
+++ b/camel/camel-sasl-popb4smtp.c
@@ -141,7 +141,7 @@ sasl_popb4smtp_challenge_sync (CamelSasl *sasl,
 	}
 
 	/* connect to pop session */
-	if (camel_service_connect_sync (service, error)) {
+	if (camel_service_connect_sync (service, cancellable, error)) {
 		camel_sasl_set_authenticated (sasl, TRUE);
 		*timep = now;
 	} else {
diff --git a/camel/camel-service.c b/camel/camel-service.c
index bf3e7f1..f197186 100644
--- a/camel/camel-service.c
+++ b/camel/camel-service.c
@@ -36,6 +36,7 @@
 #include <glib/gi18n-lib.h>
 
 #include "camel-debug.h"
+#include "camel-enumtypes.h"
 #include "camel-local-settings.h"
 #include "camel-network-settings.h"
 #include "camel-operation.h"
@@ -49,7 +50,9 @@
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), CAMEL_TYPE_SERVICE, CamelServicePrivate))
 
+typedef struct _AsyncClosure AsyncClosure;
 typedef struct _AsyncContext AsyncContext;
+typedef struct _ConnectionOp ConnectionOp;
 
 struct _CamelServicePrivate {
 	gpointer session;  /* weak pointer */
@@ -63,11 +66,21 @@ struct _CamelServicePrivate {
 	gchar *uid;
 	gchar *password;
 
-	GCancellable *connect_op;
-	CamelServiceConnectionStatus status;
-
 	GStaticRecMutex connect_lock;	/* for locking connection operations */
 	GStaticMutex connect_op_lock;	/* for locking the connection_op */
+
+	GMutex *connection_lock;
+	ConnectionOp *connection_op;
+	CamelServiceConnectionStatus status;
+};
+
+/* This is copied from EAsyncClosure in libedataserver.
+ * If this proves useful elsewhere in Camel we may want
+ * to split this out and make it part of the public API. */
+struct _AsyncClosure {
+	GMainLoop *loop;
+	GMainContext *context;
+	GAsyncResult *result;
 };
 
 struct _AsyncContext {
@@ -76,8 +89,21 @@ struct _AsyncContext {
 	CamelAuthenticationResult auth_result;
 };
 
+/* The GQueue is only modified while CamelService's
+ * connection_lock is held, so it does not need its
+ * own mutex. */
+struct _ConnectionOp {
+	volatile gint ref_count;
+	GQueue pending;
+	GMutex simple_lock;
+	GSimpleAsyncResult *simple;
+	GCancellable *cancellable;
+	gulong cancel_id;
+};
+
 enum {
 	PROP_0,
+	PROP_CONNECTION_STATUS,
 	PROP_DISPLAY_NAME,
 	PROP_PASSWORD,
 	PROP_PROVIDER,
@@ -93,6 +119,67 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
 	CamelService, camel_service, CAMEL_TYPE_OBJECT,
 	G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, camel_service_initable_init))
 
+static AsyncClosure *
+async_closure_new (void)
+{
+	AsyncClosure *closure;
+
+	closure = g_slice_new0 (AsyncClosure);
+	closure->context = g_main_context_new ();
+	closure->loop = g_main_loop_new (closure->context, FALSE);
+
+	g_main_context_push_thread_default (closure->context);
+
+	return closure;
+}
+
+static GAsyncResult *
+async_closure_wait (AsyncClosure *closure)
+{
+	g_return_val_if_fail (closure != NULL, NULL);
+
+	g_main_loop_run (closure->loop);
+
+	return closure->result;
+}
+
+static void
+async_closure_free (AsyncClosure *closure)
+{
+	g_return_if_fail (closure != NULL);
+
+	g_main_context_pop_thread_default (closure->context);
+
+	g_main_loop_unref (closure->loop);
+	g_main_context_unref (closure->context);
+
+	if (closure->result != NULL)
+		g_object_unref (closure->result);
+
+	g_slice_free (AsyncClosure, closure);
+}
+
+static void
+async_closure_callback (GObject *object,
+                        GAsyncResult *result,
+                        gpointer closure)
+{
+	AsyncClosure *real_closure;
+
+	g_return_if_fail (G_IS_OBJECT (object));
+	g_return_if_fail (G_IS_ASYNC_RESULT (result));
+	g_return_if_fail (closure != NULL);
+
+	real_closure = closure;
+
+	/* Replace any previous result. */
+	if (real_closure->result != NULL)
+		g_object_unref (real_closure->result);
+	real_closure->result = g_object_ref (result);
+
+	g_main_loop_quit (real_closure->loop);
+}
+
 static void
 async_context_free (AsyncContext *async_context)
 {
@@ -103,6 +190,123 @@ async_context_free (AsyncContext *async_context)
 	g_slice_free (AsyncContext, async_context);
 }
 
+static ConnectionOp *
+connection_op_new (GSimpleAsyncResult *simple,
+                   GCancellable *cancellable)
+{
+	ConnectionOp *op;
+
+	op = g_slice_new0 (ConnectionOp);
+	op->ref_count = 1;
+	g_mutex_init (&op->simple_lock);
+	op->simple = g_object_ref (simple);
+
+	if (G_IS_CANCELLABLE (cancellable))
+		op->cancellable = g_object_ref (cancellable);
+
+	return op;
+}
+
+static ConnectionOp *
+connection_op_ref (ConnectionOp *op)
+{
+	g_return_val_if_fail (op != NULL, NULL);
+	g_return_val_if_fail (op->ref_count > 0, NULL);
+
+	g_atomic_int_inc (&op->ref_count);
+
+	return op;
+}
+
+static void
+connection_op_unref (ConnectionOp *op)
+{
+	g_return_if_fail (op != NULL);
+	g_return_if_fail (op->ref_count > 0);
+
+	if (g_atomic_int_dec_and_test (&op->ref_count)) {
+
+		/* The pending queue should be empty. */
+		g_warn_if_fail (g_queue_is_empty (&op->pending));
+
+		g_mutex_clear (&op->simple_lock);
+
+		if (op->simple != NULL)
+			g_object_unref (op->simple);
+
+		if (op->cancel_id > 0)
+			g_cancellable_disconnect (
+				op->cancellable, op->cancel_id);
+
+		if (op->cancellable != NULL)
+			g_object_unref (op->cancellable);
+
+		g_slice_free (ConnectionOp, op);
+	}
+}
+
+static void
+connection_op_complete (ConnectionOp *op,
+                        const GError *error)
+{
+	g_mutex_lock (&op->simple_lock);
+
+	if (op->simple != NULL && error != NULL)
+		g_simple_async_result_set_from_error (op->simple, error);
+
+	if (op->simple != NULL) {
+		g_simple_async_result_complete_in_idle (op->simple);
+		g_object_unref (op->simple);
+		op->simple = NULL;
+	}
+
+	g_mutex_unlock (&op->simple_lock);
+}
+
+static void
+connection_op_cancelled (GCancellable *cancellable,
+                         ConnectionOp *op)
+{
+	/* Because we called g_simple_async_result_set_check_cancellable()
+	 * we don't need to explicitly set a G_IO_ERROR_CANCELLED here. */
+	connection_op_complete (op, NULL);
+}
+
+static void
+connection_op_add_pending (ConnectionOp *op,
+                           GSimpleAsyncResult *simple,
+                           GCancellable *cancellable)
+{
+	ConnectionOp *pending_op;
+
+	g_return_if_fail (op != NULL);
+
+	pending_op = connection_op_new (simple, cancellable);
+
+	if (pending_op->cancellable != NULL)
+		pending_op->cancel_id = g_cancellable_connect (
+			pending_op->cancellable,
+			G_CALLBACK (connection_op_cancelled),
+			pending_op, (GDestroyNotify) NULL);
+
+	g_queue_push_tail (&op->pending, pending_op);
+}
+
+static void
+connection_op_complete_pending (ConnectionOp *op,
+                                const GError *error)
+{
+	ConnectionOp *pending_op;
+
+	g_return_if_fail (op != NULL);
+
+	while (!g_queue_is_empty (&op->pending)) {
+		pending_op = g_queue_pop_head (&op->pending);
+		connection_op_complete (pending_op, error);
+		connection_op_unref (pending_op);
+	}
+}
+
 static gchar *
 service_find_old_data_dir (CamelService *service)
 {
@@ -188,6 +392,116 @@ service_find_old_data_dir (CamelService *service)
 	return old_data_dir;
 }
 
+static gboolean
+service_notify_connection_status_cb (gpointer user_data)
+{
+	CamelService *service = CAMEL_SERVICE (user_data);
+
+	g_object_notify (G_OBJECT (service), "connection-status");
+
+	return FALSE;
+}
+
+static void
+service_queue_notify_connection_status (CamelService *service)
+{
+	CamelSession *session;
+
+	session = camel_service_get_session (service);
+
+	camel_session_idle_add (
+		session, G_PRIORITY_DEFAULT_IDLE,
+		service_notify_connection_status_cb,
+		g_object_ref (service),
+		(GDestroyNotify) g_object_unref);
+}
+
+static void
+service_shared_connect_cb (GObject *source_object,
+                           GAsyncResult *result,
+                           gpointer user_data)
+{
+	CamelService *service;
+	CamelServiceClass *class;
+	ConnectionOp *op = user_data;
+	gboolean success;
+	GError *error = NULL;
+
+	/* This avoids a compiler warning
+	 * in the CAMEL_CHECK_GERROR macro. */
+	GError **p_error = &error;
+
+	service = CAMEL_SERVICE (source_object);
+	class = CAMEL_SERVICE_GET_CLASS (service);
+	g_return_if_fail (class->connect_finish != NULL);
+
+	success = class->connect_finish (service, result, &error);
+	CAMEL_CHECK_GERROR (service, connect_sync, success, p_error);
+
+	g_mutex_lock (service->priv->connection_lock);
+
+	if (service->priv->connection_op == op) {
+		connection_op_unref (service->priv->connection_op);
+		service->priv->connection_op = NULL;
+		if (success)
+			service->priv->status = CAMEL_SERVICE_CONNECTED;
+		else
+			service->priv->status = CAMEL_SERVICE_DISCONNECTED;
+		service_queue_notify_connection_status (service);
+	}
+
+	connection_op_complete (op, error);
+	connection_op_complete_pending (op, error);
+
+	g_mutex_unlock (service->priv->connection_lock);
+
+	connection_op_unref (op);
+	g_clear_error (&error);
+}
+
+static void
+service_shared_disconnect_cb (GObject *source_object,
+                              GAsyncResult *result,
+                              gpointer user_data)
+{
+	CamelService *service;
+	CamelServiceClass *class;
+	ConnectionOp *op = user_data;
+	gboolean success;
+	GError *error = NULL;
+
+	/* This avoids a compiler warning
+	 * in the CAMEL_CHECK_GERROR macro. */
+	GError **p_error = &error;
+
+	service = CAMEL_SERVICE (source_object);
+	class = CAMEL_SERVICE_GET_CLASS (service);
+	g_return_if_fail (class->disconnect_finish != NULL);
+
+	success = class->disconnect_finish (service, result, &error);
+	CAMEL_CHECK_GERROR (service, disconnect_sync, success, p_error);
+
+	g_mutex_lock (service->priv->connection_lock);
+
+	if (service->priv->connection_op == op) {
+		connection_op_unref (service->priv->connection_op);
+		service->priv->connection_op = NULL;
+		if (success)
+			service->priv->status = CAMEL_SERVICE_DISCONNECTED;
+		else
+			service->priv->status = CAMEL_SERVICE_CONNECTED;
+		service_queue_notify_connection_status (service);
+	}
+
+	connection_op_complete (op, error);
+	connection_op_complete_pending (op, error);
+
+	g_mutex_unlock (service->priv->connection_lock);
+
+	connection_op_unref (op);
+	g_clear_error (&error);
+}
+
 static void
 service_set_provider (CamelService *service,
                       CamelProvider *provider)
@@ -275,6 +589,12 @@ service_get_property (GObject *object,
                       GParamSpec *pspec)
 {
 	switch (property_id) {
+		case PROP_CONNECTION_STATUS:
+			g_value_set_enum (
+				value, camel_service_get_connection_status (
+				CAMEL_SERVICE (object)));
+			return;
+
 		case PROP_DISPLAY_NAME:
 			g_value_set_string (
 				value, camel_service_get_display_name (
@@ -357,6 +677,10 @@ service_finalize (GObject *object)
 	g_static_rec_mutex_free (&priv->connect_lock);
 	g_static_mutex_free (&priv->connect_op_lock);
 
+	/* There should be no outstanding connection operations. */
+	g_warn_if_fail (priv->connection_op == NULL);
+	g_mutex_free (priv->connection_lock);
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (camel_service_parent_class)->finalize (object);
 }
@@ -395,20 +719,13 @@ service_get_name (CamelService *service,
 	return g_strdup (G_OBJECT_TYPE_NAME (service));
 }
 
-static void
-service_cancel_connect (CamelService *service)
-{
-	g_cancellable_cancel (service->priv->connect_op);
-}
-
 static gboolean
 service_connect_sync (CamelService *service,
                       GCancellable *cancellable,
                       GError **error)
 {
-	/* Things like the CamelMboxStore can validly
-	 * not define a connect function. */
-	 return TRUE;
+	/* Default behavior for local storage providers. */
+	return TRUE;
 }
 
 static gboolean
@@ -417,8 +734,7 @@ service_disconnect_sync (CamelService *service,
                          GCancellable *cancellable,
                          GError **error)
 {
-	/* We let people get away with not having a disconnect
-	 * function -- CamelMboxStore, for example. */
+	/* Default behavior for local storage providers. */
 	return TRUE;
 }
 
@@ -431,6 +747,129 @@ service_query_auth_types_sync (CamelService *service,
 }
 
 static void
+service_connect_thread (GSimpleAsyncResult *simple,
+                        GObject *object,
+                        GCancellable *cancellable)
+{
+	CamelService *service;
+	CamelServiceClass *class;
+	GError *error = NULL;
+
+	/* Note we call the class method directly here. */
+
+	service = CAMEL_SERVICE (object);
+
+	class = CAMEL_SERVICE_GET_CLASS (service);
+	g_return_if_fail (class->connect_sync != NULL);
+
+	class->connect_sync (service, cancellable, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+}
+
+static void
+service_connect (CamelService *service,
+                 gint io_priority,
+                 GCancellable *cancellable,
+                 GAsyncReadyCallback callback,
+                 gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (service), callback, user_data, service_connect);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_simple_async_result_run_in_thread (
+		simple, service_connect_thread, io_priority, cancellable);
+
+	g_object_unref (simple);
+}
+
+static gboolean
+service_connect_finish (CamelService *service,
+                        GAsyncResult *result,
+                        GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (service), service_connect), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+service_disconnect_thread (GSimpleAsyncResult *simple,
+                           GObject *object,
+                           GCancellable *cancellable)
+{
+	CamelService *service;
+	CamelServiceClass *class;
+	gboolean clean;
+	GError *error = NULL;
+
+	/* Note we call the class method directly here. */
+
+	service = CAMEL_SERVICE (object);
+	clean = g_simple_async_result_get_op_res_gboolean (simple);
+
+	class = CAMEL_SERVICE_GET_CLASS (service);
+	g_return_if_fail (class->disconnect_sync != NULL);
+
+	class->disconnect_sync (service, clean, cancellable, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+}
+
+static void
+service_disconnect (CamelService *service,
+                    gboolean clean,
+                    gint io_priority,
+                    GCancellable *cancellable,
+                    GAsyncReadyCallback callback,
+                    gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (service), callback, user_data, service_disconnect);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_simple_async_result_set_op_res_gboolean (simple, clean);
+
+	g_simple_async_result_run_in_thread (
+		simple, service_disconnect_thread, io_priority, cancellable);
+
+	g_object_unref (simple);
+}
+
+static gboolean
+service_disconnect_finish (CamelService *service,
+                           GAsyncResult *result,
+                           GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (service), service_disconnect), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
 service_authenticate_thread (GSimpleAsyncResult *simple,
                              GObject *object,
                              GCancellable *cancellable)
@@ -592,11 +1031,14 @@ camel_service_class_init (CamelServiceClass *class)
 
 	class->settings_type = CAMEL_TYPE_SETTINGS;
 	class->get_name = service_get_name;
-	class->cancel_connect = service_cancel_connect;
 	class->connect_sync = service_connect_sync;
 	class->disconnect_sync = service_disconnect_sync;
 	class->query_auth_types_sync = service_query_auth_types_sync;
 
+	class->connect = service_connect;
+	class->connect_finish = service_connect_finish;
+	class->disconnect = service_disconnect;
+	class->disconnect_finish = service_disconnect_finish;
 	class->authenticate = service_authenticate;
 	class->authenticate_finish = service_authenticate_finish;
 	class->query_auth_types = service_query_auth_types;
@@ -604,6 +1046,18 @@ camel_service_class_init (CamelServiceClass *class)
 
 	g_object_class_install_property (
 		object_class,
+		PROP_CONNECTION_STATUS,
+		g_param_spec_enum (
+			"connection-status",
+			"Connection Status",
+			"The connection status for the service",
+			CAMEL_TYPE_SERVICE_CONNECTION_STATUS,
+			CAMEL_SERVICE_DISCONNECTED,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
 		PROP_DISPLAY_NAME,
 		g_param_spec_string (
 			"display-name",
@@ -685,10 +1139,11 @@ camel_service_init (CamelService *service)
 {
 	service->priv = CAMEL_SERVICE_GET_PRIVATE (service);
 
-	service->priv->status = CAMEL_SERVICE_DISCONNECTED;
-
 	g_static_rec_mutex_init (&service->priv->connect_lock);
 	g_static_mutex_init (&service->priv->connect_op_lock);
+
+	service->priv->connection_lock = g_mutex_new ();
+	service->priv->status = CAMEL_SERVICE_DISCONNECTED;
 }
 
 GQuark
@@ -795,6 +1250,26 @@ camel_service_new_camel_url (CamelService *service)
 }
 
 /**
+ * camel_service_get_connection_status:
+ * @service: a #CamelService
+ *
+ * Returns the connection status for @service.
+ *
+ * Returns: the connection status
+ *
+ * Since: 3.2
+ **/
+CamelServiceConnectionStatus
+camel_service_get_connection_status (CamelService *service)
+{
+	g_return_val_if_fail (
+		CAMEL_IS_SERVICE (service),
+		CAMEL_SERVICE_DISCONNECTED);
+
+	return service->priv->status;
+}
+
+/**
  * camel_service_get_display_name:
  * @service: a #CamelService
  *
@@ -1074,209 +1549,408 @@ camel_service_get_uid (CamelService *service)
 }
 
 /**
- * camel_service_cancel_connect:
+ * camel_service_lock:
  * @service: a #CamelService
+ * @lock: lock type to lock
+ *
+ * Locks @service's @lock. Unlock it with camel_service_unlock().
  *
- * If @service is currently attempting to connect to or disconnect
- * from a server, this causes it to stop and fail. Otherwise it is a
- * no-op.
+ * Since: 2.32
  **/
 void
-camel_service_cancel_connect (CamelService *service)
+camel_service_lock (CamelService *service,
+                    CamelServiceLock lock)
 {
-	CamelServiceClass *class;
-
 	g_return_if_fail (CAMEL_IS_SERVICE (service));
 
-	class = CAMEL_SERVICE_GET_CLASS (service);
-	g_return_if_fail (class->cancel_connect != NULL);
+	switch (lock) {
+		case CAMEL_SERVICE_REC_CONNECT_LOCK:
+			g_static_rec_mutex_lock (&service->priv->connect_lock);
+			break;
+		case CAMEL_SERVICE_CONNECT_OP_LOCK:
+			g_static_mutex_lock (&service->priv->connect_op_lock);
+			break;
+		default:
+			g_return_if_reached ();
+	}
+}
+
+/**
+ * camel_service_unlock:
+ * @service: a #CamelService
+ * @lock: lock type to unlock
+ *
+ * Unlocks @service's @lock, previously locked with camel_service_lock().
+ *
+ * Since: 2.32
+ **/
+void
+camel_service_unlock (CamelService *service,
+                      CamelServiceLock lock)
+{
+	g_return_if_fail (CAMEL_IS_SERVICE (service));
 
-	camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
-	if (service->priv->connect_op)
-		class->cancel_connect (service);
-	camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
+	switch (lock) {
+		case CAMEL_SERVICE_REC_CONNECT_LOCK:
+			g_static_rec_mutex_unlock (&service->priv->connect_lock);
+			break;
+		case CAMEL_SERVICE_CONNECT_OP_LOCK:
+			g_static_mutex_unlock (&service->priv->connect_op_lock);
+			break;
+		default:
+			g_return_if_reached ();
+	}
 }
 
 /**
  * camel_service_connect_sync:
  * @service: a #CamelService
+ * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
- * Connect to the service using the parameters it was initialized
- * with.
+ * Connects @service to a remote server using the information in its
+ * #CamelService:settings instance.
+ *
+ * If a connect operation is already in progress when this function is
+ * called, its results will be reflected in this connect operation.
  *
  * Returns: %TRUE if the connection is made or %FALSE otherwise
+ *
+ * Since: 3.6
  **/
 gboolean
 camel_service_connect_sync (CamelService *service,
+                            GCancellable *cancellable,
                             GError **error)
 {
-	CamelServiceClass *class;
-	GCancellable *op;
-	gboolean ret = FALSE;
+	AsyncClosure *closure;
+	GAsyncResult *result;
+	gboolean success;
 
 	g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
-	g_return_val_if_fail (service->priv->session != NULL, FALSE);
+
+	closure = async_closure_new ();
+
+	camel_service_connect (
+		service, G_PRIORITY_DEFAULT, cancellable,
+		async_closure_callback, closure);
+
+	result = async_closure_wait (closure);
+
+	success = camel_service_connect_finish (service, result, error);
+
+	async_closure_free (closure);
+
+	return success;
+}
+
+/**
+ * camel_service_connect:
+ * @service: a #CamelService
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously connects @service to a remote server using the information
+ * in its #CamelService:settings instance.
+ *
+ * If a connect operation is already in progress when this function is
+ * called, its results will be reflected in this connect operation.
+ *
+ * If any disconnect operations are in progress when this function is
+ * called, they will be cancelled.
+ *
+ * When the operation is finished, @callback will be called.  You can
+ * then call camel_service_connect_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.6
+ **/
+void
+camel_service_connect (CamelService *service,
+                       gint io_priority,
+                       GCancellable *cancellable,
+                       GAsyncReadyCallback callback,
+                       gpointer user_data)
+{
+	ConnectionOp *op;
+	CamelServiceClass *class;
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail (CAMEL_IS_SERVICE (service));
 
 	class = CAMEL_SERVICE_GET_CLASS (service);
-	g_return_val_if_fail (class->connect_sync != NULL, FALSE);
+	g_return_if_fail (class->connect != NULL);
 
-	camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
+	simple = g_simple_async_result_new (
+		G_OBJECT (service), callback,
+		user_data, camel_service_connect);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_mutex_lock (service->priv->connection_lock);
+
+	switch (service->priv->status) {
+
+		/* If a connect operation is already in progress,
+		 * queue this operation so it completes at the same
+		 * time the first connect operation completes. */
+		case CAMEL_SERVICE_CONNECTING:
+			connection_op_add_pending (
+				service->priv->connection_op,
+				simple, cancellable);
+			break;
+
+		/* If we're already connected, just report success. */
+		case CAMEL_SERVICE_CONNECTED:
+			g_simple_async_result_complete_in_idle (simple);
+			break;
+
+		/* If a disconnect operation is currently in progress,
+		 * cancel it and make room for the connect operation. */
+		case CAMEL_SERVICE_DISCONNECTING:
+			g_return_if_fail (
+				service->priv->connection_op != NULL);
+			g_cancellable_cancel (
+				service->priv->connection_op->cancellable);
+			connection_op_unref (service->priv->connection_op);
+			service->priv->connection_op = NULL;
+			/* fall through */
+
+		/* Start a new connect operation.  Subsequent connect
+		 * operations are queued until this operation completes
+		 * and will share this operation's result. */
+		case CAMEL_SERVICE_DISCONNECTED:
+			g_return_if_fail (
+				service->priv->connection_op == NULL);
+
+			op = connection_op_new (simple, cancellable);
+			service->priv->connection_op = op;
+
+			service->priv->status = CAMEL_SERVICE_CONNECTING;
+			service_queue_notify_connection_status (service);
+
+			class->connect (
+				service,
+				io_priority,
+				cancellable,
+				service_shared_connect_cb,
+				connection_op_ref (op));
+			break;
 
-	if (service->priv->status == CAMEL_SERVICE_CONNECTED) {
-		camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
-		return TRUE;
+		default:
+			g_warn_if_reached ();
 	}
 
-	/* Register a separate operation for connecting, so that
-	 * the offline code can cancel it. */
-	camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
-	service->priv->connect_op = camel_operation_new ();
-	op = service->priv->connect_op;
-	camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
-
-	service->priv->status = CAMEL_SERVICE_CONNECTING;
-	ret = class->connect_sync (service, service->priv->connect_op, error);
-	CAMEL_CHECK_GERROR (service, connect_sync, ret, error);
-	service->priv->status =
-		ret ? CAMEL_SERVICE_CONNECTED : CAMEL_SERVICE_DISCONNECTED;
-
-	camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
-	g_object_unref (op);
-	if (op == service->priv->connect_op)
-		service->priv->connect_op = NULL;
-	camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
+	g_mutex_unlock (service->priv->connection_lock);
 
-	camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
+	g_object_unref (simple);
+}
 
-	return ret;
+/**
+ * camel_service_connect_finish:
+ * @service: a #CamelService
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_service_connect().
+ *
+ * Returns: %TRUE if the connection was made or %FALSE otherwise
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_service_connect_finish (CamelService *service,
+                              GAsyncResult *result,
+                              GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (service), camel_service_connect), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
 }
 
 /**
  * camel_service_disconnect_sync:
  * @service: a #CamelService
  * @clean: whether or not to try to disconnect cleanly
+ * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
  * Disconnect from the service. If @clean is %FALSE, it should not
  * try to do any synchronizing or other cleanup of the connection.
  *
- * Returns: %TRUE if the disconnect was successful or %FALSE otherwise
+ * If a disconnect operation is already in progress when this function is
+ * called, its results will be reflected in this disconnect operation.
+ *
+ * If any connect operations are in progress when this function is called,
+ * they will be cancelled.
+ *
+ * Returns: %TRUE if the connection was severed or %FALSE otherwise
  **/
 gboolean
 camel_service_disconnect_sync (CamelService *service,
                                gboolean clean,
+                               GCancellable *cancellable,
                                GError **error)
 {
-	CamelServiceClass *class;
-	gboolean res = TRUE;
+	AsyncClosure *closure;
+	GAsyncResult *result;
+	gboolean success;
 
 	g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
 
-	class = CAMEL_SERVICE_GET_CLASS (service);
-	g_return_val_if_fail (class->disconnect_sync != NULL, FALSE);
+	closure = async_closure_new ();
 
-	camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
+	camel_service_disconnect (
+		service, clean, G_PRIORITY_DEFAULT,
+		cancellable, async_closure_callback, closure);
 
-	if (service->priv->status != CAMEL_SERVICE_DISCONNECTED
-	    && service->priv->status != CAMEL_SERVICE_DISCONNECTING) {
-		GCancellable *op;
-		camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
-		service->priv->connect_op = camel_operation_new ();
-		op = service->priv->connect_op;
-		camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
-
-		service->priv->status = CAMEL_SERVICE_DISCONNECTING;
-		res = class->disconnect_sync (
-			service, clean, service->priv->connect_op, error);
-		CAMEL_CHECK_GERROR (service, disconnect_sync, res, error);
-		service->priv->status = CAMEL_SERVICE_DISCONNECTED;
-
-		camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
-		g_object_unref (op);
-		if (op == service->priv->connect_op)
-			service->priv->connect_op = NULL;
-		camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
-	}
+	result = async_closure_wait (closure);
 
-	camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
+	success = camel_service_disconnect_finish (service, result, error);
 
-	service->priv->status = CAMEL_SERVICE_DISCONNECTED;
+	async_closure_free (closure);
 
-	return res;
+	return success;
 }
 
 /**
- * camel_service_get_connection_status:
+ * camel_service_disconnect:
  * @service: a #CamelService
+ * @clean: whether or not to try to disconnect cleanly
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
  *
- * Returns the connection status for @service.
- *
- * Returns: the connection status
+ * If a disconnect operation is already in progress when this function is
+ * called, its results will be reflected in this disconnect operation.
  *
- * Since: 3.2
- **/
-CamelServiceConnectionStatus
-camel_service_get_connection_status (CamelService *service)
-{
-	g_return_val_if_fail (
-		CAMEL_IS_SERVICE (service), CAMEL_SERVICE_DISCONNECTED);
-
-	return service->priv->status;
-}
-
-/**
- * camel_service_lock:
- * @service: a #CamelService
- * @lock: lock type to lock
+ * If any connect operations are in progress when this function is called,
+ * they will be cancelled.
  *
- * Locks @service's @lock. Unlock it with camel_service_unlock().
+ * When the operation is finished, @callback will be called.  You can
+ * then call camel_service_disconnect_finish() to get the result of the
+ * operation.
  *
- * Since: 2.32
+ * Since: 3.6
  **/
 void
-camel_service_lock (CamelService *service,
-                    CamelServiceLock lock)
+camel_service_disconnect (CamelService *service,
+                          gboolean clean,
+                          gint io_priority,
+                          GCancellable *cancellable,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
 {
+	ConnectionOp *op;
+	CamelServiceClass *class;
+	GSimpleAsyncResult *simple;
+
 	g_return_if_fail (CAMEL_IS_SERVICE (service));
 
-	switch (lock) {
-		case CAMEL_SERVICE_REC_CONNECT_LOCK:
-			g_static_rec_mutex_lock (&service->priv->connect_lock);
+	class = CAMEL_SERVICE_GET_CLASS (service);
+	g_return_if_fail (class->disconnect != NULL);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (service), callback,
+		user_data, camel_service_disconnect);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_mutex_lock (service->priv->connection_lock);
+
+	switch (service->priv->status) {
+
+		/* If a connect operation is currently in progress,
+		 * cancel it and make room for the disconnect operation. */
+		case CAMEL_SERVICE_CONNECTING:
+			g_return_if_fail (
+				service->priv->connection_op != NULL);
+			g_cancellable_cancel (
+				service->priv->connection_op->cancellable);
+			connection_op_unref (service->priv->connection_op);
+			service->priv->connection_op = NULL;
+			/* fall through */
+
+		/* Start a new disconnect operation.  Subsequent disconnect
+		 * operations are queued until this operation completes and
+		 * will share this operation's result. */
+		case CAMEL_SERVICE_CONNECTED:
+			g_return_if_fail (
+				service->priv->connection_op == NULL);
+
+			op = connection_op_new (simple, cancellable);
+			service->priv->connection_op = op;
+
+			service->priv->status = CAMEL_SERVICE_DISCONNECTING;
+			service_queue_notify_connection_status (service);
+
+			class->disconnect (
+				service, clean,
+				io_priority,
+				cancellable,
+				service_shared_disconnect_cb,
+				connection_op_ref (op));
+
+		/* If a disconnect operation is already in progress,
+		 * queue this operation so it completes at the same
+		 * time the first disconnect operation completes. */
+		case CAMEL_SERVICE_DISCONNECTING:
+			connection_op_add_pending (
+				service->priv->connection_op,
+				simple, cancellable);
 			break;
-		case CAMEL_SERVICE_CONNECT_OP_LOCK:
-			g_static_mutex_lock (&service->priv->connect_op_lock);
+
+		/* If we're already disconnected, just report success. */
+		case CAMEL_SERVICE_DISCONNECTED:
+			g_simple_async_result_complete_in_idle (simple);
 			break;
+
 		default:
-			g_return_if_reached ();
+			g_warn_if_reached ();
 	}
+
+	g_mutex_unlock (service->priv->connection_lock);
+
+	g_object_unref (simple);
 }
 
 /**
- * camel_service_unlock:
+ * camel_service_disconnect_finish:
  * @service: a #CamelService
- * @lock: lock type to unlock
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
  *
- * Unlocks @service's @lock, previously locked with camel_service_lock().
+ * Finishes the operation started with camel_service_disconnect().
  *
- * Since: 2.32
+ * Returns: %TRUE if the connection was severed or %FALSE otherwise
+ *
+ * Since: 3.6
  **/
-void
-camel_service_unlock (CamelService *service,
-                      CamelServiceLock lock)
+gboolean
+camel_service_disconnect_finish (CamelService *service,
+                                 GAsyncResult *result,
+                                 GError **error)
 {
-	g_return_if_fail (CAMEL_IS_SERVICE (service));
+	GSimpleAsyncResult *simple;
 
-	switch (lock) {
-		case CAMEL_SERVICE_REC_CONNECT_LOCK:
-			g_static_rec_mutex_unlock (&service->priv->connect_lock);
-			break;
-		case CAMEL_SERVICE_CONNECT_OP_LOCK:
-			g_static_mutex_unlock (&service->priv->connect_op_lock);
-			break;
-		default:
-			g_return_if_reached ();
-	}
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (service), camel_service_disconnect), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
 }
 
 /**
diff --git a/camel/camel-service.h b/camel/camel-service.h
index 3947e27..8f9d146 100644
--- a/camel/camel-service.h
+++ b/camel/camel-service.h
@@ -107,7 +107,6 @@ struct _CamelServiceClass {
 	/* Non-Blocking Methods */
 	gchar *		(*get_name)		(CamelService *service,
 						 gboolean brief);
-	void		(*cancel_connect)	(CamelService *service);
 
 	/* Synchronous I/O Methods */
 	gboolean	(*connect_sync)		(CamelService *service,
@@ -128,6 +127,23 @@ struct _CamelServiceClass {
 						 GError **error);
 
 	/* Asynchronous I/O Methods (all have defaults) */
+	void		(*connect)		(CamelService *service,
+						 gint io_priority,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+	gboolean	(*connect_finish)	(CamelService *service,
+						 GAsyncResult *result,
+						 GError **error);
+	void		(*disconnect)		(CamelService *service,
+						 gboolean clean,
+						 gint io_priority,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+	gboolean	(*disconnect_finish)	(CamelService *service,
+						 GAsyncResult *result,
+						 GError **error);
 	void		(*authenticate)		(CamelService *service,
 						 const gchar *mechanism,
 						 gint io_priority,
@@ -162,6 +178,9 @@ GType		camel_service_get_type		(void);
 GQuark		camel_service_error_quark	(void) G_GNUC_CONST;
 void		camel_service_migrate_files	(CamelService *service);
 CamelURL *	camel_service_new_camel_url	(CamelService *service);
+CamelServiceConnectionStatus
+		camel_service_get_connection_status
+						(CamelService *service);
 const gchar *	camel_service_get_display_name	(CamelService *service);
 void		camel_service_set_display_name	(CamelService *service,
 						 const gchar *display_name);
@@ -180,20 +199,35 @@ CamelSettings *	camel_service_get_settings	(CamelService *service);
 void		camel_service_set_settings	(CamelService *service,
 						 CamelSettings *settings);
 const gchar *	camel_service_get_uid		(CamelService *service);
-void		camel_service_cancel_connect	(CamelService *service);
-gboolean	camel_service_connect_sync	(CamelService *service,
-						 GError **error);
-gboolean	camel_service_disconnect_sync	(CamelService *service,
-						 gboolean clean,
-						 GError **error);
-CamelServiceConnectionStatus
-		camel_service_get_connection_status
-						(CamelService *service);
 void		camel_service_lock		(CamelService *service,
 						 CamelServiceLock lock);
 void		camel_service_unlock		(CamelService *service,
 						 CamelServiceLock lock);
 
+gboolean	camel_service_connect_sync	(CamelService *service,
+						 GCancellable *cancellable,
+						 GError **error);
+void		camel_service_connect		(CamelService *service,
+						 gint io_priority,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	camel_service_connect_finish	(CamelService *service,
+						 GAsyncResult *result,
+						 GError **error);
+gboolean	camel_service_disconnect_sync	(CamelService *service,
+						 gboolean clean,
+						 GCancellable *cancellable,
+						 GError **error);
+void		camel_service_disconnect	(CamelService *service,
+						 gboolean clean,
+						 gint io_priority,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	camel_service_disconnect_finish	(CamelService *service,
+						 GAsyncResult *result,
+						 GError **error);
 CamelAuthenticationResult
 		camel_service_authenticate_sync	(CamelService *service,
 						 const gchar *mechanism,
diff --git a/camel/providers/imap/camel-imap-command.c b/camel/providers/imap/camel-imap-command.c
index f2b3c1e..0bd312d 100644
--- a/camel/providers/imap/camel-imap-command.c
+++ b/camel/providers/imap/camel-imap-command.c
@@ -255,7 +255,7 @@ imap_command_start (CamelImapStore *store,
 
 	if (nwritten == -1) {
 		camel_service_disconnect_sync (
-			CAMEL_SERVICE (store), FALSE, NULL);
+			CAMEL_SERVICE (store), FALSE, cancellable, NULL);
 		return FALSE;
 	}
 
@@ -309,7 +309,7 @@ camel_imap_command_continuation (CamelImapStore *store,
 	if (camel_stream_write (store->ostream, cmd, cmdlen, cancellable, error) == -1 ||
 	    camel_stream_write (store->ostream, "\r\n", 2, cancellable, error) == -1) {
 		camel_service_disconnect_sync (
-			CAMEL_SERVICE (store), FALSE, NULL);
+			CAMEL_SERVICE (store), FALSE, cancellable, NULL);
 		g_static_rec_mutex_unlock (&store->command_and_response_lock);
 		return NULL;
 	}
@@ -376,7 +376,8 @@ camel_imap_command_response (CamelImapStore *store,
 				err = g_strerror (104);
 
 			/* Connection was lost, no more data to fetch */
-			camel_service_disconnect_sync (service, FALSE, NULL);
+			camel_service_disconnect_sync (
+				service, FALSE, cancellable, NULL);
 			g_set_error (
 				error, CAMEL_SERVICE_ERROR,
 				CAMEL_SERVICE_ERROR_UNAVAILABLE,
@@ -579,7 +580,8 @@ imap_read_untagged (CamelImapStore *store,
 				cancellable, error);
 			if (n == -1) {
 				camel_service_disconnect_sync (
-					CAMEL_SERVICE (store), FALSE, NULL);
+					CAMEL_SERVICE (store),
+					FALSE, cancellable, NULL);
 				g_string_free (str, TRUE);
 				goto lose;
 			}
@@ -593,7 +595,8 @@ imap_read_untagged (CamelImapStore *store,
 				CAMEL_SERVICE_ERROR_UNAVAILABLE,
 				_("Server response ended too soon."));
 			camel_service_disconnect_sync (
-				CAMEL_SERVICE (store), FALSE, NULL);
+				CAMEL_SERVICE (store),
+				FALSE, cancellable, NULL);
 			g_string_free (str, TRUE);
 			goto lose;
 		}
diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c
index 84b6a0d..197b76d 100644
--- a/camel/providers/imap/camel-imap-store.c
+++ b/camel/providers/imap/camel-imap-store.c
@@ -779,7 +779,8 @@ imap_store_finalize (GObject *object)
 	CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
 
 	/* This frees current_folder, folders, authtypes, streams, and namespace. */
-	camel_service_disconnect_sync (CAMEL_SERVICE (imap_store), TRUE, NULL);
+	camel_service_disconnect_sync (
+		CAMEL_SERVICE (imap_store), TRUE, NULL, NULL);
 
 	g_static_rec_mutex_free (&imap_store->command_and_response_lock);
 	g_hash_table_destroy (imap_store->known_alerts);
@@ -841,7 +842,8 @@ imap_store_connect_sync (CamelService *service,
 	if (!connect_to_server_wrapper (service, cancellable, error) ||
 	    !imap_auth_loop (service, cancellable, error)) {
 		camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
-		camel_service_disconnect_sync (service, TRUE, NULL);
+		camel_service_disconnect_sync (
+			service, TRUE, cancellable, NULL);
 		return FALSE;
 	}
 
@@ -1016,7 +1018,8 @@ done:
 	camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
 
 	if (local_error != NULL) {
-		camel_service_disconnect_sync (service, TRUE, NULL);
+		camel_service_disconnect_sync (
+			service, TRUE, cancellable, NULL);
 		g_propagate_error (error, local_error);
 		return FALSE;
 	}
@@ -3273,7 +3276,7 @@ camel_imap_store_connected (CamelImapStore *store,
 
 	success =
 		camel_offline_store_get_online (offline_store) &&
-		camel_service_connect_sync (service, &local_error);
+		camel_service_connect_sync (service, NULL, &local_error);
 
 	if (success && store->istream != NULL)
 		return TRUE;
@@ -3334,7 +3337,7 @@ camel_imap_store_readline (CamelImapStore *store,
 				error, _("Server unexpectedly disconnected: "));
 
 		camel_service_disconnect_sync (
-			CAMEL_SERVICE (store), FALSE, NULL);
+			CAMEL_SERVICE (store), FALSE, cancellable, NULL);
 		g_byte_array_free (ba, TRUE);
 		return -1;
 	}
diff --git a/camel/providers/imapx/test-imapx.c b/camel/providers/imapx/test-imapx.c
index 37fb532..f8b9c97 100644
--- a/camel/providers/imapx/test-imapx.c
+++ b/camel/providers/imapx/test-imapx.c
@@ -44,7 +44,7 @@ main (gint argc,
 
 	service = camel_session_add_service (
 		session, "text-imapx", uri, CAMEL_PROVIDER_STORE, NULL);
-	camel_service_connect_sync (service, NULL);
+	camel_service_connect_sync (service, NULL, NULL);
 
 	camel_store_get_folder_info_sync (
 		CAMEL_STORE (service), "", 3, NULL, NULL);
diff --git a/camel/providers/nntp/camel-nntp-store.c b/camel/providers/nntp/camel-nntp-store.c
index 8f202af..037d2cf 100644
--- a/camel/providers/nntp/camel-nntp-store.c
+++ b/camel/providers/nntp/camel-nntp-store.c
@@ -86,7 +86,7 @@ nntp_store_dispose (GObject *object)
 	/* Only run this the first time. */
 	if (nntp_store->summary != NULL)
 		camel_service_disconnect_sync (
-			CAMEL_SERVICE (object), TRUE, NULL);
+			CAMEL_SERVICE (object), TRUE, NULL, NULL);
 
 	if (nntp_store->summary != NULL) {
 		camel_store_summary_save (
@@ -1741,7 +1741,7 @@ camel_nntp_command (CamelNNTPStore *store,
 		retry++;
 
 		if (store->stream == NULL
-		    && !camel_service_connect_sync (service, error))
+		    && !camel_service_connect_sync (service, cancellable, error))
 			return -1;
 
 		/* Check for unprocessed data, !*/
@@ -1792,11 +1792,13 @@ camel_nntp_command (CamelNNTPStore *store,
 		case 400:	/* service discontinued */
 		case 401:	/* wrong client state - this should quit but this is what the old code did */
 		case 503:	/* information not available - this should quit but this is what the old code did (?) */
-			camel_service_disconnect_sync (service, FALSE, NULL);
+			camel_service_disconnect_sync (
+				service, FALSE, cancellable, NULL);
 			ret = -1;
 			continue;
 		case -1:	/* i/o error */
-			camel_service_disconnect_sync (service, FALSE, NULL);
+			camel_service_disconnect_sync (
+				service, FALSE, cancellable, NULL);
 			if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || retry >= 3) {
 				g_propagate_error (error, local_error);
 				return -1;
diff --git a/camel/providers/pop3/camel-pop3-store.c b/camel/providers/pop3/camel-pop3-store.c
index c97854f..c764f2d 100644
--- a/camel/providers/pop3/camel-pop3-store.c
+++ b/camel/providers/pop3/camel-pop3-store.c
@@ -327,7 +327,8 @@ pop3_store_finalize (GObject *object)
 	/* force disconnect so we dont have it run later, after we've cleaned up some stuff */
 	/* SIGH */
 
-	camel_service_disconnect_sync ((CamelService *) pop3_store, TRUE, NULL);
+	camel_service_disconnect_sync (
+		CAMEL_SERVICE (pop3_store), TRUE, NULL, NULL);
 
 	if (pop3_store->engine)
 		g_object_unref (pop3_store->engine);
@@ -414,7 +415,8 @@ pop3_store_connect_sync (CamelService *service,
 		session, service, mechanism, cancellable, error);
 
 	if (!success) {
-		camel_service_disconnect_sync (service, TRUE, NULL);
+		camel_service_disconnect_sync (
+			service, TRUE, cancellable, NULL);
 		goto exit;
 	}
 
diff --git a/camel/providers/smtp/camel-smtp-transport.c b/camel/providers/smtp/camel-smtp-transport.c
index 78364fc..661e72e 100644
--- a/camel/providers/smtp/camel-smtp-transport.c
+++ b/camel/providers/smtp/camel-smtp-transport.c
@@ -177,7 +177,8 @@ connect_to_server (CamelService *service,
 
 		if (!smtp_helo (transport, cancellable, error)) {
 			camel_service_disconnect_sync (
-				(CamelService *) transport, TRUE, NULL);
+				CAMEL_SERVICE (transport),
+				TRUE, cancellable, NULL);
 			success = FALSE;
 			goto exit;
 		}
@@ -241,7 +242,8 @@ connect_to_server (CamelService *service,
 	 * re-fetch any supported extensions. */
 	if (!smtp_helo (transport, cancellable, error)) {
 		camel_service_disconnect_sync (
-			(CamelService *) transport, TRUE, NULL);
+			CAMEL_SERVICE (transport),
+			TRUE, cancellable, NULL);
 		success = FALSE;
 	}
 
@@ -370,7 +372,8 @@ smtp_transport_connect_sync (CamelService *service,
 		}
 
 		if (!success)
-			camel_service_disconnect_sync (service, TRUE, NULL);
+			camel_service_disconnect_sync (
+				service, TRUE, cancellable, NULL);
 	}
 
 exit:
@@ -1123,7 +1126,8 @@ smtp_helo (CamelSmtpTransport *transport,
 		camel_operation_pop_message (cancellable);
 
 		camel_service_disconnect_sync (
-			(CamelService *) transport, FALSE, NULL);
+			CAMEL_SERVICE (transport),
+			FALSE, cancellable, NULL);
 
 		return FALSE;
 	}
@@ -1221,7 +1225,8 @@ smtp_mail (CamelSmtpTransport *transport,
 		g_free (cmdbuf);
 		g_prefix_error (error, _("MAIL FROM command failed: "));
 		camel_service_disconnect_sync (
-			(CamelService *) transport, FALSE, NULL);
+			CAMEL_SERVICE (transport),
+			FALSE, cancellable, NULL);
 		return FALSE;
 	}
 	g_free (cmdbuf);
@@ -1235,7 +1240,8 @@ smtp_mail (CamelSmtpTransport *transport,
 		if (respbuf == NULL) {
 			g_prefix_error (error, _("MAIL FROM command failed: "));
 			camel_service_disconnect_sync (
-				CAMEL_SERVICE (transport), FALSE, NULL);
+				CAMEL_SERVICE (transport),
+				FALSE, cancellable, NULL);
 			return FALSE;
 		}
 		if (strncmp (respbuf, "250", 3)) {
@@ -1271,7 +1277,8 @@ smtp_rcpt (CamelSmtpTransport *transport,
 		g_free (cmdbuf);
 		g_prefix_error (error, _("RCPT TO command failed: "));
 		camel_service_disconnect_sync (
-			(CamelService *) transport, FALSE, NULL);
+			CAMEL_SERVICE (transport),
+			FALSE, cancellable, NULL);
 
 		return FALSE;
 	}
@@ -1287,7 +1294,8 @@ smtp_rcpt (CamelSmtpTransport *transport,
 			g_prefix_error (
 				error, _("RCPT TO <%s> failed: "), recipient);
 			camel_service_disconnect_sync (
-				CAMEL_SERVICE (transport), FALSE, NULL);
+				CAMEL_SERVICE (transport),
+				FALSE, cancellable, NULL);
 			return FALSE;
 		}
 		if (strncmp (respbuf, "250", 3)) {
@@ -1338,7 +1346,8 @@ smtp_data (CamelSmtpTransport *transport,
 		g_free (cmdbuf);
 		g_prefix_error (error, _("DATA command failed: "));
 		camel_service_disconnect_sync (
-			(CamelService *) transport, FALSE, NULL);
+			CAMEL_SERVICE (transport),
+			FALSE, cancellable, NULL);
 		return FALSE;
 	}
 	g_free (cmdbuf);
@@ -1348,7 +1357,8 @@ smtp_data (CamelSmtpTransport *transport,
 	if (respbuf == NULL) {
 		g_prefix_error (error, _("DATA command failed: "));
 		camel_service_disconnect_sync (
-			CAMEL_SERVICE (transport), FALSE, NULL);
+			CAMEL_SERVICE (transport),
+			FALSE, cancellable, NULL);
 		return FALSE;
 	}
 	if (strncmp (respbuf, "354", 3) != 0) {
@@ -1420,7 +1430,8 @@ smtp_data (CamelSmtpTransport *transport,
 		g_object_unref (filtered_stream);
 
 		camel_service_disconnect_sync (
-			(CamelService *) transport, FALSE, NULL);
+			CAMEL_SERVICE (transport),
+			FALSE, cancellable, NULL);
 		return FALSE;
 	}
 
@@ -1436,7 +1447,8 @@ smtp_data (CamelSmtpTransport *transport,
 		cancellable, error) == -1) {
 		g_prefix_error (error, _("DATA command failed: "));
 		camel_service_disconnect_sync (
-			(CamelService *) transport, FALSE, NULL);
+			CAMEL_SERVICE (transport),
+			FALSE, cancellable, NULL);
 		return FALSE;
 	}
 
@@ -1449,7 +1461,8 @@ smtp_data (CamelSmtpTransport *transport,
 		if (respbuf == NULL) {
 			g_prefix_error (error, _("DATA command failed: "));
 			camel_service_disconnect_sync (
-				CAMEL_SERVICE (transport), FALSE, NULL);
+				CAMEL_SERVICE (transport),
+				FALSE, cancellable, NULL);
 			return FALSE;
 		}
 		if (strncmp (respbuf, "250", 3) != 0) {
@@ -1482,7 +1495,8 @@ smtp_rset (CamelSmtpTransport *transport,
 		g_free (cmdbuf);
 		g_prefix_error (error, _("RSET command failed: "));
 		camel_service_disconnect_sync (
-			(CamelService *) transport, FALSE, NULL);
+			CAMEL_SERVICE (transport),
+			FALSE, cancellable, NULL);
 		return FALSE;
 	}
 	g_free (cmdbuf);
@@ -1496,7 +1510,8 @@ smtp_rset (CamelSmtpTransport *transport,
 		if (respbuf == NULL) {
 			g_prefix_error (error, _("RSET command failed: "));
 			camel_service_disconnect_sync (
-				CAMEL_SERVICE (transport), FALSE, NULL);
+				CAMEL_SERVICE (transport),
+				FALSE, cancellable, NULL);
 			return FALSE;
 		}
 		if (strncmp (respbuf, "250", 3) != 0) {
diff --git a/docs/reference/camel/camel-sections.txt b/docs/reference/camel/camel-sections.txt
index 4e9c4b0..d9bf763 100644
--- a/docs/reference/camel/camel-sections.txt
+++ b/docs/reference/camel/camel-sections.txt
@@ -1956,10 +1956,11 @@ camel_sexp_to_sql_sexp
 CamelService
 CAMEL_SERVICE_ERROR
 CamelServiceError
-CamelServiceConnectionStatus
 CamelServiceAuthType
 camel_service_migrate_files
 camel_service_new_camel_url
+CamelServiceConnectionStatus
+camel_service_get_connection_status
 camel_service_get_display_name
 camel_service_set_display_name
 camel_service_get_password
@@ -1972,13 +1973,15 @@ camel_service_get_session
 camel_service_get_settings
 camel_service_set_settings
 camel_service_get_uid
-camel_service_cancel_connect
-camel_service_connect_sync
-camel_service_disconnect_sync
-camel_service_get_connection_status
 CamelServiceLock
 camel_service_lock
 camel_service_unlock
+camel_service_connect_sync
+camel_service_connect
+camel_service_connect_finish
+camel_service_disconnect_sync
+camel_service_disconnect
+camel_service_disconnect_finish
 CamelAuthenticationResult
 camel_service_authenticate_sync
 camel_service_authenticate



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]