CamelNetworkService: Add a "connectable" property (GSocketConnectable).
[platform/upstream/evolution-data-server.git] / camel / camel-service.c
index b015a91..ad4cfb8 100644 (file)
 #include <glib/gstdio.h>
 #include <glib/gi18n-lib.h>
 
-#include <libedataserver/e-data-server-util.h>
-
 #include "camel-debug.h"
+#include "camel-enumtypes.h"
+#include "camel-local-settings.h"
+#include "camel-network-service.h"
+#include "camel-network-settings.h"
 #include "camel-operation.h"
 #include "camel-service.h"
 #include "camel-session.h"
        (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 */
+       GWeakRef session;
+
+       CamelSettings *settings;
+       GMutex settings_lock;
 
        CamelProvider *provider;
-       CamelURL *url;
 
+       gchar *display_name;
        gchar *user_data_dir;
+       gchar *user_cache_dir;
        gchar *uid;
+       gchar *password;
 
-       GCancellable *connect_op;
+       GMutex connection_lock;
+       ConnectionOp *connection_op;
        CamelServiceConnectionStatus status;
 
-       GStaticRecMutex connect_lock;   /* for locking connection operations */
-       GStaticMutex connect_op_lock;   /* for locking the connection_op */
+       gboolean network_service_inited;
+};
+
+/* 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 {
        GList *auth_types;
+       gchar *auth_mechanism;
+       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,
        PROP_SESSION,
-       PROP_UID,
-       PROP_URL
+       PROP_SETTINGS,
+       PROP_UID
 };
 
 /* Forward Declarations */
+void camel_network_service_init (CamelNetworkService *service);
 static void camel_service_initable_init (GInitableIface *interface);
 
 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)
 {
        g_list_free (async_context->auth_types);
 
+       g_free (async_context->auth_mechanism);
+
        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)
 {
@@ -110,8 +326,7 @@ service_find_old_data_dir (CamelService *service)
        gchar *old_data_dir;
 
        provider = camel_service_get_provider (service);
-       session = camel_service_get_session (service);
-       url = camel_service_get_camel_url (service);
+       url = camel_service_new_camel_url (service);
 
        allows_host = CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_HOST);
        allows_user = CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_USER);
@@ -158,15 +373,19 @@ service_find_old_data_dir (CamelService *service)
                }
        }
 
-       if (needs_path) {
+       if (needs_path && url->path) {
                if (*url->path != '/')
                        g_string_append_c (path, '/');
                g_string_append (path, url->path);
        }
 
+       session = camel_service_ref_session (service);
+
        base_dir = camel_session_get_user_data_dir (session);
        old_data_dir = g_build_filename (base_dir, path->str, NULL);
 
+       g_object_unref (session);
+
        g_string_free (path, TRUE);
 
        if (!g_file_test (old_data_dir, G_FILE_TEST_IS_DIR)) {
@@ -174,9 +393,124 @@ service_find_old_data_dir (CamelService *service)
                old_data_dir = NULL;
        }
 
+       camel_url_free (url);
+
        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_ref_session (service);
+
+       /* Prioritize ahead of GTK+ redraws. */
+       camel_session_idle_add (
+               session, G_PRIORITY_HIGH_IDLE,
+               service_notify_connection_status_cb,
+               g_object_ref (service),
+               (GDestroyNotify) g_object_unref);
+
+       g_object_unref (session);
+}
+
+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)
@@ -192,12 +526,8 @@ service_set_session (CamelService *service,
                      CamelSession *session)
 {
        g_return_if_fail (CAMEL_IS_SESSION (session));
-       g_return_if_fail (service->priv->session == NULL);
-
-       service->priv->session = session;
 
-       g_object_add_weak_pointer (
-               G_OBJECT (session), &service->priv->session);
+       g_weak_ref_set (&service->priv->session, session);
 }
 
 static void
@@ -211,22 +541,24 @@ service_set_uid (CamelService *service,
 }
 
 static void
-service_set_url (CamelService *service,
-                 CamelURL *url)
-{
-       g_return_if_fail (url != NULL);
-       g_return_if_fail (service->priv->url == NULL);
-
-       service->priv->url = camel_url_copy (url);
-}
-
-static void
 service_set_property (GObject *object,
                       guint property_id,
                       const GValue *value,
                       GParamSpec *pspec)
 {
        switch (property_id) {
+               case PROP_DISPLAY_NAME:
+                       camel_service_set_display_name (
+                               CAMEL_SERVICE (object),
+                               g_value_get_string (value));
+                       return;
+
+               case PROP_PASSWORD:
+                       camel_service_set_password (
+                               CAMEL_SERVICE (object),
+                               g_value_get_string (value));
+                       return;
+
                case PROP_PROVIDER:
                        service_set_provider (
                                CAMEL_SERVICE (object),
@@ -239,16 +571,16 @@ service_set_property (GObject *object,
                                g_value_get_object (value));
                        return;
 
-               case PROP_UID:
-                       service_set_uid (
+               case PROP_SETTINGS:
+                       camel_service_set_settings (
                                CAMEL_SERVICE (object),
-                               g_value_get_string (value));
+                               g_value_get_object (value));
                        return;
 
-               case PROP_URL:
-                       service_set_url (
+               case PROP_UID:
+                       service_set_uid (
                                CAMEL_SERVICE (object),
-                               g_value_get_boxed (value));
+                               g_value_get_string (value));
                        return;
        }
 
@@ -262,6 +594,24 @@ 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 (
+                               CAMEL_SERVICE (object)));
+                       return;
+
+               case PROP_PASSWORD:
+                       g_value_set_string (
+                               value, camel_service_get_password (
+                               CAMEL_SERVICE (object)));
+                       return;
+
                case PROP_PROVIDER:
                        g_value_set_pointer (
                                value, camel_service_get_provider (
@@ -269,20 +619,20 @@ service_get_property (GObject *object,
                        return;
 
                case PROP_SESSION:
-                       g_value_set_object (
-                               value, camel_service_get_session (
+                       g_value_take_object (
+                               value, camel_service_ref_session (
                                CAMEL_SERVICE (object)));
                        return;
 
-               case PROP_UID:
-                       g_value_set_string (
-                               value, camel_service_get_uid (
+               case PROP_SETTINGS:
+                       g_value_take_object (
+                               value, camel_service_ref_settings (
                                CAMEL_SERVICE (object)));
                        return;
 
-               case PROP_URL:
-                       g_value_set_boxed (
-                               value, camel_service_get_url (
+               case PROP_UID:
+                       g_value_set_string (
+                               value, camel_service_get_uid (
                                CAMEL_SERVICE (object)));
                        return;
        }
@@ -297,10 +647,11 @@ service_dispose (GObject *object)
 
        priv = CAMEL_SERVICE_GET_PRIVATE (object);
 
-       if (priv->session != NULL) {
-               g_object_remove_weak_pointer (
-                       G_OBJECT (priv->session), &priv->session);
-               priv->session = NULL;
+       g_weak_ref_set (&priv->session, NULL);
+
+       if (priv->settings != NULL) {
+               g_object_unref (priv->settings);
+               priv->settings = NULL;
        }
 
        /* Chain up to parent's dispose() method. */
@@ -318,14 +669,17 @@ service_finalize (GObject *object)
                CAMEL_SERVICE_GET_CLASS (object)->disconnect_sync (
                        CAMEL_SERVICE (object), TRUE, NULL, NULL);
 
-       if (priv->url != NULL)
-               camel_url_free (priv->url);
+       g_mutex_clear (&priv->settings_lock);
 
+       g_free (priv->display_name);
        g_free (priv->user_data_dir);
+       g_free (priv->user_cache_dir);
        g_free (priv->uid);
+       g_free (priv->password);
 
-       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_clear (&priv->connection_lock);
 
        /* Chain up to parent's finalize() method. */
        G_OBJECT_CLASS (camel_service_parent_class)->finalize (object);
@@ -336,20 +690,30 @@ service_constructed (GObject *object)
 {
        CamelService *service;
        CamelSession *session;
-       const gchar *base_data_dir;
+       const gchar *base_dir;
        const gchar *uid;
 
        /* Chain up to parent's constructed() method. */
        G_OBJECT_CLASS (camel_service_parent_class)->constructed (object);
 
        service = CAMEL_SERVICE (object);
-       session = camel_service_get_session (service);
+       session = camel_service_ref_session (service);
 
        uid = camel_service_get_uid (service);
-       base_data_dir = camel_session_get_user_data_dir (session);
 
-       service->priv->user_data_dir =
-               g_build_filename (base_data_dir, uid, NULL);
+       base_dir = camel_session_get_user_data_dir (session);
+       service->priv->user_data_dir = g_build_filename (base_dir, uid, NULL);
+
+       base_dir = camel_session_get_user_cache_dir (session);
+       service->priv->user_cache_dir = g_build_filename (base_dir, uid, NULL);
+
+       g_object_unref (session);
+
+       /* The CamelNetworkService interface needs initialization. */
+       if (CAMEL_IS_NETWORK_SERVICE (service)) {
+               camel_network_service_init (CAMEL_NETWORK_SERVICE (service));
+               service->priv->network_service_inited = TRUE;
+       }
 }
 
 static gchar *
@@ -363,20 +727,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
@@ -385,8 +742,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;
 }
 
@@ -399,140 +755,272 @@ service_query_auth_types_sync (CamelService *service,
 }
 
 static void
-service_query_auth_types_thread (GSimpleAsyncResult *simple,
-                                 GObject *object,
-                                 GCancellable *cancellable)
+service_connect_thread (GSimpleAsyncResult *simple,
+                        GObject *object,
+                        GCancellable *cancellable)
 {
-       AsyncContext *async_context;
+       CamelService *service;
+       CamelServiceClass *class;
        GError *error = NULL;
 
-       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+       /* Note we call the class method directly here. */
 
-       async_context->auth_types = camel_service_query_auth_types_sync (
-               CAMEL_SERVICE (object), cancellable, &error);
+       service = CAMEL_SERVICE (object);
 
-       if (error != NULL) {
-               g_simple_async_result_set_from_error (simple, error);
-               g_error_free (error);
-       }
+       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_query_auth_types (CamelService *service,
-                          gint io_priority,
-                          GCancellable *cancellable,
-                          GAsyncReadyCallback callback,
-                          gpointer user_data)
+service_connect (CamelService *service,
+                 gint io_priority,
+                 GCancellable *cancellable,
+                 GAsyncReadyCallback callback,
+                 gpointer user_data)
 {
        GSimpleAsyncResult *simple;
-       AsyncContext *async_context;
-
-       async_context = g_slice_new0 (AsyncContext);
 
        simple = g_simple_async_result_new (
-               G_OBJECT (service), callback,
-               user_data, service_query_auth_types);
+               G_OBJECT (service), callback, user_data, service_connect);
 
-       g_simple_async_result_set_op_res_gpointer (
-               simple, async_context, (GDestroyNotify) async_context_free);
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
 
        g_simple_async_result_run_in_thread (
-               simple, service_query_auth_types_thread,
-               io_priority, cancellable);
+               simple, service_connect_thread, io_priority, cancellable);
 
        g_object_unref (simple);
 }
 
-static GList *
-service_query_auth_types_finish (CamelService *service,
-                                 GAsyncResult *result,
-                                 GError **error)
+static gboolean
+service_connect_finish (CamelService *service,
+                        GAsyncResult *result,
+                        GError **error)
 {
        GSimpleAsyncResult *simple;
-       AsyncContext *async_context;
 
        g_return_val_if_fail (
                g_simple_async_result_is_valid (
-               result, G_OBJECT (service),
-               service_query_auth_types), NULL);
+               result, G_OBJECT (service), service_connect), FALSE);
 
        simple = G_SIMPLE_ASYNC_RESULT (result);
-       async_context = g_simple_async_result_get_op_res_gpointer (simple);
 
-       if (g_simple_async_result_propagate_error (simple, error))
-               return NULL;
-
-       return g_list_copy (async_context->auth_types);
+       /* Assume success unless a GError is set. */
+       return !g_simple_async_result_propagate_error (simple, error);
 }
 
-static gboolean
-service_initable_init (GInitable *initable,
-                       GCancellable *cancellable,
-                       GError **error)
+static void
+service_disconnect_thread (GSimpleAsyncResult *simple,
+                           GObject *object,
+                           GCancellable *cancellable)
 {
-       CamelProvider *provider;
        CamelService *service;
-       CamelURL *url;
-       gboolean success = FALSE;
-       const gchar *new_data_dir;
-       gchar *old_data_dir;
-       gchar *url_string;
+       CamelServiceClass *class;
+       gboolean clean;
+       GError *error = NULL;
 
-       service = CAMEL_SERVICE (initable);
-       url = camel_service_get_camel_url (service);
-       provider = camel_service_get_provider (service);
+       /* Note we call the class method directly here. */
 
-       url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
+       service = CAMEL_SERVICE (object);
+       clean = g_simple_async_result_get_op_res_gboolean (simple);
 
-       if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER)) {
-               if (url->user == NULL || *url->user == '\0') {
-                       g_set_error (
-                               error, CAMEL_SERVICE_ERROR,
-                               CAMEL_SERVICE_ERROR_URL_INVALID,
-                               _("URL '%s' needs a user component"),
-                               url_string);
-                       goto exit;
-               }
-       }
+       class = CAMEL_SERVICE_GET_CLASS (service);
+       g_return_if_fail (class->disconnect_sync != NULL);
 
-       if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST)) {
-               if (url->host == NULL || *url->host == '\0') {
-                       g_set_error (
-                               error, CAMEL_SERVICE_ERROR,
-                               CAMEL_SERVICE_ERROR_URL_INVALID,
-                               _("URL '%s' needs a host component"),
-                               url_string);
-                       goto exit;
-               }
-       }
+       class->disconnect_sync (service, clean, cancellable, &error);
 
-       if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH)) {
-               if (url->path == NULL || *url->path == '\0') {
-                       g_set_error (
-                               error, CAMEL_SERVICE_ERROR,
-                               CAMEL_SERVICE_ERROR_URL_INVALID,
-                               _("URL '%s' needs a path component"),
-                               url_string);
-                       goto exit;
-               }
-       }
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+}
 
-       new_data_dir = camel_service_get_user_data_dir (service);
-       old_data_dir = service_find_old_data_dir (service);
+static void
+service_disconnect (CamelService *service,
+                    gboolean clean,
+                    gint io_priority,
+                    GCancellable *cancellable,
+                    GAsyncReadyCallback callback,
+                    gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
 
-       /* If the old data directory name exists, try renaming
-        * it to the new data directory.  Failure is non-fatal. */
-       if (old_data_dir != NULL) {
-               g_rename (old_data_dir, new_data_dir);
-               g_free (old_data_dir);
-       }
+       simple = g_simple_async_result_new (
+               G_OBJECT (service), callback, user_data, service_disconnect);
 
-       success = TRUE;
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
 
-exit:
-       g_free (url_string);
+       g_simple_async_result_set_op_res_gboolean (simple, clean);
 
-       return success;
+       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)
+{
+       AsyncContext *async_context;
+       GError *error = NULL;
+
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       async_context->auth_result = camel_service_authenticate_sync (
+               CAMEL_SERVICE (object), async_context->auth_mechanism,
+               cancellable, &error);
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+}
+
+static void
+service_authenticate (CamelService *service,
+                      const gchar *mechanism,
+                      gint io_priority,
+                      GCancellable *cancellable,
+                      GAsyncReadyCallback callback,
+                      gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       async_context = g_slice_new0 (AsyncContext);
+       async_context->auth_mechanism = g_strdup (mechanism);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (service), callback, user_data, service_authenticate);
+
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+       g_simple_async_result_set_op_res_gpointer (
+               simple, async_context, (GDestroyNotify) async_context_free);
+
+       g_simple_async_result_run_in_thread (
+               simple, service_authenticate_thread, io_priority, cancellable);
+
+       g_object_unref (simple);
+}
+
+static CamelAuthenticationResult
+service_authenticate_finish (CamelService *service,
+                             GAsyncResult *result,
+                             GError **error)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (service), service_authenticate),
+               CAMEL_AUTHENTICATION_REJECTED);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       if (g_simple_async_result_propagate_error (simple, error))
+               return CAMEL_AUTHENTICATION_ERROR;
+
+       return async_context->auth_result;
+}
+
+static void
+service_query_auth_types_thread (GSimpleAsyncResult *simple,
+                                 GObject *object,
+                                 GCancellable *cancellable)
+{
+       AsyncContext *async_context;
+       GError *error = NULL;
+
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       async_context->auth_types = camel_service_query_auth_types_sync (
+               CAMEL_SERVICE (object), cancellable, &error);
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+}
+
+static void
+service_query_auth_types (CamelService *service,
+                          gint io_priority,
+                          GCancellable *cancellable,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       async_context = g_slice_new0 (AsyncContext);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (service), callback,
+               user_data, service_query_auth_types);
+
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+       g_simple_async_result_set_op_res_gpointer (
+               simple, async_context, (GDestroyNotify) async_context_free);
+
+       g_simple_async_result_run_in_thread (
+               simple, service_query_auth_types_thread,
+               io_priority, cancellable);
+
+       g_object_unref (simple);
+}
+
+static GList *
+service_query_auth_types_finish (CamelService *service,
+                                 GAsyncResult *result,
+                                 GError **error)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (service),
+               service_query_auth_types), NULL);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       if (g_simple_async_result_propagate_error (simple, error))
+               return NULL;
+
+       return g_list_copy (async_context->auth_types);
+}
+
+static gboolean
+service_initable_init (GInitable *initable,
+                       GCancellable *cancellable,
+                       GError **error)
+{
+       /* Nothing to do here, but we may need add something in the future.
+        * For now this is a placeholder so subclasses can safely chain up. */
+
+       return TRUE;
 }
 
 static void
@@ -549,17 +1037,59 @@ camel_service_class_init (CamelServiceClass *class)
        object_class->finalize = service_finalize;
        object_class->constructed = service_constructed;
 
+       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;
        class->query_auth_types_finish = service_query_auth_types_finish;
 
        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",
+                       "Display Name",
+                       "The display name for the service",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_PASSWORD,
+               g_param_spec_string (
+                       "password",
+                       "Password",
+                       "The password for the service",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
                PROP_PROVIDER,
                g_param_spec_pointer (
                        "provider",
@@ -583,6 +1113,18 @@ camel_service_class_init (CamelServiceClass *class)
 
        g_object_class_install_property (
                object_class,
+               PROP_SETTINGS,
+               g_param_spec_object (
+                       "settings",
+                       "Settings",
+                       "A CamelSettings instance",
+                       CAMEL_TYPE_SETTINGS,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
                PROP_UID,
                g_param_spec_string (
                        "uid",
@@ -592,18 +1134,6 @@ camel_service_class_init (CamelServiceClass *class)
                        G_PARAM_READWRITE |
                        G_PARAM_CONSTRUCT_ONLY |
                        G_PARAM_STATIC_STRINGS));
-
-       g_object_class_install_property (
-               object_class,
-               PROP_URL,
-               g_param_spec_boxed (
-                       "url",
-                       "URL",
-                       "The CamelURL for the service",
-                       CAMEL_TYPE_URL,
-                       G_PARAM_READWRITE |
-                       G_PARAM_CONSTRUCT_ONLY |
-                       G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -617,68 +1147,265 @@ camel_service_init (CamelService *service)
 {
        service->priv = CAMEL_SERVICE_GET_PRIVATE (service);
 
+       g_mutex_init (&service->priv->settings_lock);
+       g_mutex_init (&service->priv->connection_lock);
        service->priv->status = CAMEL_SERVICE_DISCONNECTED;
+}
 
-       g_static_rec_mutex_init (&service->priv->connect_lock);
-       g_static_mutex_init (&service->priv->connect_op_lock);
+G_DEFINE_QUARK (camel-service-error-quark, camel_service_error)
+
+/**
+ * camel_service_migrate_files:
+ * @service: a #CamelService
+ *
+ * Performs any necessary file migrations for @service.  This should be
+ * called after installing or configuring the @service's #CamelSettings,
+ * since it requires building a URL string for @service.
+ *
+ * Since: 3.4
+ **/
+void
+camel_service_migrate_files (CamelService *service)
+{
+       const gchar *new_data_dir;
+       gchar *old_data_dir;
+
+       g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+       new_data_dir = camel_service_get_user_data_dir (service);
+       old_data_dir = service_find_old_data_dir (service);
+
+       /* If the old data directory name exists, try renaming
+        * it to the new data directory.  Failure is non-fatal. */
+       if (old_data_dir != NULL) {
+               g_rename (old_data_dir, new_data_dir);
+               g_free (old_data_dir);
+       }
 }
 
-GQuark
-camel_service_error_quark (void)
+/**
+ * camel_service_new_camel_url:
+ * @service: a #CamelService
+ *
+ * Returns a new #CamelURL representing @service.
+ * Free the returned #CamelURL with camel_url_free().
+ *
+ * Returns: a new #CamelURL
+ *
+ * Since: 3.2
+ **/
+CamelURL *
+camel_service_new_camel_url (CamelService *service)
 {
-       static GQuark quark = 0;
+       CamelURL *url;
+       CamelProvider *provider;
+       CamelSettings *settings;
+       gchar *host = NULL;
+       gchar *user = NULL;
+       gchar *path = NULL;
+       guint16 port = 0;
+
+       g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+       provider = camel_service_get_provider (service);
+       g_return_val_if_fail (provider != NULL, NULL);
+
+       settings = camel_service_ref_settings (service);
 
-       if (G_UNLIKELY (quark == 0)) {
-               const gchar *string = "camel-service-error-quark";
-               quark = g_quark_from_static_string (string);
+       /* Allocate as camel_url_new_with_base() does. */
+       url = g_new0 (CamelURL, 1);
+
+       if (CAMEL_IS_NETWORK_SETTINGS (settings)) {
+               CamelNetworkSettings *network_settings;
+
+               network_settings = CAMEL_NETWORK_SETTINGS (settings);
+               host = camel_network_settings_dup_host (network_settings);
+               port = camel_network_settings_get_port (network_settings);
+               user = camel_network_settings_dup_user (network_settings);
+       }
+
+       if (CAMEL_IS_LOCAL_SETTINGS (settings)) {
+               CamelLocalSettings *local_settings;
+
+               local_settings = CAMEL_LOCAL_SETTINGS (settings);
+               path = camel_local_settings_dup_path (local_settings);
        }
 
-       return quark;
+       camel_url_set_protocol (url, provider->protocol);
+       camel_url_set_host (url, host);
+       camel_url_set_port (url, port);
+       camel_url_set_user (url, user);
+       camel_url_set_path (url, path);
+
+       g_free (host);
+       g_free (user);
+       g_free (path);
+
+       g_object_unref (settings);
+
+       return url;
+}
+
+/**
+ * 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_cancel_connect:
+ * camel_service_get_display_name:
  * @service: a #CamelService
  *
- * 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.
+ * Returns the display name for @service, or %NULL if @service has not
+ * been given a display name.  The display name is intended for use in
+ * a user interface and should generally be given a user-defined name.
+ *
+ * Compare this with camel_service_get_name(), which returns a built-in
+ * description of the type of service (IMAP, SMTP, etc.).
+ *
+ * Returns: the display name for @service, or %NULL
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_service_get_display_name (CamelService *service)
+{
+       g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+       return service->priv->display_name;
+}
+
+/**
+ * camel_service_set_display_name:
+ * @service: a #CamelService
+ * @display_name: a valid UTF-8 string, or %NULL
+ *
+ * Assigns a UTF-8 display name to @service.  The display name is intended
+ * for use in a user interface and should generally be given a user-defined
+ * name.
+ *
+ * Compare this with camel_service_get_name(), which returns a built-in
+ * description of the type of service (IMAP, SMTP, etc.).
+ *
+ * Since: 3.2
  **/
 void
-camel_service_cancel_connect (CamelService *service)
+camel_service_set_display_name (CamelService *service,
+                                const gchar *display_name)
 {
-       CamelServiceClass *class;
+       g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+       if (g_strcmp0 (service->priv->display_name, display_name) == 0)
+               return;
+
+       if (display_name != NULL)
+               g_return_if_fail (g_utf8_validate (display_name, -1, NULL));
+
+       g_free (service->priv->display_name);
+       service->priv->display_name = g_strdup (display_name);
+
+       g_object_notify (G_OBJECT (service), "display-name");
+}
+
+/**
+ * camel_service_get_password:
+ * @service: a #CamelService
+ *
+ * Returns the password for @service.  Some SASL mechanisms use this
+ * when attempting to authenticate.
+ *
+ * Returns: the password for @service
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_service_get_password (CamelService *service)
+{
+       g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
 
+       return service->priv->password;
+}
+
+/**
+ * camel_service_set_password:
+ * @service: a #CamelService
+ * @password: the password for @service
+ *
+ * Sets the password for @service.  Use this function to cache the password
+ * in memory after obtaining it through camel_session_get_password().  Some
+ * SASL mechanisms use this when attempting to authenticate.
+ *
+ * Since: 3.4
+ **/
+void
+camel_service_set_password (CamelService *service,
+                            const gchar *password)
+{
        g_return_if_fail (CAMEL_IS_SERVICE (service));
 
-       class = CAMEL_SERVICE_GET_CLASS (service);
-       g_return_if_fail (class->cancel_connect != NULL);
+       if (g_strcmp0 (service->priv->password, password) == 0)
+               return;
 
-       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);
+       g_free (service->priv->password);
+       service->priv->password = g_strdup (password);
+
+       g_object_notify (G_OBJECT (service), "password");
+}
+
+/**
+ * camel_service_get_user_data_dir:
+ * @service: a #CamelService
+ *
+ * Returns the base directory under which to store user-specific data
+ * for @service.  The directory is formed by appending the directory
+ * returned by camel_session_get_user_data_dir() with the service's
+ * #CamelService:uid value.
+ *
+ * Returns: the base directory for @service
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_service_get_user_data_dir (CamelService *service)
+{
+       g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+       return service->priv->user_data_dir;
 }
 
 /**
- * camel_service_get_user_data_dir:
+ * camel_service_get_user_cache_dir:
  * @service: a #CamelService
  *
- * Returns the base directory under which to store user-specific data
+ * Returns the base directory under which to store cache data
  * for @service.  The directory is formed by appending the directory
- * returned by camel_session_get_user_data_dir() with the service's
+ * returned by camel_session_get_user_cache_dir() with the service's
  * #CamelService:uid value.
  *
- * Returns: the base directory for @service
+ * Returns: the base cache directory for @service
  *
- * Since: 3.2
+ * Since: 3.4
  **/
 const gchar *
-camel_service_get_user_data_dir (CamelService *service)
+camel_service_get_user_cache_dir (CamelService *service)
 {
        g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
 
-       return service->priv->user_data_dir;
+       return service->priv->user_cache_dir;
 }
 
 /**
@@ -700,7 +1427,6 @@ camel_service_get_name (CamelService *service,
        CamelServiceClass *class;
 
        g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
-       g_return_val_if_fail (service->priv->url, NULL);
 
        class = CAMEL_SERVICE_GET_CLASS (service);
        g_return_val_if_fail (class->get_name != NULL, NULL);
@@ -725,260 +1451,656 @@ camel_service_get_provider (CamelService *service)
 }
 
 /**
- * camel_service_get_session:
+ * camel_service_ref_session:
  * @service: a #CamelService
  *
- * Gets the #CamelSession associated with the service.
+ * Returns the #CamelSession associated with the service.
+ *
+ * The returned #CamelSession is referenced for thread-safety.  Unreference
+ * the #CamelSession with g_object_unref() when finished with it.
  *
  * Returns: the #CamelSession
+ *
+ * Since: 3.8
  **/
 CamelSession *
-camel_service_get_session (CamelService *service)
+camel_service_ref_session (CamelService *service)
 {
        g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
 
-       return CAMEL_SESSION (service->priv->session);
+       return g_weak_ref_get (&service->priv->session);
 }
 
 /**
- * camel_service_get_uid:
+ * camel_service_get_session:
  * @service: a #CamelService
  *
- * Gets the unique identifier string associated with the service.
+ * Returns the #CamelSession associated with the service.
  *
- * Returns: the UID string
+ * Note this function is not thread-safe.  The returned #CamelSession could
+ * be finalized by another thread while the caller is still using it.
  *
- * Since: 3.2
+ * Returns: the #CamelSession
+ *
+ * Deprecated: 3.8: Use camel_service_ref_session() instead.
  **/
-const gchar *
-camel_service_get_uid (CamelService *service)
+CamelSession *
+camel_service_get_session (CamelService *service)
 {
+       CamelSession *session;
+
        g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
 
-       return service->priv->uid;
+       session = camel_service_ref_session (service);
+
+       /* XXX Drop the CamelSession reference for backward-compatibility.
+        *     This is risky.  Without a reference, the CamelSession could
+        *     be finalized while the caller is still using it. */
+       if (session != NULL)
+               g_object_unref (session);
+
+       return session;
 }
 
 /**
- * camel_service_get_camel_url:
+ * camel_service_ref_settings:
  * @service: a #CamelService
  *
- * Returns the #CamelURL representing @service.
+ * Returns the #CamelSettings instance associated with the service.
  *
- * Returns: the #CamelURL representing @service
+ * The returned #CamelSettings is referenced for thread-safety and must
+ * be unreferenced with g_object_unref() when finished with it.
  *
- * Since: 3.2
+ * Returns: the #CamelSettings
+ *
+ * Since: 3.6
  **/
-CamelURL *
-camel_service_get_camel_url (CamelService *service)
+CamelSettings *
+camel_service_ref_settings (CamelService *service)
 {
+       CamelSettings *settings;
+
        g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
 
-       return service->priv->url;
+       /* Every service should have a settings object. */
+       g_return_val_if_fail (service->priv->settings != NULL, NULL);
+
+       g_mutex_lock (&service->priv->settings_lock);
+
+       settings = g_object_ref (service->priv->settings);
+
+       g_mutex_unlock (&service->priv->settings_lock);
+
+       return settings;
 }
 
 /**
- * camel_service_get_url:
+ * camel_service_set_settings:
  * @service: a #CamelService
+ * @settings: an instance derviced from #CamelSettings, or %NULL
  *
- * Gets the URL representing @service. The returned URL must be
- * freed when it is no longer needed. For security reasons, this
- * routine does not return the password.
+ * Associates a new #CamelSettings instance with the service.
+ * The @settings instance must match the settings type defined in
+ * #CamelServiceClass.  If @settings is %NULL, a new #CamelSettings
+ * instance of the appropriate type is created with all properties
+ * set to defaults.
  *
- * Returns: the URL representing @service
+ * Since: 3.2
  **/
-gchar *
-camel_service_get_url (CamelService *service)
+void
+camel_service_set_settings (CamelService *service,
+                            CamelSettings *settings)
 {
-       CamelURL *url;
+       CamelServiceClass *class;
 
-       g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+       g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+       class = CAMEL_SERVICE_GET_CLASS (service);
+
+       if (settings != NULL) {
+               g_return_if_fail (
+                       g_type_is_a (
+                               G_OBJECT_TYPE (settings),
+                               class->settings_type));
+               g_object_ref (settings);
+
+       } else {
+               g_return_if_fail (
+                       g_type_is_a (
+                               class->settings_type,
+                               CAMEL_TYPE_SETTINGS));
+               settings = g_object_new (class->settings_type, NULL);
+       }
+
+       g_mutex_lock (&service->priv->settings_lock);
+
+       if (service->priv->settings != NULL)
+               g_object_unref (service->priv->settings);
 
-       url = camel_service_get_camel_url (service);
+       service->priv->settings = settings;  /* takes ownership */
+
+       g_mutex_unlock (&service->priv->settings_lock);
+
+       /* If the service is a CamelNetworkService, it needs to
+        * replace its GSocketConnectable for the new settings. */
+       if (service->priv->network_service_inited)
+               camel_network_service_set_connectable (
+                       CAMEL_NETWORK_SERVICE (service), NULL);
+
+       g_object_notify (G_OBJECT (service), "settings");
+}
+
+/**
+ * camel_service_get_uid:
+ * @service: a #CamelService
+ *
+ * Gets the unique identifier string associated with the service.
+ *
+ * Returns: the UID string
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_service_get_uid (CamelService *service)
+{
+       g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
 
-       return camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
+       return service->priv->uid;
 }
 
 /**
  * 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);
-       g_return_val_if_fail (service->priv->url != 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);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (service), callback,
+               user_data, camel_service_connect);
 
-       camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
+       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);
+       g_mutex_unlock (&service->priv->connection_lock);
+
+       g_object_unref (simple);
+}
 
-       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_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;
 
-       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_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (service), camel_service_connect), FALSE);
 
-       camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
+       simple = G_SIMPLE_ASYNC_RESULT (result);
 
-       return ret;
+       /* 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
+ *
+ * Since: 3.6
  **/
 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);
 
+       closure = async_closure_new ();
+
+       camel_service_disconnect (
+               service, clean, G_PRIORITY_DEFAULT,
+               cancellable, async_closure_callback, closure);
+
+       result = async_closure_wait (closure);
+
+       success = camel_service_disconnect_finish (service, result, error);
+
+       async_closure_free (closure);
+
+       return success;
+}
+
+/**
+ * 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
+ *
+ * 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.
+ *
+ * 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: 3.6
+ **/
+void
+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));
+
        class = CAMEL_SERVICE_GET_CLASS (service);
-       g_return_val_if_fail (class->disconnect_sync != NULL, FALSE);
-
-       camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
-
-       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);
+       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));
+                       break;
+
+               /* 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;
+
+               /* If we're already disconnected, just report success. */
+               case CAMEL_SERVICE_DISCONNECTED:
+                       g_simple_async_result_complete_in_idle (simple);
+                       break;
+
+               default:
+                       g_warn_if_reached ();
        }
 
-       camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
+       g_mutex_unlock (&service->priv->connection_lock);
 
-       service->priv->status = CAMEL_SERVICE_DISCONNECTED;
+       g_object_unref (simple);
+}
+
+/**
+ * camel_service_disconnect_finish:
+ * @service: a #CamelService
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_service_disconnect().
+ *
+ * Returns: %TRUE if the connection was severed or %FALSE otherwise
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_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), camel_service_disconnect), FALSE);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
 
-       return res;
+       /* Assume success unless a GError is set. */
+       return !g_simple_async_result_propagate_error (simple, error);
 }
 
 /**
- * camel_service_get_connection_status:
+ * camel_service_authenticate_sync:
  * @service: a #CamelService
+ * @mechanism: a SASL mechanism name, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
  *
- * Returns the connection status for @service.
+ * Attempts to authenticate @service using @mechanism and, if necessary,
+ * @service's #CamelService:password property.  The function makes only
+ * ONE attempt at authentication and does not loop.
  *
- * Returns: the connection status
+ * If the authentication attempt completed and the server accepted the
+ * credentials, the function returns #CAMEL_AUTHENTICATION_ACCEPTED.
  *
- * Since: 3.2
+ * If the authentication attempt completed but the server rejected the
+ * credentials, the function returns #CAMEL_AUTHENTICATION_REJECTED.
+ *
+ * If the authentication attempt failed to complete due to a network
+ * communication issue or some other mishap, the function sets @error
+ * and returns #CAMEL_AUTHENTICATION_ERROR.
+ *
+ * Generally this function should only be called from a #CamelSession
+ * subclass in order to implement its own authentication loop.
+ *
+ * Returns: the authentication result
+ *
+ * Since: 3.4
  **/
-CamelServiceConnectionStatus
-camel_service_get_connection_status (CamelService *service)
+CamelAuthenticationResult
+camel_service_authenticate_sync (CamelService *service,
+                                 const gchar *mechanism,
+                                 GCancellable *cancellable,
+                                 GError **error)
 {
+       CamelServiceClass *class;
+       CamelAuthenticationResult result;
+
        g_return_val_if_fail (
-               CAMEL_IS_SERVICE (service), CAMEL_SERVICE_DISCONNECTED);
+               CAMEL_IS_SERVICE (service),
+               CAMEL_AUTHENTICATION_REJECTED);
 
-       return service->priv->status;
+       class = CAMEL_SERVICE_GET_CLASS (service);
+       g_return_val_if_fail (
+               class->authenticate_sync != NULL,
+               CAMEL_AUTHENTICATION_REJECTED);
+
+       result = class->authenticate_sync (
+               service, mechanism, cancellable, error);
+       CAMEL_CHECK_GERROR (
+               service, authenticate_sync,
+               result != CAMEL_AUTHENTICATION_ERROR, error);
+
+       return result;
 }
 
 /**
- * camel_service_lock:
+ * camel_service_authenticate:
  * @service: a #CamelService
- * @lock: lock type to lock
+ * @mechanism: a SASL mechanism name, or %NULL
+ * @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
  *
- * Locks @service's @lock. Unlock it with camel_service_unlock().
+ * Asynchronously attempts to authenticate @service using @mechanism and,
+ * if necessary, @service's #CamelService:password property.  The function
+ * makes only ONE attempt at authentication and does not loop.
  *
- * Since: 2.32
+ * Generally this function should only be called from a #CamelSession
+ * subclass in order to implement its own authentication loop.
+ *
+ * When the operation is finished, @callback will be called.  You can
+ * then call camel_service_authenticate_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.4
  **/
 void
-camel_service_lock (CamelService *service,
-                    CamelServiceLock lock)
+camel_service_authenticate (CamelService *service,
+                            const gchar *mechanism,
+                            gint io_priority,
+                            GCancellable *cancellable,
+                            GAsyncReadyCallback callback,
+                            gpointer user_data)
 {
+       CamelServiceClass *class;
+
        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);
-                       break;
-               case CAMEL_SERVICE_CONNECT_OP_LOCK:
-                       g_static_mutex_lock (&service->priv->connect_op_lock);
-                       break;
-               default:
-                       g_return_if_reached ();
-       }
+       class = CAMEL_SERVICE_GET_CLASS (service);
+       g_return_if_fail (class->authenticate != NULL);
+
+       class->authenticate (
+               service, mechanism, io_priority,
+               cancellable, callback, user_data);
 }
 
 /**
- * camel_service_unlock:
+ * camel_service_authenticate_finish:
  * @service: a #CamelService
- * @lock: lock type to unlock
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_service_authenticate().
+ *
+ * If the authentication attempt completed and the server accepted the
+ * credentials, the function returns #CAMEL_AUTHENTICATION_ACCEPTED.
  *
- * Unlocks @service's @lock, previously locked with camel_service_lock().
+ * If the authentication attempt completed but the server rejected the
+ * credentials, the function returns #CAMEL_AUTHENTICATION_REJECTED.
  *
- * Since: 2.32
+ * If the authentication attempt failed to complete due to a network
+ * communication issue or some other mishap, the function sets @error
+ * and returns #CAMEL_AUTHENTICATION_ERROR.
+ *
+ * Returns: the authentication result
+ *
+ * Since: 3.4
  **/
-void
-camel_service_unlock (CamelService *service,
-                      CamelServiceLock lock)
+CamelAuthenticationResult
+camel_service_authenticate_finish (CamelService *service,
+                                   GAsyncResult *result,
+                                   GError **error)
 {
-       g_return_if_fail (CAMEL_IS_SERVICE (service));
+       CamelServiceClass *class;
 
-       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 (
+               CAMEL_IS_SERVICE (service),
+               CAMEL_AUTHENTICATION_REJECTED);
+       g_return_val_if_fail (
+               G_IS_ASYNC_RESULT (result),
+               CAMEL_AUTHENTICATION_REJECTED);
+
+       class = CAMEL_SERVICE_GET_CLASS (service);
+       g_return_val_if_fail (
+               class->authenticate_finish,
+               CAMEL_AUTHENTICATION_REJECTED);
+
+       return class->authenticate_finish (service, result, error);
 }
 
 /**
@@ -998,20 +2120,13 @@ camel_service_query_auth_types_sync (CamelService *service,
                                      GError **error)
 {
        CamelServiceClass *class;
-       GList *list;
 
        g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
 
        class = CAMEL_SERVICE_GET_CLASS (service);
        g_return_val_if_fail (class->query_auth_types_sync != NULL, NULL);
 
-       /* Note that we get the connect lock here, which means the
-        * callee must not call the connect functions itself. */
-       camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
-       list = class->query_auth_types_sync (service, cancellable, error);
-       camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
-
-       return list;
+       return class->query_auth_types_sync (service, cancellable, error);
 }
 
 /**