Add a new "evolution-source-registry" D-Bus service.
authorMatthew Barnes <mbarnes@redhat.com>
Thu, 29 Sep 2011 20:08:20 +0000 (16:08 -0400)
committerMatthew Barnes <mbarnes@redhat.com>
Sun, 3 Jun 2012 23:51:07 +0000 (19:51 -0400)
This new service manages data source key files and serves them to
clients, through the ESource and ESourceRegistry client-side APIs.

31 files changed:
configure.ac
docs/reference/libebackend/libebackend-docs.xml
docs/reference/libebackend/libebackend-sections.txt
docs/reference/libebackend/libebackend.types
libebackend/Makefile.am
libebackend/e-authentication-mediator.c [new file with mode: 0644]
libebackend/e-authentication-mediator.h [new file with mode: 0644]
libebackend/e-authentication-session.c [new file with mode: 0644]
libebackend/e-authentication-session.h [new file with mode: 0644]
libebackend/e-backend-enums.h
libebackend/e-collection-backend-factory.c [new file with mode: 0644]
libebackend/e-collection-backend-factory.h [new file with mode: 0644]
libebackend/e-collection-backend.c [new file with mode: 0644]
libebackend/e-collection-backend.h [new file with mode: 0644]
libebackend/e-dbus-server.c
libebackend/e-server-side-source.c [new file with mode: 0644]
libebackend/e-server-side-source.h [new file with mode: 0644]
libebackend/e-source-registry-server.c [new file with mode: 0644]
libebackend/e-source-registry-server.h [new file with mode: 0644]
libebackend/libebackend.pc.in
services/Makefile.am
services/evolution-addressbook-factory/Makefile.am
services/evolution-addressbook-factory/evolution-addressbook-factory-migrate-basedir.c [deleted file]
services/evolution-addressbook-factory/evolution-addressbook-factory.c
services/evolution-calendar-factory/Makefile.am
services/evolution-calendar-factory/evolution-calendar-factory.c
services/evolution-source-registry/Makefile.am [new file with mode: 0644]
services/evolution-source-registry/evolution-source-registry-migrate-basedir.c [moved from services/evolution-calendar-factory/evolution-calendar-factory-migrate-basedir.c with 82% similarity]
services/evolution-source-registry/evolution-source-registry-migrate-sources.c [new file with mode: 0644]
services/evolution-source-registry/evolution-source-registry.c [new file with mode: 0644]
services/evolution-source-registry/org.gnome.evolution.dataserver.Sources.service.in [new file with mode: 0644]

index f1acae1..acff506 100644 (file)
@@ -63,6 +63,7 @@ dnl D-Bus versioning
 dnl ******************************
 ADDRESS_BOOK_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.AddressBook3"
 CALENDAR_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.Calendar2"
+SOURCES_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.Sources0"
 
 AC_DEFINE_UNQUOTED(
        ADDRESS_BOOK_DBUS_SERVICE_NAME,
@@ -74,8 +75,14 @@ AC_DEFINE_UNQUOTED(
        ["$CALENDAR_DBUS_SERVICE_NAME"],
        [D-Bus service name for the calendar factory])
 
+AC_DEFINE_UNQUOTED(
+       SOURCES_DBUS_SERVICE_NAME,
+       ["$SOURCES_DBUS_SERVICE_NAME"],
+       [D-Bus service name for the source registry])
+
 AC_SUBST(ADDRESS_BOOK_DBUS_SERVICE_NAME)
 AC_SUBST(CALENDAR_DBUS_SERVICE_NAME)
+AC_SUBST(SOURCES_DBUS_SERVICE_NAME)
 
 dnl ******************************
 dnl Libtool versioning
@@ -397,14 +404,15 @@ if test "x$enable_goa" = xyes; then
 fi
 AM_CONDITIONAL(HAVE_GOA, [test x$enable_goa = xyes])
 
+dnl ***********************************
+dnl Check for GNOME Keyring.
+dnl ***********************************
 if test x$os_win32 = xno; then
-  dnl ***********************************
-  dnl Check for GNOME Keyring.
-  dnl ***********************************
-  PKG_CHECK_MODULES(GNOME_KEYRING,
-       [gnome-keyring-1 >= gnome_keyring_minimum_version])
+       PKG_CHECK_MODULES(GNOME_KEYRING,
+               [gnome-keyring-1 >= gnome_keyring_minimum_version])
 fi
-
+AC_SUBST(GNOME_KEYRING_CFLAGS)
+AC_SUBST(GNOME_KEYRING_LIBS)
 
 dnl **********************************************************
 dnl gcr-base is needed for secure password exchange over D-Bus
@@ -1327,7 +1335,7 @@ if test "x$enable_maintainer_mode" = "xyes" ; then
        AC_SUBST(FACTORY_GTK_LIBS)
 fi
 
-E_BACKEND_DEPS="gio-2.0 gmodule-2.0 libxml-2.0 gconf-2.0"
+E_BACKEND_DEPS="gio-2.0 gmodule-2.0 gnome-keyring-1 libxml-2.0 gconf-2.0"
 
 dnl ******************************
 dnl libebackend flags
@@ -1491,12 +1499,21 @@ AC_SUBST(privlibdir)
 imagesdir='${datadir}'/pixmaps/evolution-data-server
 AC_SUBST(imagesdir)
 
+moduledir='${libdir}'/evolution-data-server/registry-modules
+AC_SUBST(moduledir)
+
 ebook_backenddir='${libdir}'/evolution-data-server/addressbook-backends
 AC_SUBST(ebook_backenddir)
 
 ecal_backenddir='${libdir}'/evolution-data-server/calendar-backends
 AC_SUBST(ecal_backenddir)
 
+ro_sourcesdir='${datadir}'/evolution-data-server-$BASE_VERSION/ro-sources
+AC_SUBST(ro_sourcesdir)
+
+rw_sourcesdir='${datadir}'/evolution-data-server-$BASE_VERSION/rw-sources
+AC_SUBST(rw_sourcesdir)
+
 if test "x$use_gweather" = "xyes"; then
        weatherdatadir="$privdatadir/weather"
        AC_SUBST(weatherdatadir)
@@ -1510,7 +1527,7 @@ dnl *******************
 dnl D-BUS service stuff
 dnl *******************
 m4_pattern_allow([AM_V_GEN])
-EVO_SUBST_SERVICE_RULE='%.service: %.service.in Makefile ; $(AM_V_GEN) sed -e "s|\@libexecdir\@|$(libexecdir)|" -e s"|\@ADDRESS_BOOK_DBUS_SERVICE_NAME\@|$(ADDRESS_BOOK_DBUS_SERVICE_NAME)|" -e "s|\@CALENDAR_DBUS_SERVICE_NAME\@|$(CALENDAR_DBUS_SERVICE_NAME)|" $< > $@'
+EVO_SUBST_SERVICE_RULE='%.service: %.service.in Makefile ; $(AM_V_GEN) sed -e "s|\@libexecdir\@|$(libexecdir)|" -e s"|\@ADDRESS_BOOK_DBUS_SERVICE_NAME\@|$(ADDRESS_BOOK_DBUS_SERVICE_NAME)|" -e "s|\@CALENDAR_DBUS_SERVICE_NAME\@|$(CALENDAR_DBUS_SERVICE_NAME)|" -e "s|\@SOURCES_DBUS_SERVICE_NAME\@|$(SOURCES_DBUS_SERVICE_NAME)|" $< > $@'
 AC_SUBST(EVO_SUBST_SERVICE_RULE)
 
 dnl ******************************
@@ -1626,6 +1643,7 @@ private/Makefile
 services/Makefile
 services/evolution-addressbook-factory/Makefile
 services/evolution-calendar-factory/Makefile
+services/evolution-source-registry/Makefile
 tests/Makefile
 tests/libebook/Makefile
 tests/libebook/client/Makefile
index 5abb0ca..7d94fc2 100644 (file)
   </chapter>
 
   <chapter>
+    <title>Registry Service Classes</title>
+    <xi:include href="xml/e-authentication-mediator.xml"/>
+    <xi:include href="xml/e-authentication-session.xml"/>
+    <xi:include href="xml/e-collection-backend.xml"/>
+    <xi:include href="xml/e-collection-backend-factory.xml"/>
+    <xi:include href="xml/e-server-side-source.xml"/>
+    <xi:include href="xml/e-source-registry-server.xml"/>
+  </chapter>
+
+  <chapter>
     <title>Miscellaneous Utilities</title>
     <xi:include href="xml/e-file-cache.xml"/>
     <xi:include href="xml/e-dbhash.xml"/>
index f1872c3..89d1fa6 100644 (file)
@@ -1,4 +1,75 @@
 <SECTION>
+<FILE>e-authentication-mediator</FILE>
+<TITLE>EAuthenticationMediator</TITLE>
+EAuthenticationMediator
+e_authentication_mediator_new
+e_authentication_mediator_get_connection
+e_authentication_mediator_get_object_path
+e_authentication_mediator_get_sender
+e_authentication_mediator_wait_for_client_sync
+e_authentication_mediator_wait_for_client
+e_authentication_mediator_wait_for_client_finish
+e_authentication_mediator_dismiss
+<SUBSECTION Standard>
+E_AUTHENTICATION_MEDIATOR
+E_IS_AUTHENTICATION_MEDIATOR
+E_TYPE_AUTHENTICATION_MEDIATOR
+E_AUTHENTICATION_MEDIATOR_CLASS
+E_IS_AUTHENTICATION_MEDIATOR_CLASS
+E_AUTHENTICATION_MEDIATOR_GET_CLASS
+<SUBSECTION Private>
+EAuthenticationMediatorPrivate
+e_authentication_mediator_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-authentication-session</FILE>
+<TITLE>EAuthenticationSession</TITLE>
+E_AUTHENTICATION_SESSION_KEYRING_ERROR
+EAuthenticationSession
+EAuthenticationSessionResult
+e_authentication_session_new
+e_authentication_session_get_server
+e_authentication_session_get_authenticator
+e_authentication_session_get_source_uid
+e_authentication_session_get_prompt_title
+e_authentication_session_dup_prompt_title
+e_authentication_session_set_prompt_title
+e_authentication_session_get_prompt_message
+e_authentication_session_dup_prompt_message
+e_authentication_session_set_prompt_message
+e_authentication_session_get_prompt_description
+e_authentication_session_dup_prompt_description
+e_authentication_session_set_prompt_description
+e_authentication_session_execute_sync
+e_authentication_session_execute
+e_authentication_session_execute_finish
+e_authentication_session_store_password_sync
+e_authentication_session_store_password
+e_authentication_session_store_password_finish
+e_authentication_session_lookup_password_sync
+e_authentication_session_lookup_password
+e_authentication_session_lookup_password_finish
+e_authentication_session_delete_password_sync
+e_authentication_session_delete_password
+e_authentication_session_delete_password_finish
+<SUBSECTION Standard>
+E_AUTHENTICATION_SESSION
+E_IS_AUTHENTICATION_SESSION
+E_TYPE_AUTHENTICATION_SESSION
+E_AUTHENTICATION_SESSION_CLASS
+E_IS_AUTHENTICATION_SESSION_CLASS
+E_AUTHENTICATION_SESSION_GET_CLASS
+E_TYPE_AUTHENTICATION_SESSION_RESULT
+EAuthenticationSessionClass
+<SUBSECTION Private>
+EAuthenticationSessionPrivate
+e_authentication_session_error_quark
+e_authentication_session_get_type
+e_authentication_session_result_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-backend</FILE>
 <TITLE>EBackend</TITLE>
 EBackend
@@ -38,6 +109,46 @@ e_backend_factory_get_type
 </SECTION>
 
 <SECTION>
+<FILE>e-collection-backend</FILE>
+<TITLE>ECollectionBackend</TITLE>
+ECollectionBackend
+e_collection_backend_new_child
+e_collection_backend_ref_server
+e_collection_backend_list_calendar_sources
+e_collection_backend_list_contacts_sources
+e_collection_backend_list_mail_sources
+<SUBSECTION Standard>
+E_COLLECTION_BACKEND
+E_IS_COLLECTION_BACKEND
+E_TYPE_COLLECTION_BACKEND
+E_COLLECTION_BACKEND_CLASS
+E_IS_COLLECTION_BACKEND_CLASS
+E_COLLECTION_BACKEND_GET_CLASS
+ECollectionBackendClass
+<SUBSECTION Private>
+ECollectionBackendPrivate
+e_collection_backend_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-collection-backend-factory</FILE>
+<TITLE>ECollectionBackendFactory</TITLE>
+ECollectionBackendFactory
+e_collection_backend_factory_prepare_mail
+<SUBSECTION Standard>
+E_COLLECTION_BACKEND_FACTORY
+E_IS_COLLECTION_BACKEND_FACTORY
+E_TYPE_COLLECTION_BACKEND_FACTORY
+E_COLLECTION_BACKEND_FACTORY_CLASS
+E_IS_COLLECTION_BACKEND_FACTORY_CLASS
+E_COLLECTION_BACKEND_FACTORY_GET_CLASS
+ECollectionBackendFactoryClass
+<SUBSECTION Private>
+ECollectionBackendFactoryPrivate
+e_collection_backend_factory_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-data-factory</FILE>
 <TITLE>EDataFactory</TITLE>
 EDataFactory
@@ -209,6 +320,69 @@ e_offline_listener_get_type
 </SECTION>
 
 <SECTION>
+<FILE>e-server-side-source</FILE>
+<TITLE>EServerSideSource</TITLE>
+EServerSideSource
+e_server_side_source_get_user_dir
+e_server_side_source_new_user_file
+e_server_side_source_uid_from_file
+e_server_side_source_new
+e_server_side_source_new_memory_only
+e_server_side_source_load
+e_server_side_source_get_file
+e_server_side_source_get_node
+e_server_side_source_get_server
+e_server_side_source_get_allow_auth_prompt
+e_server_side_source_set_allow_auth_prompt
+e_server_side_source_set_removable
+e_server_side_source_set_writable
+<SUBSECTION Standard>
+E_SERVER_SIDE_SOURCE
+E_IS_SERVER_SIDE_SOURCE
+E_TYPE_SERVER_SIDE_SOURCE
+E_SERVER_SIDE_SOURCE_CLASS
+E_IS_SERVER_SIDE_SOURCE_CLASS
+E_SERVER_SIDE_SOURCE_GET_CLASS
+EServerSideSourceClass
+<SUBSECTION Private>
+EServerSideSourcePrivate
+e_server_side_source_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-source-registry-server</FILE>
+<TITLE>ESourceRegistryServer</TITLE>
+E_SOURCE_REGISTRY_SERVER_OBJECT_PATH
+ESourceRegistryServer
+e_source_registry_server_new
+e_source_registry_server_add_source
+e_source_registry_server_remove_source
+e_source_registry_server_queue_auth_session
+e_source_registry_server_load_all
+ESourcePermissionFlags
+e_source_registry_server_load_directory
+e_source_registry_server_load_file
+e_source_registry_server_load_error
+e_source_registry_server_ref_source
+e_source_registry_server_list_sources
+e_source_registry_server_ref_backend_factory
+<SUBSECTION Standard>
+E_SOURCE_REGISTRY_SERVER
+E_IS_SOURCE_REGISTRY_SERVER
+E_TYPE_SOURCE_REGISTRY_SERVER
+E_SOURCE_REGISTRY_SERVER_CLASS
+E_IS_SOURCE_REGISTRY_SERVER_CLASS
+E_SOURCE_REGISTRY_SERVER_GET_CLASS
+E_TYPE_SOURCE_PERMISSION_FLAGS
+E_TYPE_PASSWORD_REMEMBER_TYPE
+ESourceRegistryServerClass
+<SUBSECTION Private>
+ESourceRegistryServerPrivate
+e_source_registry_server_get_type
+e_source_permission_flags_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-sqlite3-vfs</FILE>
 e_sqlite3_vfs_init
 </SECTION>
index e23f54b..7ecf584 100644 (file)
@@ -1,5 +1,9 @@
+#include <libebackend/e-authentication-mediator.h>
+#include <libebackend/e-authentication-session.h>
 #include <libebackend/e-backend.h>
 #include <libebackend/e-backend-factory.h>
+#include <libebackend/e-collection-backend.h>
+#include <libebackend/e-collection-backend-factory.h>
 #include <libebackend/e-data-factory.h>
 #include <libebackend/e-dbus-server.h>
 #include <libebackend/e-extensible.h>
@@ -7,9 +11,15 @@
 #include <libebackend/e-file-cache.h>
 #include <libebackend/e-module.h>
 #include <libebackend/e-offline-listener.h>
+#include <libebackend/e-server-side-source.h>
+#include <libebackend/e-source-registry-server.h>
 
+e_authentication_mediator_get_type
+e_authentication_session_get_type
 e_backend_get_type
 e_backend_factory_get_type
+e_collection_backend_get_type
+e_collection_backend_factory_get_type
 e_data_factory_get_type
 e_dbus_server_get_type
 e_extensible_get_type
@@ -17,3 +27,5 @@ e_extension_get_type
 e_file_cache_get_type
 e_module_get_type
 e_offline_listener_get_type
+e_server_side_source_get_type
+e_source_registry_server_get_type
index 0b6c2a6..378b205 100644 (file)
@@ -14,18 +14,27 @@ libebackend_1_2_la_CPPFLAGS = \
        $(AM_CPPFLAGS)                                          \
        -I$(top_srcdir)                                         \
        -I$(top_srcdir)/private                                 \
-       -DG_LOG_DOMAIN=\"e-data-server\"                        \
+       -DG_LOG_DOMAIN=\"libebackend\"                          \
+       -DMODULE_DIRECTORY=\"$(moduledir)\"                     \
+       -DE_DATA_SERVER_PRIVDATADIR=\"$(privdatadir)\"          \
+       -DSYSTEM_WIDE_RO_SOURCES_DIRECTORY=\"$(ro_sourcesdir)\" \
+       -DSYSTEM_WIDE_RW_SOURCES_DIRECTORY=\"$(rw_sourcesdir)\" \
        $(DB_CFLAGS)                                            \
        $(SQLITE3_CFLAGS)                                       \
        $(E_BACKEND_CFLAGS)                                     \
+       $(GCR_BASE_CFLAGS)                                      \
        $(GIO_UNIX_CFLAGS)                                      \
        $(CODE_COVERAGE_CFLAGS)                                 \
        $(NULL)
 
 libebackend_1_2_la_SOURCES =           \
        $(BUILT_SOURCES)                \
+       e-authentication-mediator.c     \
+       e-authentication-session.c      \
        e-backend.c                     \
        e-backend-factory.c             \
+       e-collection-backend.c          \
+       e-collection-backend-factory.c  \
        e-data-factory.c                \
        e-dbus-server.c                 \
        e-extensible.c                  \
@@ -34,13 +43,17 @@ libebackend_1_2_la_SOURCES =                \
        e-dbhash.c                      \
        e-db3-utils.c                   \
        e-module.c                      \
+       e-server-side-source.c          \
+       e-source-registry-server.c      \
        e-sqlite3-vfs.c                 \
        e-file-cache.c
 
 libebackend_1_2_la_LIBADD =                            \
        $(top_builddir)/libedataserver/libedataserver-1.2.la \
+       $(top_builddir)/private/libedbus-private.la     \
        $(E_BACKEND_LIBS)                               \
        $(SQLITE3_LIBS)                                 \
+       $(GCR_BASE_LIBS)                                \
        $(GIO_UNIX_LIBS)                                \
        $(DB_LIBS)
 
@@ -52,9 +65,13 @@ libebackend_1_2_la_LDFLAGS = \
 libebackendincludedir = $(privincludedir)/libebackend
 
 libebackendinclude_HEADERS =           \
+       e-authentication-mediator.h     \
+       e-authentication-session.h      \
        e-backend.h                     \
        e-backend-enums.h               \
        e-backend-factory.h             \
+       e-collection-backend.h          \
+       e-collection-backend-factory.h  \
        e-data-factory.h                \
        e-dbus-server.h                 \
        e-extensible.h                  \
@@ -63,6 +80,8 @@ libebackendinclude_HEADERS =          \
        e-db3-utils.h                   \
        e-dbhash.h                      \
        e-module.h                      \
+       e-server-side-source.h          \
+       e-source-registry-server.h      \
        e-sqlite3-vfs.h                 \
        e-file-cache.h
 
diff --git a/libebackend/e-authentication-mediator.c b/libebackend/e-authentication-mediator.c
new file mode 100644 (file)
index 0000000..0af6805
--- /dev/null
@@ -0,0 +1,1032 @@
+/*
+ * e-authentication-mediator.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-authentication-mediator
+ * @include: libebackend/e-authentication-mediator.h
+ * @short_description: Authenticator proxy for remote clients
+ *
+ * #EAuthenticationMediator runs on the registry D-Bus service.  It mediates
+ * authentication attempts between the client requesting authentication and
+ * the server-side #EAuthenticationSession interacting with the user and/or
+ * secret service.  It implements the #ESourceAuthenticator interface and
+ * securely transmits passwords to a remote #ESourceRegistry over D-Bus.
+ **/
+
+#include "e-authentication-mediator.h"
+
+/* XXX Yeah, yeah... */
+#define GCR_API_SUBJECT_TO_CHANGE
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <gcr/gcr-base.h>
+
+#include <libedataserver/e-flag.h>
+#include <libedataserver/e-data-server-util.h>
+
+/* Private D-Bus classes. */
+#include <e-dbus-authenticator.h>
+
+#define E_AUTHENTICATION_MEDIATOR_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), E_TYPE_AUTHENTICATION_MEDIATOR, EAuthenticationMediatorPrivate))
+
+/* How long should clients have to respond before timing out the
+ * authentication session?  Need to balance allowing adequate time
+ * without blocking other requests too long if a client gets stuck. */
+#define INACTIVITY_TIMEOUT (2 * 60)  /* in seconds */
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EAuthenticationMediatorPrivate {
+       GDBusConnection *connection;
+       EDBusAuthenticator *interface;
+       GcrSecretExchange *secret_exchange;
+       gchar *object_path;
+       gchar *sender;
+
+       GMutex *shared_data_lock;
+
+       GQueue try_password_queue;
+       GQueue wait_for_client_queue;
+
+       gboolean client_is_ready;
+       gboolean client_cancelled;
+       gboolean client_vanished;
+
+       guint watcher_id;
+};
+
+struct _AsyncContext {
+       /* These point into the EAuthenticationMediatorPrivate
+        * struct.  Do not free them in async_context_free(). */
+       GMutex *shared_data_lock;
+       GQueue *operation_queue;
+
+       GCancellable *cancellable;
+       gulong cancel_id;
+       guint timeout_id;
+};
+
+enum {
+       PROP_0,
+       PROP_CONNECTION,
+       PROP_OBJECT_PATH,
+       PROP_SENDER
+};
+
+/* Forward Declarations */
+static void    e_authentication_mediator_initable_init
+                               (GInitableIface *interface);
+static void    e_authentication_mediator_interface_init
+                               (ESourceAuthenticatorInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+       EAuthenticationMediator,
+       e_authentication_mediator,
+       G_TYPE_OBJECT,
+       G_IMPLEMENT_INTERFACE (
+               G_TYPE_INITABLE,
+               e_authentication_mediator_initable_init)
+       G_IMPLEMENT_INTERFACE (
+               E_TYPE_SOURCE_AUTHENTICATOR,
+               e_authentication_mediator_interface_init))
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+       if (async_context->cancellable != NULL) {
+               g_cancellable_disconnect (
+                       async_context->cancellable,
+                       async_context->cancel_id);
+               g_object_unref (async_context->cancellable);
+       }
+
+       if (async_context->timeout_id > 0)
+               g_source_remove (async_context->timeout_id);
+
+       g_slice_free (AsyncContext, async_context);
+}
+
+static void
+authentication_mediator_name_vanished_cb (GDBusConnection *connection,
+                                          const gchar *name,
+                                          gpointer user_data)
+{
+       EAuthenticationMediator *mediator;
+       GSimpleAsyncResult *simple;
+       GQueue *queue;
+
+       mediator = E_AUTHENTICATION_MEDIATOR (user_data);
+
+       g_mutex_lock (mediator->priv->shared_data_lock);
+
+       mediator->priv->client_vanished = TRUE;
+
+       queue = &mediator->priv->try_password_queue;
+
+       /* Notify any unfinished try_password() operations. */
+       while ((simple = g_queue_pop_head (queue)) != NULL) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       "%s", _("Bus name vanished (client terminated?)"));
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       queue = &mediator->priv->wait_for_client_queue;
+
+       /* Notify any unfinished wait_for_client() operations. */
+       while ((simple = g_queue_pop_head (queue)) != NULL) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       "%s", _("Bus name vanished (client terminated?)"));
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       g_bus_unwatch_name (mediator->priv->watcher_id);
+       mediator->priv->watcher_id = 0;
+
+       g_mutex_unlock (mediator->priv->shared_data_lock);
+}
+
+static void
+authentication_mediator_cancelled_cb (GCancellable *cancellable,
+                                      gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       simple = G_SIMPLE_ASYNC_RESULT (user_data);
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       g_mutex_lock (async_context->shared_data_lock);
+
+       /* Because we called g_simple_async_result_set_check_cancellable(),
+        * g_simple_async_result_propagate_error() will automatically set a
+        * cancelled error so we don't need to explicitly set one here. */
+       if (g_queue_remove (async_context->operation_queue, simple)) {
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       g_mutex_unlock (async_context->shared_data_lock);
+}
+
+static gboolean
+authentication_mediator_timeout_cb (gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       simple = G_SIMPLE_ASYNC_RESULT (user_data);
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       g_mutex_lock (async_context->shared_data_lock);
+
+       if (g_queue_remove (async_context->operation_queue, simple)) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                       "%s", _("No response from client"));
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       g_mutex_unlock (async_context->shared_data_lock);
+
+       return FALSE;
+}
+
+static gboolean
+authentication_mediator_handle_ready (EDBusAuthenticator *interface,
+                                      GDBusMethodInvocation *invocation,
+                                      const gchar *encrypted_key,
+                                      EAuthenticationMediator *mediator)
+{
+       GcrSecretExchange *secret_exchange;
+       GSimpleAsyncResult *simple;
+       GQueue *queue;
+
+       g_mutex_lock (mediator->priv->shared_data_lock);
+
+       mediator->priv->client_is_ready = TRUE;
+
+       secret_exchange = mediator->priv->secret_exchange;
+       gcr_secret_exchange_receive (secret_exchange, encrypted_key);
+
+       queue = &mediator->priv->wait_for_client_queue;
+
+       /* Notify any unfinished wait_for_client() operations. */
+       while ((simple = g_queue_pop_head (queue)) != NULL) {
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       g_mutex_unlock (mediator->priv->shared_data_lock);
+
+       e_dbus_authenticator_complete_ready (interface, invocation);
+
+       return TRUE;
+}
+
+static gboolean
+authentication_mediator_handle_cancel (EDBusAuthenticator *interface,
+                                       GDBusMethodInvocation *invocation,
+                                       EAuthenticationMediator *mediator)
+{
+       GSimpleAsyncResult *simple;
+       GQueue *queue;
+
+       g_mutex_lock (mediator->priv->shared_data_lock);
+
+       mediator->priv->client_cancelled = TRUE;
+
+       queue = &mediator->priv->try_password_queue;
+
+       /* Notify any unfinished try_password() operations. */
+       while ((simple = g_queue_pop_head (queue)) != NULL) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       "%s", _("Client cancelled the operation"));
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       queue = &mediator->priv->wait_for_client_queue;
+
+       /* Notify any unfinished wait_for_client() operations. */
+       while ((simple = g_queue_pop_head (queue)) != NULL) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       "%s", _("Client cancelled the operation"));
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       g_mutex_unlock (mediator->priv->shared_data_lock);
+
+       e_dbus_authenticator_complete_cancel (interface, invocation);
+
+       return TRUE;
+}
+
+static gboolean
+authentication_mediator_handle_accepted (EDBusAuthenticator *interface,
+                                         GDBusMethodInvocation *invocation,
+                                         EAuthenticationMediator *mediator)
+{
+       GSimpleAsyncResult *simple;
+       GQueue *queue;
+
+       g_mutex_lock (mediator->priv->shared_data_lock);
+
+       queue = &mediator->priv->try_password_queue;
+
+       if (g_queue_is_empty (queue))
+               g_warning ("%s: Unexpected 'accepted' signal", G_STRFUNC);
+
+       /* Notify any unfinished try_password() operations. */
+       while ((simple = g_queue_pop_head (queue)) != NULL) {
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       g_mutex_unlock (mediator->priv->shared_data_lock);
+
+       e_dbus_authenticator_complete_accepted (interface, invocation);
+
+       return TRUE;
+}
+
+static gboolean
+authentication_mediator_handle_rejected (EDBusAuthenticator *interface,
+                                         GDBusMethodInvocation *invocation,
+                                         EAuthenticationMediator *mediator)
+{
+       GSimpleAsyncResult *simple;
+       GQueue *queue;
+
+       g_mutex_lock (mediator->priv->shared_data_lock);
+
+       queue = &mediator->priv->try_password_queue;
+
+       if (g_queue_is_empty (queue))
+               g_warning ("%s: Unexpected 'rejected' signal", G_STRFUNC);
+
+       /* Notify any unfinished try_password() operations. */
+       while ((simple = g_queue_pop_head (queue)) != NULL) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+                       "%s", _("Client reports password was rejected"));
+               g_simple_async_result_complete_in_idle (simple);
+               g_object_unref (simple);
+       }
+
+       g_mutex_unlock (mediator->priv->shared_data_lock);
+
+       e_dbus_authenticator_complete_rejected (interface, invocation);
+
+       return TRUE;
+}
+
+static void
+authentication_mediator_set_connection (EAuthenticationMediator *mediator,
+                                        GDBusConnection *connection)
+{
+       g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+       g_return_if_fail (mediator->priv->connection == NULL);
+
+       mediator->priv->connection = g_object_ref (connection);
+}
+
+static void
+authentication_mediator_set_object_path (EAuthenticationMediator *mediator,
+                                         const gchar *object_path)
+{
+       g_return_if_fail (object_path != NULL);
+       g_return_if_fail (mediator->priv->object_path == NULL);
+
+       mediator->priv->object_path = g_strdup (object_path);
+}
+
+static void
+authentication_mediator_set_sender (EAuthenticationMediator *mediator,
+                                    const gchar *sender)
+{
+       g_return_if_fail (sender != NULL);
+       g_return_if_fail (mediator->priv->sender == NULL);
+
+       mediator->priv->sender = g_strdup (sender);
+}
+
+static void
+authentication_mediator_set_property (GObject *object,
+                                      guint property_id,
+                                      const GValue *value,
+                                      GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_CONNECTION:
+                       authentication_mediator_set_connection (
+                               E_AUTHENTICATION_MEDIATOR (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_OBJECT_PATH:
+                       authentication_mediator_set_object_path (
+                               E_AUTHENTICATION_MEDIATOR (object),
+                               g_value_get_string (value));
+                       return;
+
+               case PROP_SENDER:
+                       authentication_mediator_set_sender (
+                               E_AUTHENTICATION_MEDIATOR (object),
+                               g_value_get_string (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+authentication_mediator_get_property (GObject *object,
+                                      guint property_id,
+                                      GValue *value,
+                                      GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_CONNECTION:
+                       g_value_set_object (
+                               value,
+                               e_authentication_mediator_get_connection (
+                               E_AUTHENTICATION_MEDIATOR (object)));
+                       return;
+
+               case PROP_OBJECT_PATH:
+                       g_value_set_string (
+                               value,
+                               e_authentication_mediator_get_object_path (
+                               E_AUTHENTICATION_MEDIATOR (object)));
+                       return;
+
+               case PROP_SENDER:
+                       g_value_set_string (
+                               value,
+                               e_authentication_mediator_get_sender (
+                               E_AUTHENTICATION_MEDIATOR (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+authentication_mediator_dispose (GObject *object)
+{
+       EAuthenticationMediatorPrivate *priv;
+       GQueue *queue;
+
+       priv = E_AUTHENTICATION_MEDIATOR_GET_PRIVATE (object);
+
+       if (priv->connection != NULL) {
+               g_object_unref (priv->connection);
+               priv->connection = NULL;
+       }
+
+       if (priv->interface != NULL) {
+               g_object_unref (priv->interface);
+               priv->interface = NULL;
+       }
+
+       if (priv->secret_exchange != NULL) {
+               g_object_unref (priv->secret_exchange);
+               priv->secret_exchange = NULL;
+       }
+
+       queue = &priv->wait_for_client_queue;
+
+       while (!g_queue_is_empty (queue))
+               g_object_unref (g_queue_pop_head (queue));
+
+       if (priv->watcher_id > 0) {
+               g_bus_unwatch_name (priv->watcher_id);
+               priv->watcher_id = 0;
+       }
+
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_authentication_mediator_parent_class)->
+               dispose (object);
+}
+
+static void
+authentication_mediator_finalize (GObject *object)
+{
+       EAuthenticationMediatorPrivate *priv;
+
+       priv = E_AUTHENTICATION_MEDIATOR_GET_PRIVATE (object);
+
+       g_mutex_free (priv->shared_data_lock);
+
+       g_free (priv->object_path);
+       g_free (priv->sender);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_authentication_mediator_parent_class)->
+               finalize (object);
+}
+
+static void
+authentication_mediator_constructed (GObject *object)
+{
+       EAuthenticationMediator *mediator;
+       GDBusConnection *connection;
+       const gchar *sender;
+
+       mediator = E_AUTHENTICATION_MEDIATOR (object);
+       connection = e_authentication_mediator_get_connection (mediator);
+       sender = e_authentication_mediator_get_sender (mediator);
+
+       /* This should notify us if the client process terminates. */
+       mediator->priv->watcher_id =
+               g_bus_watch_name_on_connection (
+                       connection, sender,
+                       G_BUS_NAME_WATCHER_FLAGS_NONE,
+                       NULL,
+                       authentication_mediator_name_vanished_cb,
+                       mediator, (GDestroyNotify) NULL);
+
+       /* Chain up to parent's constructed() method. */
+       G_OBJECT_CLASS (e_authentication_mediator_parent_class)->
+               constructed (object);
+}
+
+static gboolean
+authentication_mediator_initable_init (GInitable *initable,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       EAuthenticationMediator *mediator;
+       GDBusInterfaceSkeleton *interface;
+       GDBusConnection *connection;
+       const gchar *object_path;
+
+       mediator = E_AUTHENTICATION_MEDIATOR (initable);
+
+       interface = G_DBUS_INTERFACE_SKELETON (mediator->priv->interface);
+       connection = e_authentication_mediator_get_connection (mediator);
+       object_path = e_authentication_mediator_get_object_path (mediator);
+
+       return g_dbus_interface_skeleton_export (
+               interface, connection, object_path, error);
+}
+
+static ESourceAuthenticationResult
+authentication_mediator_try_password_sync (ESourceAuthenticator *auth,
+                                           const GString *password,
+                                           GCancellable *cancellable,
+                                           GError **error)
+{
+       ESourceAuthenticationResult auth_result;
+       GAsyncResult *async_result;
+       EAsyncClosure *closure;
+
+       closure = e_async_closure_new ();
+
+       e_source_authenticator_try_password (
+               auth, password, cancellable,
+               e_async_closure_callback, closure);
+
+       async_result = e_async_closure_wait (closure);
+
+       auth_result = e_source_authenticator_try_password_finish (
+               auth, async_result, error);
+
+       e_async_closure_free (closure);
+
+       return auth_result;
+}
+
+static void
+authentication_mediator_try_password (ESourceAuthenticator *auth,
+                                      const GString *password,
+                                      GCancellable *cancellable,
+                                      GAsyncReadyCallback callback,
+                                      gpointer user_data)
+{
+       EAuthenticationMediator *mediator;
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       mediator = E_AUTHENTICATION_MEDIATOR (auth);
+
+       async_context = g_slice_new0 (AsyncContext);
+       async_context->shared_data_lock = mediator->priv->shared_data_lock;
+       async_context->operation_queue = &mediator->priv->try_password_queue;
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (mediator), callback, user_data,
+               authentication_mediator_try_password);
+
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+       g_simple_async_result_set_op_res_gpointer (
+               simple, async_context, (GDestroyNotify) async_context_free);
+
+       if (G_IS_CANCELLABLE (cancellable)) {
+               async_context->cancellable = g_object_ref (cancellable);
+               async_context->cancel_id = g_cancellable_connect (
+                       async_context->cancellable,
+                       G_CALLBACK (authentication_mediator_cancelled_cb),
+                       simple, (GDestroyNotify) NULL);
+       }
+
+       async_context->timeout_id = g_timeout_add_seconds (
+               INACTIVITY_TIMEOUT,
+               authentication_mediator_timeout_cb, simple);
+
+       g_mutex_lock (mediator->priv->shared_data_lock);
+
+       if (mediator->priv->client_cancelled) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       "%s", _("Client cancelled the operation"));
+               g_simple_async_result_complete_in_idle (simple);
+
+       } else if (mediator->priv->client_vanished) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       "%s", _("Bus name vanished (client terminated?)"));
+               g_simple_async_result_complete_in_idle (simple);
+
+       } else {
+               gchar *encrypted_secret;
+
+               g_queue_push_tail (
+                       async_context->operation_queue,
+                       g_object_ref (simple));
+
+               encrypted_secret = gcr_secret_exchange_send (
+                       mediator->priv->secret_exchange, password->str, -1);
+
+               e_dbus_authenticator_emit_authenticate (
+                       mediator->priv->interface, encrypted_secret);
+
+               g_free (encrypted_secret);
+       }
+
+       g_mutex_unlock (mediator->priv->shared_data_lock);
+
+       g_object_unref (simple);
+}
+
+static ESourceAuthenticationResult
+authentication_mediator_try_password_finish (ESourceAuthenticator *auth,
+                                             GAsyncResult *result,
+                                             GError **error)
+{
+       GSimpleAsyncResult *simple;
+       GError *local_error = NULL;
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+
+       if (!g_simple_async_result_propagate_error (simple, &local_error))
+               return E_SOURCE_AUTHENTICATION_ACCEPTED;
+
+       if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) {
+               g_clear_error (&local_error);
+               return E_SOURCE_AUTHENTICATION_REJECTED;
+       }
+
+       g_propagate_error (error, local_error);
+
+       return E_SOURCE_AUTHENTICATION_ERROR;
+}
+
+static void
+e_authentication_mediator_class_init (EAuthenticationMediatorClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (class, sizeof (EAuthenticationMediatorPrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = authentication_mediator_set_property;
+       object_class->get_property = authentication_mediator_get_property;
+       object_class->dispose = authentication_mediator_dispose;
+       object_class->finalize = authentication_mediator_finalize;
+       object_class->constructed = authentication_mediator_constructed;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_CONNECTION,
+               g_param_spec_object (
+                       "connection",
+                       "Connection",
+                       "The GDBusConnection on which to "
+                       "export the authenticator interface",
+                       G_TYPE_DBUS_CONNECTION,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_OBJECT_PATH,
+               g_param_spec_string (
+                       "object-path",
+                       "Object Path",
+                       "The object path at which to "
+                       "export the authenticator interface",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_SENDER,
+               g_param_spec_string (
+                       "sender",
+                       "Sender",
+                       "Unique bus name of the process that "
+                       "initiated the authentication session",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_authentication_mediator_initable_init (GInitableIface *interface)
+{
+       interface->init = authentication_mediator_initable_init;
+}
+
+static void
+e_authentication_mediator_interface_init (ESourceAuthenticatorInterface *interface)
+{
+       interface->try_password_sync = authentication_mediator_try_password_sync;
+       interface->try_password = authentication_mediator_try_password;
+       interface->try_password_finish = authentication_mediator_try_password_finish;
+}
+
+static void
+e_authentication_mediator_init (EAuthenticationMediator *mediator)
+{
+       mediator->priv = E_AUTHENTICATION_MEDIATOR_GET_PRIVATE (mediator);
+
+       mediator->priv->interface = e_dbus_authenticator_skeleton_new ();
+       mediator->priv->secret_exchange = gcr_secret_exchange_new (NULL);
+
+       mediator->priv->shared_data_lock = g_mutex_new ();
+
+       g_dbus_interface_skeleton_set_flags (
+               G_DBUS_INTERFACE_SKELETON (mediator->priv->interface),
+               G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+
+       g_signal_connect (
+               mediator->priv->interface, "handle-ready",
+               G_CALLBACK (authentication_mediator_handle_ready), mediator);
+
+       g_signal_connect (
+               mediator->priv->interface, "handle-cancel",
+               G_CALLBACK (authentication_mediator_handle_cancel), mediator);
+
+       g_signal_connect (
+               mediator->priv->interface, "handle-accepted",
+               G_CALLBACK (authentication_mediator_handle_accepted), mediator);
+
+       g_signal_connect (
+               mediator->priv->interface, "handle-rejected",
+               G_CALLBACK (authentication_mediator_handle_rejected), mediator);
+}
+
+/**
+ * e_authentication_mediator_new:
+ * @connection: a #GDBusConnection
+ * @object_path: object path of the authentication session
+ * @sender: bus name of the client requesting authentication
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new #EAuthenticationMediator and exports the Authenticator
+ * D-Bus interface on @connection at @object_path.  If the Authenticator
+ * interface fails to export, the function sets @error and returns %NULL.
+ *
+ * #EAuthenticationMediator watches the bus name of the client requesting
+ * authentication, given by @sender.  If it sees the bus name vanish, it
+ * cancels the authentication session so the next authentication session
+ * can begin without delay.
+ *
+ * Returns: an #EAuthenticationMediator, or %NULL on error
+ *
+ * Since: 3.6
+ **/
+ESourceAuthenticator *
+e_authentication_mediator_new (GDBusConnection *connection,
+                               const gchar *object_path,
+                               const gchar *sender,
+                               GError **error)
+{
+       g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+       g_return_val_if_fail (object_path != NULL, NULL);
+       g_return_val_if_fail (sender != NULL, NULL);
+
+       return g_initable_new (
+               E_TYPE_AUTHENTICATION_MEDIATOR, NULL, error,
+               "connection", connection,
+               "object-path", object_path,
+               "sender", sender, NULL);
+}
+
+/**
+ * e_authentication_mediator_get_connection:
+ * @mediator: an #EAuthenticationMediator
+ *
+ * Returns the #GDBusConnection on which the Authenticator D-Bus interface
+ * is exported.
+ *
+ * Returns: the #GDBusConnection
+ *
+ * Since: 3.6
+ **/
+GDBusConnection *
+e_authentication_mediator_get_connection (EAuthenticationMediator *mediator)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_MEDIATOR (mediator), NULL);
+
+       return mediator->priv->connection;
+}
+
+/**
+ * e_authentication_mediator_get_object_path:
+ * @mediator: an #EAuthenticationMediator
+ *
+ * Returns the object path at which the Authenticator D-Bus interface is
+ * exported.
+ *
+ * Returns: the object path
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_authentication_mediator_get_object_path (EAuthenticationMediator *mediator)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_MEDIATOR (mediator), NULL);
+
+       return mediator->priv->object_path;
+}
+
+/**
+ * e_authentication_mediator_get_sender:
+ * @mediator: an #EAuthenticationMediator
+ *
+ * Returns the authentication client's unique bus name.
+ *
+ * Returns: the client's bus name
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_authentication_mediator_get_sender (EAuthenticationMediator *mediator)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_MEDIATOR (mediator), NULL);
+
+       return mediator->priv->sender;
+}
+
+/**
+ * e_authentication_mediator_wait_for_client_sync:
+ * @mediator: an #EAuthenticationMediator
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Waits for the authentication client to indicate it is ready to begin
+ * authentication attempts.  Call this function to synchronize with the
+ * client before initiating any authentication attempts through @mediator.
+ *
+ * If the authentication client's bus name vanishes or the client fails
+ * to signal it is ready before a timer expires, the function sets @error
+ * and returns %FALSE.
+ *
+ * Returns: %TRUE if the client is ready, %FALSE if an error occurred
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_authentication_mediator_wait_for_client_sync (EAuthenticationMediator *mediator,
+                                                GCancellable *cancellable,
+                                                GError **error)
+{
+       EAsyncClosure *closure;
+       GAsyncResult *result;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_AUTHENTICATION_MEDIATOR (mediator), FALSE);
+
+       closure = e_async_closure_new ();
+
+       e_authentication_mediator_wait_for_client (
+               mediator, cancellable, e_async_closure_callback, closure);
+
+       result = e_async_closure_wait (closure);
+
+       success = e_authentication_mediator_wait_for_client_finish (
+               mediator, result, error);
+
+       e_async_closure_free (closure);
+
+       return success;
+}
+
+/**
+ * e_authentication_mediator_wait_for_client:
+ * @mediator: an #EAuthenticationMediator
+ * @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 waits for the authentication client to indicate it
+ * is ready to being authentication attempts.  Call this function to
+ * synchronize with the client before initiating any authentication
+ * attempts through @mediator.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_authentication_mediator_wait_for_client_finished() to get the
+ * result of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_mediator_wait_for_client (EAuthenticationMediator *mediator,
+                                           GCancellable *cancellable,
+                                           GAsyncReadyCallback callback,
+                                           gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_if_fail (E_IS_AUTHENTICATION_MEDIATOR (mediator));
+
+       async_context = g_slice_new0 (AsyncContext);
+       async_context->shared_data_lock = mediator->priv->shared_data_lock;
+       async_context->operation_queue = &mediator->priv->wait_for_client_queue;
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (mediator), callback, user_data,
+               e_authentication_mediator_wait_for_client);
+
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+       g_simple_async_result_set_op_res_gpointer (
+               simple, async_context, (GDestroyNotify) async_context_free);
+
+       if (G_IS_CANCELLABLE (cancellable)) {
+               async_context->cancellable = g_object_ref (cancellable);
+               async_context->cancel_id = g_cancellable_connect (
+                       cancellable,
+                       G_CALLBACK (authentication_mediator_cancelled_cb),
+                       simple, (GDestroyNotify) NULL);
+       }
+
+       async_context->timeout_id = g_timeout_add_seconds (
+               INACTIVITY_TIMEOUT, authentication_mediator_timeout_cb, simple);
+
+       g_mutex_lock (mediator->priv->shared_data_lock);
+
+       if (mediator->priv->client_is_ready) {
+               g_simple_async_result_complete_in_idle (simple);
+
+       } else if (mediator->priv->client_cancelled) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       "%s", _("Client cancelled the operation"));
+               g_simple_async_result_complete_in_idle (simple);
+
+       } else if (mediator->priv->client_vanished) {
+               g_simple_async_result_set_error (
+                       simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       "%s", _("Bus name vanished (client terminated?)"));
+               g_simple_async_result_complete_in_idle (simple);
+
+       } else {
+               g_queue_push_tail (
+                       async_context->operation_queue,
+                       g_object_ref (simple));
+       }
+
+       g_mutex_unlock (mediator->priv->shared_data_lock);
+
+       g_object_unref (simple);
+}
+
+/**
+ * e_authentication_mediator_wait_for_client_finish:
+ * @mediator: an #EAuthenticationMediator
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * e_authentication_mediator_wait_for_client().
+ *
+ * If the authentication client's bus name vanishes or the client fails
+ * to signal it is ready before a timer expires, the function sets @error
+ * and returns %FALSE.
+ *
+ * Returns: %TRUE if the client is ready, %FALSE if an error occurred
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_authentication_mediator_wait_for_client_finish (EAuthenticationMediator *mediator,
+                                                  GAsyncResult *result,
+                                                  GError **error)
+{
+       GSimpleAsyncResult *simple;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (mediator),
+               e_authentication_mediator_wait_for_client), FALSE);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+
+       /* Assume success unless a GError is set. */
+       return !g_simple_async_result_propagate_error (simple, error);
+}
+
+/**
+ * e_authentication_mediator_dismiss:
+ * @mediator: an #EAuthenticationMediator
+ *
+ * Signals to the authentication client that the user declined to provide a
+ * password when prompted and that the authentication session has terminated.
+ * This is also called when a server-side error has occurred, but the client
+ * doesn't need to know the difference.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_mediator_dismiss (EAuthenticationMediator *mediator)
+{
+       g_return_if_fail (E_IS_AUTHENTICATION_MEDIATOR (mediator));
+
+       e_dbus_authenticator_emit_dismissed (mediator->priv->interface);
+}
+
diff --git a/libebackend/e-authentication-mediator.h b/libebackend/e-authentication-mediator.h
new file mode 100644 (file)
index 0000000..8161ffc
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * e-authentication-mediator.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_AUTHENTICATION_MEDIATOR_H
+#define E_AUTHENTICATION_MEDIATOR_H
+
+#include <libedataserver/e-source-authenticator.h>
+
+/* Standard GObject macros */
+#define E_TYPE_AUTHENTICATION_MEDIATOR \
+       (e_authentication_mediator_get_type ())
+#define E_AUTHENTICATION_MEDIATOR(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_AUTHENTICATION_MEDIATOR, EAuthenticationMediator))
+#define E_AUTHENTICATION_MEDIATOR_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_AUTHENTICATION_MEDIATOR, EAuthenticationMediatorClass))
+#define E_IS_AUTHENTICATION_MEDIATOR(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_AUTHENTICATION_MEDIATOR))
+#define E_IS_AUTHENTICATION_MEDIATOR_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_AUTHENTICATION_MEDIATOR))
+#define E_AUTHENTICATION_MEDIATOR_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_AUTHENTICATION_MEDIATOR, EAuthenticationMediatorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAuthenticationMediator EAuthenticationMediator;
+typedef struct _EAuthenticationMediatorClass EAuthenticationMediatorClass;
+typedef struct _EAuthenticationMediatorPrivate EAuthenticationMediatorPrivate;
+
+/**
+ * EAuthenticationMediator:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.6
+ **/
+struct _EAuthenticationMediator {
+       GObject parent;
+       EAuthenticationMediatorPrivate *priv;
+};
+
+struct _EAuthenticationMediatorClass {
+       GObjectClass parent_class;
+};
+
+GType          e_authentication_mediator_get_type
+                                       (void) G_GNUC_CONST;
+ESourceAuthenticator *
+               e_authentication_mediator_new
+                                       (GDBusConnection *connection,
+                                        const gchar *object_path,
+                                        const gchar *sender,
+                                        GError **error);
+GDBusConnection *
+               e_authentication_mediator_get_connection
+                                       (EAuthenticationMediator *mediator);
+const gchar *  e_authentication_mediator_get_object_path
+                                       (EAuthenticationMediator *mediator);
+const gchar *  e_authentication_mediator_get_sender
+                                       (EAuthenticationMediator *mediator);
+gboolean       e_authentication_mediator_wait_for_client_sync
+                                       (EAuthenticationMediator *mediator,
+                                        GCancellable *cancellable,
+                                        GError **error);
+void           e_authentication_mediator_wait_for_client
+                                       (EAuthenticationMediator *mediator,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data);
+gboolean       e_authentication_mediator_wait_for_client_finish
+                                       (EAuthenticationMediator *mediator,
+                                        GAsyncResult *result,
+                                        GError **error);
+void           e_authentication_mediator_dismiss
+                                       (EAuthenticationMediator *mediator);
+
+G_END_DECLS
+
+#endif /* E_AUTHENTICATION_MEDIATOR_H */
+
diff --git a/libebackend/e-authentication-session.c b/libebackend/e-authentication-session.c
new file mode 100644 (file)
index 0000000..0e6ebd1
--- /dev/null
@@ -0,0 +1,1745 @@
+/*
+ * e-authentication-session.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-authentication-session
+ * @include: libedataserver/e-authentication-session.h
+ * @short_description: Centralized authentication management
+ *
+ * #EAuthenticationSession provides centralized password management and
+ * password prompting for all clients of the registry D-Bus service.
+ *
+ * An #EAuthenticationSession is created within the registry D-Bus service
+ * when the service receives a request to authenticate some data source.
+ * Clients can issue requests by calling e_source_registry_authenticate().
+ * Requests can also come from any #ECollectionBackend running within the
+ * service itself.
+ *
+ * An #EAuthenticationSession requires some object implementing the
+ * #ESourceAuthenticator interface to verify stored or user-provided
+ * passwords.  #EAuthenticationMediator is used for client-issued
+ * authentication requests.  Custom collection backends derived from
+ * #ECollectionBackend usually implement the #ESourceAuthenticator
+ * interface themselves.
+ *
+ * The #EAuthenticationSession is then handed to #ESourceRegistryServer
+ * through e_source_registry_server_queue_auth_session() where it waits
+ * in line behind other previously added authentication sessions.  When
+ * its turn comes, the server calls e_authentication_session_execute()
+ * to begin the interactive authentication session.
+ **/
+
+#include "e-authentication-session.h"
+
+/* XXX Yeah, yeah... */
+#define GCR_API_SUBJECT_TO_CHANGE
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gcr/gcr-base.h>
+
+/* Private D-Bus classes. */
+#include <e-dbus-authenticator.h>
+
+#include <libebackend/e-authentication-mediator.h>
+#include <libebackend/e-backend-enumtypes.h>
+#include <libebackend/e-server-side-source.h>
+#include <libebackend/e-source-registry-server.h>
+
+#define E_AUTHENTICATION_SESSION_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), E_TYPE_AUTHENTICATION_SESSION, EAuthenticationSessionPrivate))
+
+/* Wait forever for a system prompt. */
+#define SYSTEM_PROMPT_TIMEOUT (-1)
+
+#define KEYRING_ITEM_ATTRIBUTE_NAME    "e-source-uid"
+#define KEYRING_ITEM_DISPLAY_FORMAT    "Evolution Data Source %s"
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EAuthenticationSessionPrivate {
+       ESourceRegistryServer *server;
+       ESourceAuthenticator *authenticator;
+       gchar *source_uid;
+
+       /* These are for configuring system prompts. */
+       GMutex *property_lock;
+       gchar *prompt_title;
+       gchar *prompt_message;
+       gchar *prompt_description;
+};
+
+struct _AsyncContext {
+       EAuthenticationSessionResult auth_result;
+       gchar *password;
+       gboolean permanently;
+};
+
+enum {
+       PROP_0,
+       PROP_AUTHENTICATOR,
+       PROP_PROMPT_DESCRIPTION,
+       PROP_PROMPT_MESSAGE,
+       PROP_PROMPT_TITLE,
+       PROP_SERVER,
+       PROP_SOURCE_UID
+};
+
+static GnomeKeyringPasswordSchema schema = {
+       GNOME_KEYRING_ITEM_GENERIC_SECRET,
+       {
+               { KEYRING_ITEM_ATTRIBUTE_NAME,
+                 GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+               { NULL, 0 }
+       }
+};
+
+/* Forward Declarations */
+static void    authentication_session_msg
+                                       (EAuthenticationSession *session,
+                                        const gchar *format,
+                                        ...) G_GNUC_PRINTF (2, 3);
+
+G_DEFINE_TYPE (
+       EAuthenticationSession,
+       e_authentication_session,
+       G_TYPE_OBJECT)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+       g_free (async_context->password);
+       g_slice_free (AsyncContext, async_context);
+}
+
+static void
+authentication_session_msg (EAuthenticationSession *session,
+                            const gchar *format,
+                            ...)
+{
+       GString *buffer;
+       va_list args;
+
+       buffer = g_string_sized_new (256);
+
+       g_string_append_printf (
+               buffer, "AUTH (%s): ",
+               session->priv->source_uid);
+
+       va_start (args, format);
+       g_string_append_vprintf (buffer, format, args);
+       va_end (args);
+
+       g_print ("%s\n", buffer->str);
+
+       g_string_free (buffer, TRUE);
+}
+
+static void
+authentication_session_set_authenticator (EAuthenticationSession *session,
+                                          ESourceAuthenticator *authenticator)
+{
+       g_return_if_fail (E_IS_SOURCE_AUTHENTICATOR (authenticator));
+       g_return_if_fail (session->priv->authenticator == NULL);
+
+       session->priv->authenticator = g_object_ref (authenticator);
+}
+
+static void
+authentication_session_set_server (EAuthenticationSession *session,
+                                   ESourceRegistryServer *server)
+{
+       g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+       g_return_if_fail (session->priv->server == NULL);
+
+       session->priv->server = g_object_ref (server);
+}
+
+static void
+authentication_session_set_source_uid (EAuthenticationSession *session,
+                                       const gchar *source_uid)
+{
+       g_return_if_fail (source_uid != NULL);
+       g_return_if_fail (session->priv->source_uid == NULL);
+
+       session->priv->source_uid = g_strdup (source_uid);
+}
+
+static void
+authentication_session_set_property (GObject *object,
+                                     guint property_id,
+                                     const GValue *value,
+                                     GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_AUTHENTICATOR:
+                       authentication_session_set_authenticator (
+                               E_AUTHENTICATION_SESSION (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_PROMPT_DESCRIPTION:
+                       e_authentication_session_set_prompt_description (
+                               E_AUTHENTICATION_SESSION (object),
+                               g_value_get_string (value));
+                       return;
+
+               case PROP_PROMPT_MESSAGE:
+                       e_authentication_session_set_prompt_message (
+                               E_AUTHENTICATION_SESSION (object),
+                               g_value_get_string (value));
+                       return;
+
+               case PROP_PROMPT_TITLE:
+                       e_authentication_session_set_prompt_title (
+                               E_AUTHENTICATION_SESSION (object),
+                               g_value_get_string (value));
+                       return;
+
+               case PROP_SERVER:
+                       authentication_session_set_server (
+                               E_AUTHENTICATION_SESSION (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_SOURCE_UID:
+                       authentication_session_set_source_uid (
+                               E_AUTHENTICATION_SESSION (object),
+                               g_value_get_string (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+authentication_session_get_property (GObject *object,
+                                     guint property_id,
+                                     GValue *value,
+                                     GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_AUTHENTICATOR:
+                       g_value_set_object (
+                               value,
+                               e_authentication_session_get_authenticator (
+                               E_AUTHENTICATION_SESSION (object)));
+                       return;
+
+               case PROP_PROMPT_DESCRIPTION:
+                       g_value_take_string (
+                               value,
+                               e_authentication_session_dup_prompt_description (
+                               E_AUTHENTICATION_SESSION (object)));
+                       return;
+
+               case PROP_PROMPT_MESSAGE:
+                       g_value_take_string (
+                               value,
+                               e_authentication_session_dup_prompt_message (
+                               E_AUTHENTICATION_SESSION (object)));
+                       return;
+
+               case PROP_PROMPT_TITLE:
+                       g_value_take_string (
+                               value,
+                               e_authentication_session_dup_prompt_title (
+                               E_AUTHENTICATION_SESSION (object)));
+                       return;
+
+               case PROP_SERVER:
+                       g_value_set_object (
+                               value,
+                               e_authentication_session_get_server (
+                               E_AUTHENTICATION_SESSION (object)));
+                       return;
+
+               case PROP_SOURCE_UID:
+                       g_value_set_string (
+                               value,
+                               e_authentication_session_get_source_uid (
+                               E_AUTHENTICATION_SESSION (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+authentication_session_dispose (GObject *object)
+{
+       EAuthenticationSessionPrivate *priv;
+
+       priv = E_AUTHENTICATION_SESSION_GET_PRIVATE (object);
+
+       if (priv->server != NULL) {
+               g_object_unref (priv->server);
+               priv->server = NULL;
+       }
+
+       if (priv->authenticator != NULL) {
+               g_object_unref (priv->authenticator);
+               priv->authenticator = NULL;
+       }
+
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_authentication_session_parent_class)->
+               dispose (object);
+}
+
+static void
+authentication_session_finalize (GObject *object)
+{
+       EAuthenticationSessionPrivate *priv;
+
+       priv = E_AUTHENTICATION_SESSION_GET_PRIVATE (object);
+
+       g_mutex_free (priv->property_lock);
+
+       g_free (priv->source_uid);
+       g_free (priv->prompt_title);
+       g_free (priv->prompt_message);
+       g_free (priv->prompt_description);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_authentication_session_parent_class)->
+               finalize (object);
+}
+
+static void
+authentication_session_constructed (GObject *object)
+{
+       EAuthenticationSession *session;
+       ESourceAuthenticator *authenticator;
+       ESourceRegistryServer *server;
+       ESource *source;
+       const gchar *source_uid;
+
+       session = E_AUTHENTICATION_SESSION (object);
+
+       /* Chain up to parent's constructed() method. */
+       G_OBJECT_CLASS (e_authentication_session_parent_class)->
+               constructed (object);
+
+       /* If the server knows about the data source UID we've been
+        * given, then we can auto-configure our own prompt strings. */
+
+       server = e_authentication_session_get_server (session);
+       source_uid = e_authentication_session_get_source_uid (session);
+       authenticator = e_authentication_session_get_authenticator (session);
+
+       source = e_source_registry_server_ref_source (server, source_uid);
+       if (source != NULL) {
+               gchar *prompt_title = NULL;
+               gchar *prompt_message = NULL;
+               gchar *prompt_description = NULL;
+
+               e_source_authenticator_get_prompt_strings (
+                       authenticator, source,
+                       &prompt_title,
+                       &prompt_message,
+                       &prompt_description);
+
+               g_object_set (
+                       session,
+                       "prompt-title", prompt_title,
+                       "prompt-message", prompt_message,
+                       "prompt-description", prompt_description,
+                       NULL);
+
+               g_free (prompt_title);
+               g_free (prompt_message);
+               g_free (prompt_description);
+
+               g_object_unref (source);
+       }
+}
+
+/* Helper for authentication_session_execute() */
+static void
+authentication_session_execute_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 =
+               e_authentication_session_execute_sync (
+                       E_AUTHENTICATION_SESSION (object),
+                       cancellable, &error);
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+}
+
+static EAuthenticationSessionResult
+authentication_session_execute_sync (EAuthenticationSession *session,
+                                     GCancellable *cancellable,
+                                     GError **error)
+{
+       ESourceAuthenticator *authenticator;
+       EAuthenticationSessionResult session_result;
+       ESourceAuthenticationResult auth_result;
+       ESourceRegistryServer *server;
+       ESource *source;
+       GcrPrompt *prompt;
+       GString *password_string;
+       gboolean allow_auth_prompt;
+       const gchar *label;
+       const gchar *source_uid;
+       const gchar *prompt_password;
+       gchar *stored_password = NULL;
+       gboolean success;
+       GError *local_error = NULL;
+
+       /* XXX I moved the execute() operation into a class method thinking
+        *     we might someday want to subclass EAuthenticationSession and
+        *     override this method to make it behave differently.
+        *
+        *     It would be a little premature to do this now, but we might
+        *     also want to use the basic algorithm here as a template and
+        *     turn the password lookup/delete/store operations into class
+        *     methods that could also be overridden.  I reserved adequate
+        *     space in the class struct for this should the need arise.
+        *
+        *     For now though we'll keep it simple.  I don't want to over-
+        *     engineer this too much in trying to make it future-proof.
+        */
+
+       authentication_session_msg (session, "Initiated");
+
+       server = e_authentication_session_get_server (session);
+       source_uid = e_authentication_session_get_source_uid (session);
+       authenticator = e_authentication_session_get_authenticator (session);
+
+       success = e_authentication_session_lookup_password_sync (
+               session, cancellable, &stored_password, error);
+
+       if (!success) {
+               session_result = E_AUTHENTICATION_SESSION_ERROR;
+               goto exit;
+       }
+
+       auth_result = E_SOURCE_AUTHENTICATION_REJECTED;
+
+       /* If we found a stored password, signal the client without
+        * interrupting the user.  Note, if the client responds with
+        * REJECTED, we'll have to interrupt the user after all. */
+       if (stored_password != NULL) {
+               password_string = g_string_new (stored_password);
+
+               auth_result = e_source_authenticator_try_password_sync (
+                       authenticator, password_string, cancellable, error);
+
+               g_string_free (password_string, TRUE);
+               password_string = NULL;
+
+               g_free (stored_password);
+               stored_password = NULL;
+       }
+
+       if (auth_result == E_SOURCE_AUTHENTICATION_ERROR) {
+               session_result = E_AUTHENTICATION_SESSION_ERROR;
+               goto exit;
+       }
+
+       if (auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
+               session_result = E_AUTHENTICATION_SESSION_SUCCESS;
+               goto exit;
+       }
+
+       g_warn_if_fail (auth_result == E_SOURCE_AUTHENTICATION_REJECTED);
+
+       /* The stored password is bad so delete it from the keyring.
+        * Failure here does not affect the outcome of this operation,
+        * but leave a breadcrumb as evidence that something went wrong. */
+
+       e_authentication_session_delete_password_sync (
+               session, cancellable, &local_error);
+
+       if (local_error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, local_error->message);
+               g_clear_error (&local_error);
+       }
+
+       /* This will return NULL if the authenticating data source
+        * has not yet been submitted to the D-Bus registry service. */
+       source = e_source_registry_server_ref_source (server, source_uid);
+       if (source != NULL) {
+               allow_auth_prompt =
+                       e_server_side_source_get_allow_auth_prompt (
+                       E_SERVER_SIDE_SOURCE (source));
+               g_object_unref (source);
+       } else {
+               allow_auth_prompt = TRUE;
+       }
+
+       /* Check if we're allowed to interrupt the user for a password.
+        * If not, we have no choice but to dismiss the authentication
+        * request. */
+       if (!allow_auth_prompt) {
+               session_result = E_AUTHENTICATION_SESSION_DISMISSED;
+               goto exit;
+       }
+
+       /* Configure a system prompt. */
+
+       prompt = gcr_system_prompt_open (
+               SYSTEM_PROMPT_TIMEOUT, cancellable, error);
+
+       if (prompt == NULL) {
+               session_result = E_AUTHENTICATION_SESSION_ERROR;
+               goto exit;
+       }
+
+       g_object_bind_property (
+               session, "prompt-title",
+               prompt, "title",
+               G_BINDING_SYNC_CREATE);
+
+       g_object_bind_property (
+               session, "prompt-message",
+               prompt, "message",
+               G_BINDING_SYNC_CREATE);
+
+       g_object_bind_property (
+               session, "prompt-description",
+               prompt, "description",
+               G_BINDING_SYNC_CREATE);
+
+       label = _("Add this password to your keyring");
+       gcr_prompt_set_choice_label (prompt, label);
+       gcr_prompt_set_choice_chosen (prompt, TRUE);
+
+try_again:
+
+       /* Prompt the user for a password. */
+
+       prompt_password = gcr_prompt_password (
+               prompt, cancellable, &local_error);
+
+       if (local_error != NULL) {
+               session_result = E_AUTHENTICATION_SESSION_ERROR;
+               g_propagate_error (error, local_error);
+               local_error = NULL;
+               goto close_prompt;
+       }
+
+       /* No password and no error indicates a dismissal. */
+       if (prompt_password == NULL) {
+               session_result = E_AUTHENTICATION_SESSION_DISMISSED;
+               goto close_prompt;
+       }
+
+       /* Attempt authentication with the provided password. */
+
+       password_string = g_string_new (prompt_password);
+
+       auth_result = e_source_authenticator_try_password_sync (
+               authenticator, password_string, cancellable, error);
+
+       g_string_free (password_string, TRUE);
+       password_string = NULL;
+
+       if (auth_result == E_SOURCE_AUTHENTICATION_ERROR) {
+               session_result = E_AUTHENTICATION_SESSION_ERROR;
+               goto close_prompt;
+       }
+
+       if (auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
+               gboolean permanently;
+
+               permanently = gcr_prompt_get_choice_chosen (prompt);
+               session_result = E_AUTHENTICATION_SESSION_SUCCESS;
+
+               /* Failure here does not affect the outcome of this
+                * operation, but leave a breadcrumb as evidence that
+                * something went wrong. */
+
+               e_authentication_session_store_password_sync (
+                       session, prompt_password, permanently,
+                       cancellable, &local_error);
+
+               if (local_error != NULL) {
+                       g_warning ("%s: %s", G_STRFUNC, local_error->message);
+                       g_clear_error (&local_error);
+               }
+
+               goto close_prompt;
+       }
+
+       g_warn_if_fail (auth_result == E_SOURCE_AUTHENTICATION_REJECTED);
+
+       gcr_prompt_set_warning (prompt, _("Password was incorrect"));
+
+       goto try_again;
+
+close_prompt:
+
+       /* Failure here does not affect the outcome of this operation,
+        * but leave a breadcrumb as evidence that something went wrong. */
+
+       gcr_system_prompt_close (
+               GCR_SYSTEM_PROMPT (prompt),
+               cancellable, &local_error);
+
+       if (local_error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, local_error->message);
+               g_clear_error (&local_error);
+       }
+
+       g_object_unref (prompt);
+
+exit:
+
+       switch (session_result) {
+               case E_AUTHENTICATION_SESSION_ERROR:
+                       authentication_session_msg (
+                               session, "Complete (ERROR)");
+                       break;
+               case E_AUTHENTICATION_SESSION_SUCCESS:
+                       authentication_session_msg (
+                               session, "Complete (SUCCESS)");
+                       break;
+               case E_AUTHENTICATION_SESSION_DISMISSED:
+                       authentication_session_msg (
+                               session, "Complete (DISMISSED)");
+                       break;
+               default:
+                       g_warn_if_reached ();
+       }
+
+       return session_result;
+}
+
+static void
+authentication_session_execute (EAuthenticationSession *session,
+                                gint io_priority,
+                                GCancellable *cancellable,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+
+       async_context = g_slice_new0 (AsyncContext);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (session), callback, user_data,
+               e_authentication_session_execute);
+
+       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, authentication_session_execute_thread,
+               io_priority, cancellable);
+
+       g_object_unref (simple);
+}
+
+static EAuthenticationSessionResult
+authentication_session_execute_finish (EAuthenticationSession *session,
+                                       GAsyncResult *result,
+                                       GError **error)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (session),
+               e_authentication_session_execute),
+               E_AUTHENTICATION_SESSION_DISMISSED);
+
+       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 E_AUTHENTICATION_SESSION_ERROR;
+
+       return async_context->auth_result;
+}
+
+static void
+e_authentication_session_class_init (EAuthenticationSessionClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (
+               class, sizeof (EAuthenticationSessionPrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = authentication_session_set_property;
+       object_class->get_property = authentication_session_get_property;
+       object_class->dispose = authentication_session_dispose;
+       object_class->finalize = authentication_session_finalize;
+       object_class->constructed = authentication_session_constructed;
+
+       class->execute_sync = authentication_session_execute_sync;
+       class->execute = authentication_session_execute;
+       class->execute_finish = authentication_session_execute_finish;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_AUTHENTICATOR,
+               g_param_spec_object (
+                       "authenticator",
+                       "Authenticator",
+                       "Handles authentication attempts",
+                       E_TYPE_SOURCE_AUTHENTICATOR,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_PROMPT_DESCRIPTION,
+               g_param_spec_string (
+                       "prompt-description",
+                       "Prompt Description",
+                       "The detailed description of the prompt",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_PROMPT_MESSAGE,
+               g_param_spec_string (
+                       "prompt-message",
+                       "Prompt Message",
+                       "The prompt message for the user",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_PROMPT_TITLE,
+               g_param_spec_string (
+                       "prompt-title",
+                       "Prompt Title",
+                       "The title of the prompt",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_SERVER,
+               g_param_spec_object (
+                       "server",
+                       "Server",
+                       "The server to which the session belongs",
+                       E_TYPE_SOURCE_REGISTRY_SERVER,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_SOURCE_UID,
+               g_param_spec_string (
+                       "source-uid",
+                       "Source UID",
+                       "Unique ID of the data source being authenticated",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_authentication_session_init (EAuthenticationSession *session)
+{
+       session->priv = E_AUTHENTICATION_SESSION_GET_PRIVATE (session);
+       session->priv->property_lock = g_mutex_new ();
+}
+
+GQuark
+e_authentication_session_error_quark (void)
+{
+       static GQuark quark = 0;
+
+       if (G_UNLIKELY (quark == 0)) {
+               const gchar *string;
+               string = "e-authentication-session-error-quark";
+               quark = g_quark_from_static_string (string);
+       }
+
+       return quark;
+}
+
+/**
+ * e_authentication_session_new:
+ * @server: an #ESourceRegistryServer
+ * @authenticator: an #ESourceAuthenticator
+ * @source_uid: a data source identifier
+ *
+ * Creates a new #EAuthenticationSession instance for @server using
+ * @authenticator to handle authentication attempts.
+ *
+ * Note that @source_uid does not necessarily have to be known to the
+ * @server, as in the case when configuring a new data source, but it
+ * still has to be unique.
+ *
+ * Returns: a newly-created #EAuthenticationSession
+ *
+ * Since: 3.6
+ **/
+EAuthenticationSession *
+e_authentication_session_new (ESourceRegistryServer *server,
+                              ESourceAuthenticator *authenticator,
+                              const gchar *source_uid)
+{
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
+       g_return_val_if_fail (E_IS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+       g_return_val_if_fail (source_uid != NULL, NULL);
+
+       return g_object_new (
+               E_TYPE_AUTHENTICATION_SESSION,
+               "server", server,
+               "authenticator", authenticator,
+               "source-uid", source_uid, NULL);
+}
+
+/**
+ * e_authentication_session_get_server:
+ * @session: an #EAuthenticationSession
+ *
+ * Returns the #ESourceRegistryServer to which @session belongs.
+ *
+ * Returns: the #ESourceRegistryServer for @session
+ *
+ * Since: 3.6
+ **/
+ESourceRegistryServer *
+e_authentication_session_get_server (EAuthenticationSession *session)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       return session->priv->server;
+}
+
+/**
+ * e_authentication_session_get_authenticator:
+ * @session: an #EAuthenticationSession
+ *
+ * Returns the #ESourceAuthenticator handling authentication attempts for
+ * @session.  This is usually an #EAuthenticationMediator but can also be
+ * a custom collection backend derived from #ECollectionBackend.
+ *
+ * Returns: the #ESourceAuthenticator for @session
+ *
+ * Since: 3.6
+ **/
+ESourceAuthenticator *
+e_authentication_session_get_authenticator (EAuthenticationSession *session)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       return session->priv->authenticator;
+}
+
+/**
+ * e_authentication_session_get_source_uid:
+ * @session: an #EAuthenticationSession
+ *
+ * Returns the #ESource:uid of the authenticating data source.  The data
+ * source may or may not be known to the #EAuthenticationSession:server.
+ *
+ * Returns: the UID of the authenticating data source
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_authentication_session_get_source_uid (EAuthenticationSession *session)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       return session->priv->source_uid;
+}
+
+/**
+ * e_authentication_session_get_prompt_title:
+ * @session: an #EAuthenticationSession
+ *
+ * Returns the text used for the password prompt title should prompting
+ * be necessary.  See #GcrPrompt for more details about password prompts.
+ *
+ * Returns: the password prompt title
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_authentication_session_get_prompt_title (EAuthenticationSession *session)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       return session->priv->prompt_title;
+}
+
+/**
+ * e_authentication_session_dup_prompt_title:
+ * @session: an #EAuthenticationSession
+ *
+ * Thread-safe variation of e_authentication_session_get_prompt_title().
+ * Use this function when accessing @session from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #EAuthenticationSession:prompt-title
+ *
+ * Since: 3.6
+ **/
+gchar *
+e_authentication_session_dup_prompt_title (EAuthenticationSession *session)
+{
+       const gchar *protected;
+       gchar *duplicate;
+
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       g_mutex_lock (session->priv->property_lock);
+
+       protected = e_authentication_session_get_prompt_title (session);
+       duplicate = g_strdup (protected);
+
+       g_mutex_unlock (session->priv->property_lock);
+
+       return duplicate;
+}
+
+/**
+ * e_authentication_session_set_prompt_title:
+ * @session: an #EAuthenticationSession
+ * @prompt_title: the password prompt title, or %NULL
+ *
+ * Sets the text used for the password prompt title should prompting be
+ * necessary.  See #GcrPrompt for more details about password prompts.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_session_set_prompt_title (EAuthenticationSession *session,
+                                           const gchar *prompt_title)
+{
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+
+       g_mutex_lock (session->priv->property_lock);
+
+       g_free (session->priv->prompt_title);
+       session->priv->prompt_title = g_strdup (prompt_title);
+
+       g_mutex_unlock (session->priv->property_lock);
+
+       g_object_notify (G_OBJECT (session), "prompt-title");
+}
+
+/**
+ * e_authentication_session_get_prompt_message:
+ * @session: an #EAuthenticationSession
+ *
+ * Returns the text used for the password prompt message should prompting
+ * be necessary.  See #GcrPrompt for more details about password prompts.
+ *
+ * Returns: the password prompt message
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_authentication_session_get_prompt_message (EAuthenticationSession *session)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       return session->priv->prompt_message;
+}
+
+/**
+ * e_authentication_session_dup_prompt_message:
+ * @session: an #EAuthenticationSession
+ *
+ * Thread-safe variation of e_authentication_session_get_prompt_message().
+ * Use this function when accessing @session from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #EAuthenticationSession:prompt-message
+ *
+ * Since: 3.6
+ **/
+gchar *
+e_authentication_session_dup_prompt_message (EAuthenticationSession *session)
+{
+       const gchar *protected;
+       gchar *duplicate;
+
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       g_mutex_lock (session->priv->property_lock);
+
+       protected = e_authentication_session_get_prompt_message (session);
+       duplicate = g_strdup (protected);
+
+       g_mutex_unlock (session->priv->property_lock);
+
+       return duplicate;
+}
+
+/**
+ * e_authentication_session_set_prompt_message:
+ * @session: an #EAuthenticationSession
+ * @prompt_message: the password prompt message, or %NULL
+ *
+ * Sets the text used for the password prompt message should prompting be
+ * necessary.  See #GcrPrompt for more details about password prompts.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_session_set_prompt_message (EAuthenticationSession *session,
+                                             const gchar *prompt_message)
+{
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+
+       g_mutex_lock (session->priv->property_lock);
+
+       g_free (session->priv->prompt_message);
+       session->priv->prompt_message = g_strdup (prompt_message);
+
+       g_mutex_unlock (session->priv->property_lock);
+
+       g_object_notify (G_OBJECT (session), "prompt-message");
+}
+
+/**
+ * e_authentication_session_get_prompt_description:
+ * @session: an #EAuthenticationSession
+ *
+ * Returns the text used for the password prompt description should prompting
+ * be necessary.  See #GcrPrompt for more details about password prompts.
+ *
+ * Returns: the password prompt description
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_authentication_session_get_prompt_description (EAuthenticationSession *session)
+{
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       return session->priv->prompt_description;
+}
+
+/**
+ * e_authentication_session_dup_prompt_description:
+ * @session: an #EAuthenticationSession
+ *
+ * Thread-safe variation of e_authentication_session_get_prompt_description().
+ * Use this function when accessing @session from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of
+ *          #EAuthenticationSession:prompt-description
+ *
+ * Since: 3.6
+ **/
+gchar *
+e_authentication_session_dup_prompt_description (EAuthenticationSession *session)
+{
+       const gchar *protected;
+       gchar *duplicate;
+
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), NULL);
+
+       g_mutex_lock (session->priv->property_lock);
+
+       protected = e_authentication_session_get_prompt_description (session);
+       duplicate = g_strdup (protected);
+
+       g_mutex_unlock (session->priv->property_lock);
+
+       return duplicate;
+}
+
+/**
+ * e_authentication_session_set_prompt_description:
+ * @session: an #EAuthenticationSession
+ * @prompt_description: the password prompt description
+ *
+ * Sets the text used for the password prompt description should prompting
+ * be necessary.  See #GcrPrompt for more details about password prompts.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_session_set_prompt_description (EAuthenticationSession *session,
+                                                 const gchar *prompt_description)
+{
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+
+       g_mutex_lock (session->priv->property_lock);
+
+       g_free (session->priv->prompt_description);
+       session->priv->prompt_description = g_strdup (prompt_description);
+
+       g_mutex_unlock (session->priv->property_lock);
+
+       g_object_notify (G_OBJECT (session), "prompt-description");
+}
+
+/**
+ * e_authentication_session_execute_sync:
+ * @session: an #EAuthenticationSession
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Executes an authentication session.
+ *
+ * First the secret service is queried for a stored password.  If found,
+ * an authentication attempt is made without disturbing the user.  If no
+ * stored password is found, or if the stored password is rejected, the
+ * user is shown a system-modal dialog requesting a password.  Further
+ * authentication attempts are repeated with user-provided passwords
+ * until authentication is verified or the user dismisses the prompt.
+ * The returned #EAuthenticationSessionResult indicates the outcome.
+ *
+ * If an error occurs while interacting with the secret service, or while
+ * prompting the user for a password, or while attempting authentication,
+ * the function sets @error and returns #E_AUTHENTICATION_SESSION_ERROR.
+ *
+ * Returns: the result of the authentication session
+ *
+ * Since: 3.6
+ **/
+EAuthenticationSessionResult
+e_authentication_session_execute_sync (EAuthenticationSession *session,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       EAuthenticationSessionClass *class;
+
+       g_return_val_if_fail (
+               E_IS_AUTHENTICATION_SESSION (session),
+               E_AUTHENTICATION_SESSION_DISMISSED);
+
+       class = E_AUTHENTICATION_SESSION_GET_CLASS (session);
+       g_return_val_if_fail (
+               class->execute_sync != NULL,
+               E_AUTHENTICATION_SESSION_DISMISSED);
+
+       return class->execute_sync (session, cancellable, error);
+}
+
+/**
+ * e_authentication_session_execute:
+ * @session: an #EAuthenticationSession
+ * @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
+ *
+ * See e_authentication_session_execute_sync() for details.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_authentication_session_execute_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_session_execute (EAuthenticationSession *session,
+                                  gint io_priority,
+                                  GCancellable *cancellable,
+                                  GAsyncReadyCallback callback,
+                                  gpointer user_data)
+{
+       EAuthenticationSessionClass *class;
+
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+
+       class = E_AUTHENTICATION_SESSION_GET_CLASS (session);
+       g_return_if_fail (class->execute != NULL);
+
+       return class->execute (
+               session, io_priority, cancellable, callback, user_data);
+}
+
+/**
+ * e_authentication_session_execute_finish:
+ * @session: an #EAuthenticationSession
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_authentication_session_execute().
+ *
+ * If an error occurs while interacting with the secret service, or while
+ * prompting the user for a password, or while attempting authentication,
+ * the function sets @error and returns #E_AUTHENTICATION_SESSION_ERROR.
+ *
+ * Returns: the result of the authentication session
+ *
+ * Since: 3.6
+ **/
+EAuthenticationSessionResult
+e_authentication_session_execute_finish (EAuthenticationSession *session,
+                                         GAsyncResult *result,
+                                         GError **error)
+{
+       EAuthenticationSessionClass *class;
+
+       g_return_val_if_fail (
+               E_IS_AUTHENTICATION_SESSION (session),
+               E_AUTHENTICATION_SESSION_DISMISSED);
+       g_return_val_if_fail (
+               G_IS_ASYNC_RESULT (result),
+               E_AUTHENTICATION_SESSION_DISMISSED);
+
+       class = E_AUTHENTICATION_SESSION_GET_CLASS (session);
+       g_return_val_if_fail (
+               class->execute_finish != NULL,
+               E_AUTHENTICATION_SESSION_DISMISSED);
+
+       return class->execute_finish (session, result, error);
+}
+
+/* Helper for e_authentication_session_store_password() */
+static void
+authentication_session_store_password_thread (GSimpleAsyncResult *simple,
+                                              GObject *object,
+                                              GCancellable *cancellable)
+{
+       AsyncContext *async_context;
+       GError *error = NULL;
+
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       e_authentication_session_store_password_sync (
+               E_AUTHENTICATION_SESSION (object),
+               async_context->password,
+               async_context->permanently,
+               cancellable, &error);
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_authentication_session_store_password_sync:
+ * @session: an #EAuthenticationSession
+ * @password: the password to store
+ * @permanently: store permanently or just for the session
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Store a password for the data source that @session is representing.
+ * If @permanently is %TRUE, the password is stored in the default keyring.
+ * Otherwise the password is stored in the memory-only session keyring.  If
+ * an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_authentication_session_store_password_sync (EAuthenticationSession *session,
+                                              const gchar *password,
+                                              gboolean permanently,
+                                              GCancellable *cancellable,
+                                              GError **error)
+{
+       GnomeKeyringResult result;
+       const gchar *keyring;
+       const gchar *uid;
+       gchar *display_name;
+
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), FALSE);
+       g_return_val_if_fail (password != NULL, FALSE);
+
+       /* XXX Synchronous gnome-keyring functions are not cancellable.
+        *     Maybe they will be someday, but in the meantime check for
+        *     cancellation ourselves before doing this. */
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return FALSE;
+
+       if (permanently)
+               keyring = GNOME_KEYRING_DEFAULT;
+       else
+               keyring = GNOME_KEYRING_SESSION;
+
+       uid = e_authentication_session_get_source_uid (session);
+       display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
+
+       result = gnome_keyring_store_password_sync (
+               &schema, keyring, display_name, password,
+               KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+
+       if (result == GNOME_KEYRING_RESULT_CANCELLED) {
+               g_cancellable_cancel (cancellable);
+               g_set_error_literal (
+                       error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       _("Keyring operation was cancelled"));
+
+       } else if (result != GNOME_KEYRING_RESULT_OK) {
+               g_set_error_literal (
+                       error, E_AUTHENTICATION_SESSION_KEYRING_ERROR,
+                       result, gnome_keyring_result_to_message (result));
+       }
+
+       g_free (display_name);
+
+       return (result == GNOME_KEYRING_RESULT_OK);
+}
+
+/**
+ * e_authentication_session_store_password:
+ * @session: an #EAuthenticationSession
+ * @password: the password to store
+ * @permanently: store permanently or just for the session
+ * @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 stores a password for the data source that @session
+ * is representing.  If @permanently is %TRUE, the password is stored in the
+ * default keyring.  Otherwise the password is stored in the memory-only
+ * session keyring.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_authentication_session_store_password_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_session_store_password (EAuthenticationSession *session,
+                                         const gchar *password,
+                                         gboolean permanently,
+                                         gint io_priority,
+                                         GCancellable *cancellable,
+                                         GAsyncReadyCallback callback,
+                                         gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+       g_return_if_fail (password != NULL);
+
+       async_context = g_slice_new0 (AsyncContext);
+       async_context->password = g_strdup (password);
+       async_context->permanently = permanently;
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (session), callback, user_data,
+               e_authentication_session_store_password);
+
+       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, authentication_session_store_password_thread,
+               io_priority, cancellable);
+
+       g_object_unref (simple);
+}
+
+/**
+ * e_authentication_session_store_password_finish:
+ * @session: an #EAuthenticationSession
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finished the operation started with
+ * e_authentication_session_store_password().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_authentication_session_store_password_finish (EAuthenticationSession *session,
+                                                GAsyncResult *result,
+                                                GError **error)
+{
+       GSimpleAsyncResult *simple;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (session),
+               e_authentication_session_store_password), FALSE);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+
+       /* Assume success unless a GError is set. */
+       return !g_simple_async_result_propagate_error (simple, error);
+}
+
+/* Helper for e_authentication_session_store_password() */
+static void
+authentication_session_lookup_password_thread (GSimpleAsyncResult *simple,
+                                               GObject *object,
+                                               GCancellable *cancellable)
+{
+       AsyncContext *async_context;
+       GError *error = NULL;
+
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       e_authentication_session_lookup_password_sync (
+               E_AUTHENTICATION_SESSION (object), cancellable,
+               &async_context->password, &error);
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_authentication_session_lookup_password_sync:
+ * @session: an #EAuthenticationSession
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @password: return location for the password, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Looks up a password for the data source that @session is
+ * representing.  Both the default and session keyrings are queried.
+ *
+ * Note the boolean return value indicates whether the lookup operation
+ * itself completed successfully, not whether a password was found.  If
+ * no password was found, the function will set @password to %NULL but
+ * still return %TRUE.  If an error occurs, the function sets @error
+ * and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_authentication_session_lookup_password_sync (EAuthenticationSession *session,
+                                               GCancellable *cancellable,
+                                               gchar **password,
+                                               GError **error)
+{
+       GnomeKeyringResult result;
+       const gchar *uid;
+       gchar *temp = NULL;
+
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), FALSE);
+
+       /* XXX Synchronous gnome-keyring functions are not cancellable.
+        *     Maybe they will be someday, but in the meantime check for
+        *     cancellation ourselves before doing this. */
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return FALSE;
+
+       uid = e_authentication_session_get_source_uid (session);
+
+       result = gnome_keyring_find_password_sync (
+               &schema, &temp, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+
+       /* Not finding a data source password is not an error. */
+       if (result == GNOME_KEYRING_RESULT_NO_MATCH) {
+               result = GNOME_KEYRING_RESULT_OK;
+               gnome_keyring_free_password (temp);
+               temp = NULL;
+
+       } else if (result == GNOME_KEYRING_RESULT_CANCELLED) {
+               g_cancellable_cancel (cancellable);
+               g_set_error_literal (
+                       error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       _("Keyring operation was cancelled"));
+               g_warn_if_fail (temp == NULL);
+
+       } else if (result != GNOME_KEYRING_RESULT_OK) {
+               g_set_error_literal (
+                       error, E_AUTHENTICATION_SESSION_KEYRING_ERROR,
+                       result, gnome_keyring_result_to_message (result));
+               g_warn_if_fail (temp == NULL);
+       }
+
+       /* Do not impose gnome-keyring's non-pageable memory API on the
+        * caller, it's not worth the hassle.  Return a newly-allocated
+        * string so the caller can free it with g_free(). */
+       if (password != NULL)
+               *password = g_strdup (temp);
+
+       if (temp != NULL)
+               gnome_keyring_free_password (temp);
+
+       return (result == GNOME_KEYRING_RESULT_OK);
+}
+
+/**
+ * e_authentication_session_lookup_password:
+ * @session: an #EAuthenticationSession
+ * @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 looks up a password for the data source that @session
+ * is representing.  Both the default and session keyrings are queried.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_authentication_session_lookup_password_finish() to get the
+ * result of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_session_lookup_password (EAuthenticationSession *session,
+                                          gint io_priority,
+                                          GCancellable *cancellable,
+                                          GAsyncReadyCallback callback,
+                                          gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+
+       async_context = g_slice_new0 (AsyncContext);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (session), callback, user_data,
+               e_authentication_session_lookup_password);
+
+       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, authentication_session_lookup_password_thread,
+               io_priority, cancellable);
+
+       g_object_unref (simple);
+}
+
+/**
+ * e_authentication_session_lookup_password_finish:
+ * @session: an #EAuthenticationSession
+ * @result: a #GAsyncResult
+ * @password: return location for the password, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * e_authentication_session_lookup_password().
+ *
+ * Note the boolean return value indicates whether the lookup operation
+ * itself completed successfully, not whether a password was found.  If
+ * no password was found, the function will set @password to %NULL but
+ * still return %TRUE.  If an error occurs, the function sets @error
+ * and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_authentication_session_lookup_password_finish (EAuthenticationSession *session,
+                                                 GAsyncResult *result,
+                                                 gchar **password,
+                                                 GError **error)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (session),
+               e_authentication_session_lookup_password), 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 FALSE;
+
+       if (password != NULL) {
+               *password = async_context->password;
+               async_context->password = NULL;
+       }
+
+       return TRUE;
+}
+
+/* Helper for e_authentication_session_delete_password() */
+static void
+authentication_session_delete_password_thread (GSimpleAsyncResult *simple,
+                                               GObject *object,
+                                               GCancellable *cancellable)
+{
+       GError *error = NULL;
+
+       e_authentication_session_delete_password_sync (
+               E_AUTHENTICATION_SESSION (object), cancellable, &error);
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_authentication_session_delete_password_sync:
+ * @session: an #EAuthenticationSession
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Deletes the password for the data source that @session is
+ * representing from either the default keyring or session keyring.
+ *
+ * Note the boolean return value indicates whether the delete operation
+ * itself completed successfully, not whether a password was found and
+ * deleted.  If no password was found, the function will still return
+ * %TRUE.  If an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_authentication_session_delete_password_sync (EAuthenticationSession *session,
+                                               GCancellable *cancellable,
+                                               GError **error)
+{
+       GnomeKeyringResult result;
+       const gchar *uid;
+
+       g_return_val_if_fail (E_IS_AUTHENTICATION_SESSION (session), FALSE);
+
+       /* XXX Synchronous gnome-keyring functions are not cancellable.
+        *     Maybe they will be someday, but in the meantime check for
+        *     cancellation ourselves before doing this. */
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return FALSE;
+
+       uid = e_authentication_session_get_source_uid (session);
+
+       result = gnome_keyring_delete_password_sync (
+               &schema, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+
+       /* Not finding a data source password is not an error. */
+       if (result == GNOME_KEYRING_RESULT_NO_MATCH) {
+               result = GNOME_KEYRING_RESULT_OK;
+
+       } else if (result == GNOME_KEYRING_RESULT_CANCELLED) {
+               g_cancellable_cancel (cancellable);
+               g_set_error_literal (
+                       error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                       _("Keyring operation was cancelled"));
+
+       } else if (result != GNOME_KEYRING_RESULT_OK) {
+               g_set_error_literal (
+                       error, E_AUTHENTICATION_SESSION_KEYRING_ERROR,
+                       result, gnome_keyring_result_to_message (result));
+       }
+
+       return (result == GNOME_KEYRING_RESULT_OK);
+}
+
+/**
+ * e_authentication_session_delete_password:
+ * @session: an #EAuthenticationSession
+ * @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
+ *
+ * Asyncronously deletes the password for the data source that @session
+ * is representing from either the default keyring or session keyring.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_authentication_session_delete_password_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_authentication_session_delete_password (EAuthenticationSession *session,
+                                          gint io_priority,
+                                          GCancellable *cancellable,
+                                          GAsyncReadyCallback callback,
+                                          gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (session), callback, user_data,
+               e_authentication_session_delete_password);
+
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+       g_simple_async_result_run_in_thread (
+               simple, authentication_session_delete_password_thread,
+               io_priority, cancellable);
+
+       g_object_unref (simple);
+}
+
+/**
+ * e_authentication_session_delete_password_finish:
+ * @session: an #EAuthenticationSession
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * e_authentication_session_delete_password().
+ *
+ * Note the boolean return value indicates whether the delete operation
+ * itself completed successfully, not whether a password was found and
+ * deleted.  If no password was found, the function will still return
+ * %TRUE.  If an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_authentication_session_delete_password_finish (EAuthenticationSession *session,
+                                                 GAsyncResult *result,
+                                                 GError **error)
+{
+       GSimpleAsyncResult *simple;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (session),
+               e_authentication_session_delete_password), 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/libebackend/e-authentication-session.h b/libebackend/e-authentication-session.h
new file mode 100644 (file)
index 0000000..4ef4e78
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * e-authentication-session.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_AUTHENTICATION_SESSION_H
+#define E_AUTHENTICATION_SESSION_H
+
+#include <gio/gio.h>
+
+/* This needs to be in the public header since we're
+ * reusing the GnomeKeyringResult enum for error codes. */
+#include <gnome-keyring.h>
+
+#include <libebackend/e-backend-enums.h>
+#include <libedataserver/e-source-authenticator.h>
+
+/* Standard GObject macros */
+#define E_TYPE_AUTHENTICATION_SESSION \
+       (e_authentication_session_get_type ())
+#define E_AUTHENTICATION_SESSION(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_AUTHENTICATION_SESSION, EAuthenticationSession))
+#define E_AUTHENTICATION_SESSION_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_AUTHENTICATION_SESSION, EAuthenticationSessionClass))
+#define E_IS_AUTHENTICATION_SESSION(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_AUTHENTICATION_SESSION))
+#define E_IS_AUTHENTICATION_SESSION_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_AUTHENTICATION_SESSION))
+#define E_AUTHENTICATION_SESSION_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_AUTHENTICATION_SESSION, EAuthenticationSessionClass))
+
+/**
+ * E_AUTHENTICATION_SESSION_KEYRING_ERROR:
+ *
+ * Error domain for password storage and retrieval.  Error codes in this
+ * domain are defined by the #GnomeKeyringResult enumeration.  See #GError
+ * for information on error domains.
+ *
+ * Since: 3.6
+ **/
+#define E_AUTHENTICATION_SESSION_KEYRING_ERROR \
+       (e_authentication_session_error_quark ())
+
+G_BEGIN_DECLS
+
+struct _ESourceRegistryServer;
+
+typedef struct _EAuthenticationSession EAuthenticationSession;
+typedef struct _EAuthenticationSessionClass EAuthenticationSessionClass;
+typedef struct _EAuthenticationSessionPrivate EAuthenticationSessionPrivate;
+
+/**
+ * EAuthenticationSession:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.6
+ **/
+struct _EAuthenticationSession {
+       GObject parent;
+       EAuthenticationSessionPrivate *priv;
+};
+
+struct _EAuthenticationSessionClass {
+       GObjectClass parent_class;
+
+       /* Methods */
+       EAuthenticationSessionResult
+                       (*execute_sync) (EAuthenticationSession *session,
+                                        GCancellable *cancellable,
+                                        GError **error);
+       void            (*execute)      (EAuthenticationSession *session,
+                                        gint io_priority,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data);
+       EAuthenticationSessionResult
+                       (*execute_finish)
+                                       (EAuthenticationSession *session,
+                                        GAsyncResult *result,
+                                        GError **error);
+
+       /* Reserved slots. */
+       gpointer reserved[16];
+};
+
+GQuark         e_authentication_session_error_quark
+                                       (void) G_GNUC_CONST;
+GType          e_authentication_session_get_type
+                                       (void) G_GNUC_CONST;
+EAuthenticationSession *
+               e_authentication_session_new
+                                       (struct _ESourceRegistryServer *server,
+                                        ESourceAuthenticator *authenticator,
+                                        const gchar *source_uid);
+struct _ESourceRegistryServer *
+               e_authentication_session_get_server
+                                       (EAuthenticationSession *session);
+ESourceAuthenticator *
+               e_authentication_session_get_authenticator
+                                       (EAuthenticationSession *session);
+const gchar *  e_authentication_session_get_source_uid
+                                       (EAuthenticationSession *session);
+const gchar *  e_authentication_session_get_prompt_title
+                                       (EAuthenticationSession *session);
+gchar *                e_authentication_session_dup_prompt_title
+                                       (EAuthenticationSession *session);
+void           e_authentication_session_set_prompt_title
+                                       (EAuthenticationSession *session,
+                                        const gchar *prompt_title);
+const gchar *  e_authentication_session_get_prompt_message
+                                       (EAuthenticationSession *session);
+gchar *                e_authentication_session_dup_prompt_message
+                                       (EAuthenticationSession *session);
+void           e_authentication_session_set_prompt_message
+                                       (EAuthenticationSession *session,
+                                        const gchar *prompt_message);
+const gchar *  e_authentication_session_get_prompt_description
+                                       (EAuthenticationSession *session);
+gchar *                e_authentication_session_dup_prompt_description
+                                       (EAuthenticationSession *session);
+void           e_authentication_session_set_prompt_description
+                                       (EAuthenticationSession *session,
+                                        const gchar *prompt_description);
+EAuthenticationSessionResult
+               e_authentication_session_execute_sync
+                                       (EAuthenticationSession *session,
+                                        GCancellable *cancellable,
+                                        GError **error);
+void           e_authentication_session_execute
+                                       (EAuthenticationSession *session,
+                                        gint io_priority,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data);
+EAuthenticationSessionResult
+               e_authentication_session_execute_finish
+                                       (EAuthenticationSession *session,
+                                        GAsyncResult *result,
+                                        GError **error);
+gboolean       e_authentication_session_store_password_sync
+                                       (EAuthenticationSession *session,
+                                        const gchar *password,
+                                        gboolean permanently,
+                                        GCancellable *cancellable,
+                                        GError **error);
+void           e_authentication_session_store_password
+                                       (EAuthenticationSession *session,
+                                        const gchar *password,
+                                        gboolean permanently,
+                                        gint io_priority,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data);
+gboolean       e_authentication_session_store_password_finish
+                                       (EAuthenticationSession *session,
+                                        GAsyncResult *result,
+                                        GError **error);
+gboolean       e_authentication_session_lookup_password_sync
+                                       (EAuthenticationSession *session,
+                                        GCancellable *cancellable,
+                                        gchar **password,
+                                        GError **error);
+void           e_authentication_session_lookup_password
+                                       (EAuthenticationSession *session,
+                                        gint io_priority,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data);
+gboolean       e_authentication_session_lookup_password_finish
+                                       (EAuthenticationSession *session,
+                                        GAsyncResult *result,
+                                        gchar **password,
+                                        GError **error);
+gboolean       e_authentication_session_delete_password_sync
+                                       (EAuthenticationSession *session,
+                                        GCancellable *cancellable,
+                                        GError **error);
+void           e_authentication_session_delete_password
+                                       (EAuthenticationSession *session,
+                                        gint io_priority,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data);
+gboolean       e_authentication_session_delete_password_finish
+                                       (EAuthenticationSession *session,
+                                        GAsyncResult *result,
+                                        GError **error);
+
+G_END_DECLS
+
+#endif /* E_AUTHENTICATION_SESSION_H */
+
index 4d06518..f011aab 100644 (file)
 #define E_BACKEND_ENUMS_H
 
 /**
+ * EAuthenticationSessionResult:
+ * @E_AUTHENTICATION_SESSION_ERROR:
+ *   An error occurred while authenticating.
+ * @E_AUTHENTICATION_SESSION_SUCCESS:
+ *   Client reported successful authentication.
+ * @E_AUTHENTICATION_SESSION_DISMISSED:
+ *   User dismissed the authentication prompt.
+ *
+ * Completion codes used by #EAuthenticationSession.
+ *
+ * Since: 3.6
+ **/
+typedef enum {
+       E_AUTHENTICATION_SESSION_ERROR,
+       E_AUTHENTICATION_SESSION_SUCCESS,
+       E_AUTHENTICATION_SESSION_DISMISSED
+} EAuthenticationSessionResult;
+
+/**
  * EDBusServerExitCode:
  * @E_DBUS_SERVER_EXIT_NONE:
  *   The server's run state is unchanged.
@@ -41,5 +60,23 @@ typedef enum {
        E_DBUS_SERVER_EXIT_RELOAD
 } EDBusServerExitCode;
 
-#endif /* E_BACKEND_ENUMS_H */
+/**
+ * ESourcePermissionFlags:
+ * @E_SOURCE_PERMISSION_NONE:
+ *   The data source gets no initial permissions.
+ * @E_SOURCE_PERMISSION_WRITABLE:
+ *   The data source is initially writable.
+ * @E_SOURCE_PERMISSION_REMOVABLE:
+ *   The data source is initially removable.
+ *
+ * Initial permissions for a newly-loaded data source key file.
+ *
+ * Since: 3.6
+ **/
+typedef enum { /*< flags >*/
+       E_SOURCE_PERMISSION_NONE      = 0,
+       E_SOURCE_PERMISSION_WRITABLE  = 1 << 0,
+       E_SOURCE_PERMISSION_REMOVABLE = 1 << 1
+} ESourcePermissionFlags;
 
+#endif /* E_BACKEND_ENUMS_H */
diff --git a/libebackend/e-collection-backend-factory.c b/libebackend/e-collection-backend-factory.c
new file mode 100644 (file)
index 0000000..f7daf77
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * e-collection-backend-factory.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-collection-backend-factory
+ * @include: libebackend/e-collection-backend-factory.h
+ * @short_description: An abstract base class for a data source collection
+ *                     backend factory
+ *
+ * #ECollectionBackendFactory is a type of #EBackendFactory for creating
+ * #ECollectionBackend instances.
+ **/
+
+#include "e-collection-backend-factory.h"
+
+#include <libedataserver/e-source-mail-account.h>
+#include <libedataserver/e-source-mail-identity.h>
+#include <libedataserver/e-source-mail-submission.h>
+#include <libedataserver/e-source-mail-transport.h>
+
+#include <libebackend/e-collection-backend.h>
+#include <libebackend/e-source-registry-server.h>
+
+G_DEFINE_ABSTRACT_TYPE (
+       ECollectionBackendFactory,
+       e_collection_backend_factory,
+       E_TYPE_BACKEND_FACTORY)
+
+static ESourceRegistryServer *
+collection_backend_factory_get_server (EBackendFactory *factory)
+{
+       EExtensible *extensible;
+
+       extensible = e_extension_get_extensible (E_EXTENSION (factory));
+
+       return E_SOURCE_REGISTRY_SERVER (extensible);
+}
+
+static const gchar *
+collection_backend_factory_get_hash_key (EBackendFactory *factory)
+{
+       ECollectionBackendFactoryClass *class;
+
+       class = E_COLLECTION_BACKEND_FACTORY_GET_CLASS (factory);
+       g_return_val_if_fail (class->factory_name != NULL, NULL);
+
+       return class->factory_name;
+}
+
+static EBackend *
+collection_backend_factory_new_backend (EBackendFactory *factory,
+                                        ESource *source)
+{
+       ECollectionBackendFactoryClass *class;
+       ESourceRegistryServer *server;
+
+       class = E_COLLECTION_BACKEND_FACTORY_GET_CLASS (factory);
+       g_return_val_if_fail (g_type_is_a (
+               class->backend_type, E_TYPE_COLLECTION_BACKEND), NULL);
+
+       server = collection_backend_factory_get_server (factory);
+
+       return g_object_new (
+               class->backend_type,
+               "server", server,
+               "source", source, NULL);
+}
+
+static void
+collection_backend_factory_prepare_mail (ECollectionBackendFactory *factory,
+                                         ESource *mail_account_source,
+                                         ESource *mail_identity_source,
+                                         ESource *mail_transport_source)
+{
+       ESource *source;
+       ESourceExtension *extension;
+       const gchar *extension_name;
+
+       /* This does only very basic configuration.
+        * The rest is for subclasses to implement. */
+
+       source = mail_account_source;
+
+       extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
+       extension = e_source_get_extension (source, extension_name);
+
+       e_source_mail_account_set_identity_uid (
+               E_SOURCE_MAIL_ACCOUNT (extension),
+               e_source_get_uid (mail_identity_source));
+
+       source = mail_identity_source;
+
+       /* This just makes sure the extension is present
+        * so the source is recognized as a mail identity. */
+       extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+       e_source_get_extension (source, extension_name);
+
+       extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
+       extension = e_source_get_extension (source, extension_name);
+
+       e_source_mail_submission_set_transport_uid (
+               E_SOURCE_MAIL_SUBMISSION (extension),
+               e_source_get_uid (mail_transport_source));
+
+       source = mail_transport_source;
+
+       /* This just makes sure the extension is present
+        * so the source is recognized as a mail transport. */
+       extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
+       e_source_get_extension (source, extension_name);
+}
+
+static void
+e_collection_backend_factory_class_init (ECollectionBackendFactoryClass *class)
+{
+       EExtensionClass *extension_class;
+       EBackendFactoryClass *factory_class;
+
+       extension_class = E_EXTENSION_CLASS (class);
+       extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
+
+       factory_class = E_BACKEND_FACTORY_CLASS (class);
+       factory_class->get_hash_key = collection_backend_factory_get_hash_key;
+       factory_class->new_backend = collection_backend_factory_new_backend;
+
+       class->prepare_mail = collection_backend_factory_prepare_mail;
+}
+
+static void
+e_collection_backend_factory_init (ECollectionBackendFactory *factory)
+{
+}
+
+/**
+ * e_collection_backend_factory_prepare_mail:
+ * @factory: an #ECollectionBackendFactory
+ * @mail_account_source: an #ESource to hold mail account information
+ * @mail_identity_source: an #ESource to hold mail identity information
+ * @mail_transport_source: an #ESource to hold mail transport information
+ *
+ * Convenience function to populate a set of #ESource instances with mail
+ * account information to be added to an #ECollectionBackend.  This is mainly
+ * used for vendor-specific collection backends like Google or Yahoo! where
+ * the host, port, and security details are known ahead of time and only
+ * user-specific information needs to be filled in.
+ *
+ * Since: 3.6
+ **/
+void
+e_collection_backend_factory_prepare_mail (ECollectionBackendFactory *factory,
+                                           ESource *mail_account_source,
+                                           ESource *mail_identity_source,
+                                           ESource *mail_transport_source)
+{
+       ECollectionBackendFactoryClass *class;
+
+       g_return_if_fail (E_IS_COLLECTION_BACKEND_FACTORY (factory));
+       g_return_if_fail (E_IS_SOURCE (mail_account_source));
+       g_return_if_fail (E_IS_SOURCE (mail_identity_source));
+       g_return_if_fail (E_IS_SOURCE (mail_transport_source));
+
+       class = E_COLLECTION_BACKEND_FACTORY_GET_CLASS (factory);
+       g_return_if_fail (class->prepare_mail != NULL);
+
+       class->prepare_mail (
+               factory,
+               mail_account_source,
+               mail_identity_source,
+               mail_transport_source);
+}
+
diff --git a/libebackend/e-collection-backend-factory.h b/libebackend/e-collection-backend-factory.h
new file mode 100644 (file)
index 0000000..c2a436f
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * e-collection-backend-factory.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_COLLECTION_BACKEND_FACTORY_H
+#define E_COLLECTION_BACKEND_FACTORY_H
+
+#include <libebackend/e-backend-factory.h>
+
+/* Standard GObject macros */
+#define E_TYPE_COLLECTION_BACKEND_FACTORY \
+       (e_collection_backend_factory_get_type ())
+#define E_COLLECTION_BACKEND_FACTORY(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_COLLECTION_BACKEND_FACTORY, ECollectionBackendFactory))
+#define E_COLLECTION_BACKEND_FACTORY_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_COLLECTION_BACKEND_FACTORY, ECollectionBackendFactoryClass))
+#define E_IS_COLLECTION_BACKEND_FACTORY(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_COLLECTION_BACKEND_FACTORY))
+#define E_IS_COLLECTION_BACKEND_FACTORY_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_COLLECTION_BACKEND_FACTORY))
+#define E_COLLECTION_BACKEND_FACTORY_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_COLLECTION_BACKEND_FACTORY, ECollectionBackendFactoryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECollectionBackendFactory ECollectionBackendFactory;
+typedef struct _ECollectionBackendFactoryClass ECollectionBackendFactoryClass;
+typedef struct _ECollectionBackendFactoryPrivate ECollectionBackendFactoryPrivate;
+
+/**
+ * ECollectionBackendFactory:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.6
+ **/
+struct _ECollectionBackendFactory {
+       EBackendFactory parent;
+       ECollectionBackendFactoryPrivate *priv;
+};
+
+struct _ECollectionBackendFactoryClass {
+       EBackendFactoryClass parent_class;
+
+       const gchar *factory_name;
+       GType backend_type;
+
+       /* Methods */
+       void            (*prepare_mail) (ECollectionBackendFactory *factory,
+                                        ESource *mail_account_source,
+                                        ESource *mail_identity_source,
+                                        ESource *mail_transport_source);
+
+       gpointer reserved[16];
+};
+
+GType          e_collection_backend_factory_get_type
+                                       (void) G_GNUC_CONST;
+void           e_collection_backend_factory_prepare_mail
+                                       (ECollectionBackendFactory *factory,
+                                        ESource *mail_account_source,
+                                        ESource *mail_identity_source,
+                                        ESource *mail_transport_source);
+
+G_END_DECLS
+
+#endif /* E_COLLECTION_BACKEND_FACTORY_H */
+
diff --git a/libebackend/e-collection-backend.c b/libebackend/e-collection-backend.c
new file mode 100644 (file)
index 0000000..7af1192
--- /dev/null
@@ -0,0 +1,835 @@
+/*
+ * e-collection-backend.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-collection-backend
+ * @include: libebackend/e-collection-backend.h
+ * @short_description: An abstract base class for a data source
+ *                     collection backend
+ *
+ * #ECollectionBackend is an abstract base class for backends which
+ * manage a collection of data sources which collectively represent the
+ * resources on a remote server.  The resources can include any number
+ * of private and shared email stores, calendars and address books.
+ *
+ * The backend's job is to synchronize local representations of remote
+ * resources by adding and removing #EServerSideSource instances in an
+ * #ESourceRegistryServer.  If possible the backend should also listen
+ * for notifications of newly-added or deleted resources on the remote
+ * server or else poll the remote server at regular intervals and then
+ * update the data source collection accordingly.
+ *
+ * As most remote servers require authentication, the backend may also
+ * wish to implement the #ESourceAuthenticator interface so it can submit
+ * its own #EAuthenticationSession instances to the #ESourceRegistryServer.
+ **/
+
+#include "e-collection-backend.h"
+
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-source-address-book.h>
+#include <libedataserver/e-source-calendar.h>
+#include <libedataserver/e-source-collection.h>
+#include <libedataserver/e-source-mail-account.h>
+#include <libedataserver/e-source-mail-identity.h>
+#include <libedataserver/e-source-mail-transport.h>
+#include <libedataserver/e-uid.h>
+
+#include <libebackend/e-server-side-source.h>
+#include <libebackend/e-source-registry-server.h>
+
+#define E_COLLECTION_BACKEND_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), E_TYPE_COLLECTION_BACKEND, ECollectionBackendPrivate))
+
+struct _ECollectionBackendPrivate {
+       GWeakRef server;
+       GQueue children;
+
+       /* Remembers memory-only data source UIDs
+        * based on a server-assigned resource ID. */
+       gchar *collection_filename;
+       GKeyFile *collection_key_file;
+       guint save_collection_idle_id;
+
+       gulong source_added_handler_id;
+       gulong source_removed_handler_id;
+};
+
+enum {
+       PROP_0,
+       PROP_SERVER
+};
+
+enum {
+       CHILD_ADDED,
+       CHILD_REMOVED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_ABSTRACT_TYPE (
+       ECollectionBackend,
+       e_collection_backend,
+       E_TYPE_BACKEND)
+
+static void
+collection_backend_load_collection_file (ECollectionBackend *backend)
+{
+       ESource *source;
+       const gchar *uid;
+       const gchar *cache_dir;
+       gchar *dirname;
+       gchar *basename;
+       gchar *filename;
+       GError *error = NULL;
+
+       cache_dir = e_get_user_cache_dir ();
+       source = e_backend_get_source (E_BACKEND (backend));
+
+       uid = e_source_get_uid (source);
+       dirname = g_build_filename (cache_dir, "sources", NULL);
+       basename = g_strconcat (uid, ".collection", NULL);
+       filename = g_build_filename (dirname, basename, NULL);
+       backend->priv->collection_filename = filename;  /* takes ownership */
+       g_mkdir_with_parents (dirname, 0700);
+       g_free (basename);
+       g_free (dirname);
+
+       g_key_file_load_from_file (
+               backend->priv->collection_key_file,
+               backend->priv->collection_filename,
+               G_KEY_FILE_KEEP_COMMENTS, &error);
+
+       /* Disregard "file not found" errors. */
+       if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
+               g_error_free (error);
+       } else if (error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               g_error_free (error);
+       }
+}
+
+static void
+collection_backend_save_collection_file (ECollectionBackend *backend)
+{
+       gchar *contents;
+       gsize length;
+       GError *error = NULL;
+
+       contents = g_key_file_to_data (
+               backend->priv->collection_key_file, &length, NULL);
+
+       g_file_set_contents (
+               backend->priv->collection_filename,
+               contents, (gssize) length, &error);
+
+       if (error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               g_error_free (error);
+       }
+
+       g_free (contents);
+}
+
+static gboolean
+collection_backend_save_idle_cb (gpointer user_data)
+{
+       ECollectionBackend *backend;
+
+       backend = E_COLLECTION_BACKEND (user_data);
+       collection_backend_save_collection_file (backend);
+       backend->priv->save_collection_idle_id = 0;
+
+       return FALSE;
+}
+
+static ESource *
+collection_backend_register_resource (ECollectionBackend *backend,
+                                      const gchar *resource_id,
+                                      GError **error)
+{
+       ESourceRegistryServer *server;
+       ESource *source;
+       gchar *group_name;
+       gchar *uid;
+
+       server = e_collection_backend_ref_server (backend);
+       group_name = g_strdup_printf ("Resource %s", resource_id);
+
+       uid = g_key_file_get_string (
+               backend->priv->collection_key_file,
+               group_name, "SourceUid", NULL);
+
+       /* Verify the UID is not already in use.
+        * If it is, we'll have to pick a new one. */
+       if (uid != NULL) {
+               source = e_source_registry_server_ref_source (server, uid);
+               if (source != NULL) {
+                       g_object_unref (source);
+                       g_free (uid);
+                       uid = NULL;
+               }
+       }
+
+       if (uid == NULL)
+               uid = e_uid_new ();
+
+       g_key_file_set_string (
+               backend->priv->collection_key_file,
+               group_name, "SourceUid", uid);
+
+       if (backend->priv->save_collection_idle_id == 0) {
+               guint idle_id;
+
+               idle_id = g_idle_add_full (
+                       G_PRIORITY_DEFAULT_IDLE,
+                       collection_backend_save_idle_cb,
+                       g_object_ref (backend),
+                       (GDestroyNotify) g_object_unref);
+               backend->priv->save_collection_idle_id = idle_id;
+       }
+
+       source = e_server_side_source_new_memory_only (server, uid, error);
+
+       g_free (uid);
+       g_free (group_name);
+       g_object_unref (server);
+
+       return source;
+}
+
+static gboolean
+collection_backend_child_is_calendar (ESource *child_source)
+{
+       const gchar *extension_name;
+
+       extension_name = E_SOURCE_EXTENSION_CALENDAR;
+       if (e_source_has_extension (child_source, extension_name))
+               return TRUE;
+
+       extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
+       if (e_source_has_extension (child_source, extension_name))
+               return TRUE;
+
+       extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+       if (e_source_has_extension (child_source, extension_name))
+               return TRUE;
+
+       return FALSE;
+}
+
+static gboolean
+collection_backend_child_is_contacts (ESource *child_source)
+{
+       const gchar *extension_name;
+
+       extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+       if (e_source_has_extension (child_source, extension_name))
+               return TRUE;
+
+       return FALSE;
+}
+
+static gboolean
+collection_backend_child_is_mail (ESource *child_source)
+{
+       const gchar *extension_name;
+
+       extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
+       if (e_source_has_extension (child_source, extension_name))
+               return TRUE;
+
+       extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+       if (e_source_has_extension (child_source, extension_name))
+               return TRUE;
+
+       extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
+       if (e_source_has_extension (child_source, extension_name))
+               return TRUE;
+
+       return FALSE;
+}
+
+static void
+collection_backend_bind_child_enabled (ECollectionBackend *backend,
+                                       ESource *child_source)
+{
+       ESource *collection_source;
+       ESourceCollection *extension;
+       const gchar *extension_name;
+
+       /* See if the child source's "enabled" property can be
+        * bound to any ESourceCollection "enabled" properties. */
+
+       extension_name = E_SOURCE_EXTENSION_COLLECTION;
+       collection_source = e_backend_get_source (E_BACKEND (backend));
+       extension = e_source_get_extension (collection_source, extension_name);
+
+       if (collection_backend_child_is_calendar (child_source)) {
+               g_object_bind_property (
+                       extension, "calendar-enabled",
+                       child_source, "enabled",
+                       G_BINDING_SYNC_CREATE);
+               return;
+       }
+
+       if (collection_backend_child_is_contacts (child_source)) {
+               g_object_bind_property (
+                       extension, "contacts-enabled",
+                       child_source, "enabled",
+                       G_BINDING_SYNC_CREATE);
+               return;
+       }
+
+       if (collection_backend_child_is_mail (child_source)) {
+               g_object_bind_property (
+                       extension, "mail-enabled",
+                       child_source, "enabled",
+                       G_BINDING_SYNC_CREATE);
+               return;
+       }
+}
+
+static void
+collection_backend_source_added_cb (ESourceRegistryServer *server,
+                                    ESource *source,
+                                    ECollectionBackend *backend)
+{
+       ESource *collection_source;
+       ESource *parent_source;
+       const gchar *uid;
+
+       /* If the newly-added source is our own child, emit "child-added". */
+
+       collection_source = e_backend_get_source (E_BACKEND (backend));
+
+       uid = e_source_get_parent (source);
+       if (uid == NULL)
+               return;
+
+       parent_source = e_source_registry_server_ref_source (server, uid);
+       g_return_if_fail (parent_source != NULL);
+
+       if (e_source_equal (collection_source, parent_source))
+               g_signal_emit (backend, signals[CHILD_ADDED], 0, source);
+
+       g_object_unref (parent_source);
+}
+
+static void
+collection_backend_source_removed_cb (ESourceRegistryServer *server,
+                                      ESource *source,
+                                      ECollectionBackend *backend)
+{
+       ESource *collection_source;
+       ESource *parent_source;
+       const gchar *uid;
+
+       /* If the removed source was our own child, emit "child-removed".
+        * Note that the source is already unlinked from the GNode tree. */
+
+       collection_source = e_backend_get_source (E_BACKEND (backend));
+
+       uid = e_source_get_parent (source);
+       if (uid == NULL)
+               return;
+
+       parent_source = e_source_registry_server_ref_source (server, uid);
+       g_return_if_fail (parent_source != NULL);
+
+       if (e_source_equal (collection_source, parent_source))
+               g_signal_emit (backend, signals[CHILD_REMOVED], 0, source);
+
+       g_object_unref (parent_source);
+}
+
+static gboolean
+collection_backend_populate_idle_cb (gpointer user_data)
+{
+       ECollectionBackend *backend;
+       ECollectionBackendClass *class;
+
+       backend = E_COLLECTION_BACKEND (user_data);
+
+       class = E_COLLECTION_BACKEND_GET_CLASS (backend);
+       g_return_val_if_fail (class->populate != NULL, FALSE);
+
+       class->populate (backend);
+
+       return FALSE;
+}
+
+static void
+collection_backend_set_server (ECollectionBackend *backend,
+                               ESourceRegistryServer *server)
+{
+       g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+
+       g_weak_ref_set (&backend->priv->server, server);
+}
+
+static void
+collection_backend_set_property (GObject *object,
+                                 guint property_id,
+                                 const GValue *value,
+                                 GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_SERVER:
+                       collection_backend_set_server (
+                               E_COLLECTION_BACKEND (object),
+                               g_value_get_object (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+collection_backend_get_property (GObject *object,
+                                 guint property_id,
+                                 GValue *value,
+                                 GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_SERVER:
+                       g_value_take_object (
+                               value,
+                               e_collection_backend_ref_server (
+                               E_COLLECTION_BACKEND (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+collection_backend_dispose (GObject *object)
+{
+       ECollectionBackendPrivate *priv;
+       ESourceRegistryServer *server;
+
+       priv = E_COLLECTION_BACKEND_GET_PRIVATE (object);
+
+       server = g_weak_ref_get (&priv->server);
+       if (server != NULL) {
+               g_signal_handler_disconnect (
+                       server, priv->source_added_handler_id);
+               g_signal_handler_disconnect (
+                       server, priv->source_removed_handler_id);
+               g_weak_ref_set (&priv->server, NULL);
+               g_object_unref (server);
+       }
+
+       while (!g_queue_is_empty (&priv->children))
+               g_object_unref (g_queue_pop_head (&priv->children));
+
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_collection_backend_parent_class)->dispose (object);
+}
+
+static void
+collection_backend_finalize (GObject *object)
+{
+       ECollectionBackendPrivate *priv;
+
+       priv = E_COLLECTION_BACKEND_GET_PRIVATE (object);
+
+       g_free (priv->collection_filename);
+       g_key_file_free (priv->collection_key_file);
+
+       /* The idle source ID should be zero since the idle
+        * source itself holds a reference on the backend. */
+       g_warn_if_fail (priv->save_collection_idle_id == 0);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_collection_backend_parent_class)->finalize (object);
+}
+
+static void
+collection_backend_constructed (GObject *object)
+{
+       ECollectionBackend *backend;
+       ESourceRegistryServer *server;
+       ESource *source;
+       GNode *node;
+       gulong handler_id;
+
+       backend = E_COLLECTION_BACKEND (object);
+
+       /* Chain up to parent's constructed() method. */
+       G_OBJECT_CLASS (e_collection_backend_parent_class)->
+               constructed (object);
+
+       collection_backend_load_collection_file (backend);
+
+       /* Emit "child-added" signals for the children we already have. */
+
+       source = e_backend_get_source (E_BACKEND (backend));
+       node = e_server_side_source_get_node (E_SERVER_SIDE_SOURCE (source));
+       node = g_node_first_child (node);
+
+       while (node != NULL) {
+               ESource *child = E_SOURCE (node->data);
+               g_signal_emit (backend, signals[CHILD_ADDED], 0, child);
+               node = g_node_next_sibling (node);
+       }
+
+       /* Listen for "source-added" and "source-removed" signals
+        * from the server, which may trigger our own "child-added"
+        * and "child-removed" signals. */
+
+       server = e_collection_backend_ref_server (backend);
+
+       handler_id = g_signal_connect (
+               server, "source-added",
+               G_CALLBACK (collection_backend_source_added_cb), backend);
+
+       backend->priv->source_added_handler_id = handler_id;
+
+       handler_id = g_signal_connect (
+               server, "source-removed",
+               G_CALLBACK (collection_backend_source_removed_cb), backend);
+
+       backend->priv->source_removed_handler_id = handler_id;
+
+       g_object_unref (server);
+
+       /* Populate the newly-added collection from an idle callback
+        * so persistent child sources have a chance to be added first. */
+
+       g_idle_add_full (
+               G_PRIORITY_LOW,
+               collection_backend_populate_idle_cb,
+               g_object_ref (backend),
+               (GDestroyNotify) g_object_unref);
+}
+
+static void
+collection_backend_populate (ECollectionBackend *backend)
+{
+       /* Placeholder so subclasses can safely chain up. */
+}
+
+static void
+collection_backend_child_added (ECollectionBackend *backend,
+                                ESource *child_source)
+{
+       collection_backend_bind_child_enabled (backend, child_source);
+
+       g_queue_push_tail (
+               &backend->priv->children,
+               g_object_ref (child_source));
+
+       /* Collection children are not removable. */
+       e_server_side_source_set_removable (
+               E_SERVER_SIDE_SOURCE (child_source), FALSE);
+}
+
+static void
+collection_backend_child_removed (ECollectionBackend *backend,
+                                  ESource *child_source)
+{
+       if (g_queue_remove (&backend->priv->children, child_source))
+               g_object_unref (child_source);
+}
+
+static void
+e_collection_backend_class_init (ECollectionBackendClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (class, sizeof (ECollectionBackendPrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = collection_backend_set_property;
+       object_class->get_property = collection_backend_get_property;
+       object_class->dispose = collection_backend_dispose;
+       object_class->finalize = collection_backend_finalize;
+       object_class->constructed = collection_backend_constructed;
+
+       class->populate = collection_backend_populate;
+       class->child_added = collection_backend_child_added;
+       class->child_removed = collection_backend_child_removed;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_SERVER,
+               g_param_spec_object (
+                       "server",
+                       "Server",
+                       "The server to which the backend belongs",
+                       E_TYPE_SOURCE_REGISTRY_SERVER,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       /**
+        * ECollectionBackend::child-added:
+        * @backend: the #ECollectionBackend which emitted the signal
+        * @child_source: the newly-added child #EServerSideSource
+        *
+        * Emitted when an #EServerSideSource is added to @backend's
+        * #ECollectionBackend:server as a child of @backend's collection
+        * #EBackend:source.
+        *
+        * You can think of this as a filtered version of
+        * #ESourceRegistryServer's #ESourceRegistryServer::source-added
+        * signal which only lets through sources relevant to @backend.
+        **/
+       signals[CHILD_ADDED] = g_signal_new (
+               "child-added",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (ECollectionBackendClass, child_added),
+               NULL, NULL,
+               g_cclosure_marshal_VOID__OBJECT,
+               G_TYPE_NONE, 1,
+               E_TYPE_SERVER_SIDE_SOURCE);
+
+       /**
+        * ECollectionBackend::child-removed:
+        * @backend: the #ECollectionBackend which emitted the signal
+        * @child_source: the child #EServerSideSource that got removed
+        *
+        * Emitted when an #EServerSideSource that is a child of
+        * @backend's collection #EBackend:source is removed from
+        * @backend's #ECollectionBackend:server.
+        *
+        * You can think of this as a filtered version of
+        * #ESourceRegistryServer's #ESourceRegistryServer::source-removed
+        * signal which only lets through sources relevant to @backend.
+        **/
+       signals[CHILD_REMOVED] = g_signal_new (
+               "child-removed",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (ECollectionBackendClass, child_removed),
+               NULL, NULL,
+               g_cclosure_marshal_VOID__OBJECT,
+               G_TYPE_NONE, 1,
+               E_TYPE_SERVER_SIDE_SOURCE);
+}
+
+static void
+e_collection_backend_init (ECollectionBackend *backend)
+{
+       backend->priv = E_COLLECTION_BACKEND_GET_PRIVATE (backend);
+       backend->priv->collection_key_file = g_key_file_new ();
+}
+
+/**
+ * e_collection_backend_new_child:
+ * @backend: an #ECollectionBackend
+ * @resource_id: a stable and unique resource ID
+ *
+ * Creates a new memory-only #EServerSideSource as a child of the collection
+ * #EBackend:source owned by @backend.  If possible, the #EServerSideSource
+ * is assigned a previously used #ESource:uid based on @resource_id so that
+ * locally cached data can be reused.
+ *
+ * The returned data source should be passed to
+ * e_source_registry_server_add_source() to export it over D-Bus.
+ *
+ * Return: a newly-created data source
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_collection_backend_new_child (ECollectionBackend *backend,
+                                const gchar *resource_id)
+{
+       ESource *collection_source;
+       ESource *child_source;
+       const gchar *collection_uid;
+       GError *error = NULL;
+
+       g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
+       g_return_val_if_fail (resource_id != NULL, NULL);
+
+       /* This being a memory-only data source, creating the instance
+        * should never fail but we'll check for errors just the same.
+        * It's unlikely enough that we don't need a GError parameter. */
+       child_source = collection_backend_register_resource (
+               backend, resource_id, &error);
+
+       if (error != NULL) {
+               g_warn_if_fail (child_source == NULL);
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               g_error_free (error);
+               return NULL;
+       }
+
+       collection_source = e_backend_get_source (E_BACKEND (backend));
+       collection_uid = e_source_get_uid (collection_source);
+       e_source_set_parent (child_source, collection_uid);
+
+       g_print ("%s: Pairing %s with resource %s\n",
+               e_source_get_display_name (collection_source),
+               e_source_get_uid (child_source), resource_id);
+
+       return child_source;
+}
+
+/**
+ * e_collection_backend_ref_server:
+ * @backend: an #ECollectionBackend
+ *
+ * Returns the #ESourceRegistryServer to which @backend belongs.
+ *
+ * The returned #ESourceRegistryServer is referenced for thread-safety.
+ * Unreference the #ESourceRegistryServer with g_object_unref() when
+ * finished with it.
+ *
+ * Returns: the #ESourceRegisterServer for @backend
+ *
+ * Since: 3.6
+ **/
+ESourceRegistryServer *
+e_collection_backend_ref_server (ECollectionBackend *backend)
+{
+       g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
+
+       return g_weak_ref_get (&backend->priv->server);
+}
+
+/**
+ * e_collection_backend_list_calendar_sources:
+ * @backend: an #ECollectionBackend
+ *
+ * Returns a list of calendar sources belonging to the data source
+ * collection managed by @backend.
+ *
+ * The sources returned in the list are referenced for thread-safety.
+ * They must each be unreferenced with g_object_unref() when finished
+ * with them.  Free the returned #GList itself with g_list_free().
+ *
+ * An easy way to free the list properly in one step is as follows:
+ *
+ * |[
+ *   g_list_free_full (list, g_object_unref);
+ * ]|
+ *
+ * Returns: a list of calendar sources
+ *
+ * Since: 3.6
+ **/
+GList *
+e_collection_backend_list_calendar_sources (ECollectionBackend *backend)
+{
+       GList *result_list = NULL;
+       GList *list, *link;
+
+       g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
+
+       list = g_queue_peek_head_link (&backend->priv->children);
+
+       for (link = list; link != NULL; link = g_list_next (link)) {
+               ESource *child_source = E_SOURCE (link->data);
+               if (collection_backend_child_is_calendar (child_source))
+                       result_list = g_list_prepend (
+                               result_list, g_object_ref (child_source));
+       }
+
+       return g_list_reverse (result_list);
+}
+
+/**
+ * e_collection_backend_list_contacts_sources:
+ * @backend: an #ECollectionBackend
+ *
+ * Returns a list of address book sources belonging to the data source
+ * collection managed by @backend.
+ *
+ * The sources returned in the list are referenced for thread-safety.
+ * They must each be unreferenced with g_object_unref() when finished
+ * with them.  Free the returned #GList itself with g_list_free().
+ *
+ * An easy way to free the list properly in one step is as follows:
+ *
+ * |[
+ *   g_list_free_full (list, g_object_unref);
+ * ]|
+ *
+ * Returns: a list of address book sources
+ *
+ * Since: 3.6
+ **/
+GList *
+e_collection_backend_list_contacts_sources (ECollectionBackend *backend)
+{
+       GList *result_list = NULL;
+       GList *list, *link;
+
+       g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
+
+       list = g_queue_peek_head_link (&backend->priv->children);
+
+       for (link = list; link != NULL; link = g_list_next (link)) {
+               ESource *child_source = E_SOURCE (link->data);
+               if (collection_backend_child_is_contacts (child_source))
+                       result_list = g_list_prepend (
+                               result_list, g_object_ref (child_source));
+       }
+
+       return g_list_reverse (result_list);
+}
+
+/**
+ * e_collection_backend_list_mail_sources:
+ * @backend: an #ECollectionBackend
+ *
+ * Returns a list of mail sources belonging to the data source collection
+ * managed by @backend.
+ *
+ * The sources returned in the list are referenced for thread-safety.
+ * They must each be unreferenced with g_object_unref() when finished
+ * with them.  Free the returned #GList itself with g_list_free().
+ *
+ * An easy way to free the list properly in one step is as follows:
+ *
+ * |[
+ *   g_list_free_full (list, g_object_unref);
+ * ]|
+ *
+ * Returns: a list of mail sources
+ *
+ * Since: 3.6
+ **/
+GList *
+e_collection_backend_list_mail_sources (ECollectionBackend *backend)
+{
+       GList *result_list = NULL;
+       GList *list, *link;
+
+       g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
+
+       list = g_queue_peek_head_link (&backend->priv->children);
+
+       for (link = list; link != NULL; link = g_list_next (link)) {
+               ESource *child_source = E_SOURCE (link->data);
+               if (collection_backend_child_is_mail (child_source))
+                       result_list = g_list_prepend (
+                               result_list, g_object_ref (child_source));
+       }
+
+       return g_list_reverse (result_list);
+}
+
diff --git a/libebackend/e-collection-backend.h b/libebackend/e-collection-backend.h
new file mode 100644 (file)
index 0000000..8f2b4b7
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * e-collection-backend.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_COLLECTION_BACKEND_H
+#define E_COLLECTION_BACKEND_H
+
+#include <libebackend/e-backend.h>
+
+/* Standard GObject macros */
+#define E_TYPE_COLLECTION_BACKEND \
+       (e_collection_backend_get_type ())
+#define E_COLLECTION_BACKEND(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_COLLECTION_BACKEND, ECollectionBackend))
+#define E_COLLECTION_BACKEND_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_COLLECTION_BACKEND, ECollectionBackendClass))
+#define E_IS_COLLECTION_BACKEND(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_COLLECTION_BACKEND))
+#define E_IS_COLLECTION_BACKEND_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_COLLECTION_BACKEND))
+#define E_COLLECTION_BACKEND_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_COLLECTION_BACKEND, ECollectionBackendClass))
+
+G_BEGIN_DECLS
+
+struct _ESourceRegistryServer;
+
+typedef struct _ECollectionBackend ECollectionBackend;
+typedef struct _ECollectionBackendClass ECollectionBackendClass;
+typedef struct _ECollectionBackendPrivate ECollectionBackendPrivate;
+
+/**
+ * ECollectionBackend:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.6
+ **/
+struct _ECollectionBackend {
+       EBackend parent;
+       ECollectionBackendPrivate *priv;
+};
+
+struct _ECollectionBackendClass {
+       EBackendClass parent_class;
+
+       /* Methods */
+       void            (*populate)             (ECollectionBackend *backend);
+
+       /* Signals */
+       void            (*child_added)          (ECollectionBackend *backend,
+                                                ESource *child_source);
+       void            (*child_removed)        (ECollectionBackend *backend,
+                                                ESource *child_source);
+
+       gpointer reserved[16];
+};
+
+GType          e_collection_backend_get_type   (void) G_GNUC_CONST;
+ESource *      e_collection_backend_new_child  (ECollectionBackend *backend,
+                                                const gchar *resource_id);
+struct _ESourceRegistryServer *
+               e_collection_backend_ref_server (ECollectionBackend *backend);
+GList *                e_collection_backend_list_calendar_sources
+                                               (ECollectionBackend *backend);
+GList *                e_collection_backend_list_contacts_sources
+                                               (ECollectionBackend *backend);
+GList *                e_collection_backend_list_mail_sources
+                                               (ECollectionBackend *backend);
+
+G_END_DECLS
+
+#endif /* E_COLLECTION_BACKEND_H */
+
index 0d72a36..056d975 100644 (file)
@@ -31,7 +31,6 @@
 #endif
 
 #include <libebackend/e-module.h>
-#include <libebackend/e-extensible.h>
 #include <libebackend/e-backend-enumtypes.h>
 
 #define E_DBUS_SERVER_GET_PRIVATE(obj) \
@@ -66,9 +65,8 @@ static guint signals[LAST_SIGNAL];
 static GHashTable *directories_loaded;
 G_LOCK_DEFINE_STATIC (directories_loaded);
 
-G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
-       EDBusServer, e_dbus_server, G_TYPE_OBJECT,
-       G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
+G_DEFINE_ABSTRACT_TYPE (
+       EDBusServer, e_dbus_server, G_TYPE_OBJECT)
 
 static void
 dbus_server_bus_acquired_cb (GDBusConnection *connection,
diff --git a/libebackend/e-server-side-source.c b/libebackend/e-server-side-source.c
new file mode 100644 (file)
index 0000000..ec55cdd
--- /dev/null
@@ -0,0 +1,1404 @@
+/*
+ * e-server-side-source.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-server-side-source
+ * @short_description: A server-side data source
+ * @include: libebackend/e-server-side-source.h
+ *
+ * An #EServerSideSource is an #ESource with some additional capabilities
+ * exclusive to the registry D-Bus service.
+ **/
+
+#include "e-server-side-source.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+/* Private D-Bus classes. */
+#include <e-dbus-source.h>
+
+#include <libedataserver/e-uid.h>
+#include <libedataserver/e-data-server-util.h>
+
+#define E_SERVER_SIDE_SOURCE_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSourcePrivate))
+
+#define DBUS_OBJECT_PATH       E_SOURCE_REGISTRY_SERVER_OBJECT_PATH "/Source"
+
+#define PRIMARY_GROUP_NAME     "Data Source"
+
+typedef struct _AsyncClosure AsyncClosure;
+
+struct _EServerSideSourcePrivate {
+       gpointer server;  /* weak pointer */
+
+       GNode node;
+       GFile *file;
+       gchar *uid;
+
+       /* For comparison. */
+       gchar *file_contents;
+
+       gboolean allow_auth_prompt;
+};
+
+struct _AsyncClosure {
+       GMainLoop *loop;
+       GMainContext *context;
+       GAsyncResult *result;
+};
+
+enum {
+       PROP_0,
+       PROP_ALLOW_AUTH_PROMPT,
+       PROP_FILE,
+       PROP_REMOVABLE,
+       PROP_SERVER,
+       PROP_UID,
+       PROP_WRITABLE
+};
+
+static GInitableIface *initable_parent_interface;
+
+/* Forward Declarations */
+static void    e_server_side_source_initable_init
+                                               (GInitableIface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+       EServerSideSource,
+       e_server_side_source,
+       E_TYPE_SOURCE,
+       G_IMPLEMENT_INTERFACE (
+               G_TYPE_INITABLE,
+               e_server_side_source_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_main_loop_run (closure->loop);
+
+       return closure->result;
+}
+
+static void
+async_closure_free (AsyncClosure *closure)
+{
+       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 user_data)
+{
+       AsyncClosure *closure = user_data;
+
+       /* Replace any previous result. */
+       if (closure->result != NULL)
+               g_object_unref (closure->result);
+       closure->result = g_object_ref (result);
+
+       g_main_loop_quit (closure->loop);
+}
+
+static gboolean
+server_side_source_parse_data (GKeyFile *key_file,
+                               const gchar *data,
+                               gsize length,
+                               GError **error)
+{
+       gboolean success;
+
+       success = g_key_file_load_from_data (
+               key_file, data, length, G_KEY_FILE_NONE, error);
+
+       if (!success)
+               return FALSE;
+
+       /* Make sure the key file has a [Data Source] group. */
+       if (!g_key_file_has_group (key_file, PRIMARY_GROUP_NAME)) {
+               g_set_error (
+                       error, G_KEY_FILE_ERROR,
+                       G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
+                       _("Data source is missing a [%s] group"),
+                       PRIMARY_GROUP_NAME);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+server_side_source_print_diff (ESource *source,
+                               const gchar *old_data,
+                               const gchar *new_data)
+{
+       gchar **old_strv = NULL;
+       gchar **new_strv = NULL;
+       guint old_length = 0;
+       guint new_length = 0;
+       guint ii;
+
+       g_print ("Saving %s\n", e_source_get_uid (source));
+
+       if (old_data != NULL) {
+               old_strv = g_strsplit (old_data, "\n", 0);
+               old_length = g_strv_length (old_strv);
+       }
+
+       if (new_data != NULL) {
+               new_strv = g_strsplit (new_data, "\n", 0);
+               new_length = g_strv_length (new_strv);
+       }
+
+       for (ii = 0; ii < MIN (old_length, new_length); ii++) {
+               if (g_strcmp0 (old_strv[ii], new_strv[ii]) != 0) {
+                       g_print (" - : %s\n", old_strv[ii]);
+                       g_print (" + : %s\n", new_strv[ii]);
+               } else {
+                       g_print ("   : %s\n", old_strv[ii]);
+               }
+       }
+
+       for (; ii < old_length; ii++)
+               g_print (" - : %s\n", old_strv[ii]);
+
+       for (; ii < new_length; ii++)
+               g_print (" + : %s\n", new_strv[ii]);
+
+       g_strfreev (old_strv);
+       g_strfreev (new_strv);
+}
+
+static gboolean
+server_side_source_traverse_cb (GNode *node,
+                                GQueue *queue)
+{
+       g_queue_push_tail (queue, g_object_ref (node->data));
+
+       return FALSE;
+}
+
+static gboolean
+server_side_source_allow_auth_prompt_cb (EDBusSource *interface,
+                                         GDBusMethodInvocation *invocation,
+                                         EServerSideSource *source)
+{
+       e_server_side_source_set_allow_auth_prompt (source, TRUE);
+
+       e_dbus_source_complete_allow_auth_prompt (interface, invocation);
+
+       return TRUE;
+}
+
+static gboolean
+server_side_source_remove_cb (EDBusSourceRemovable *interface,
+                              GDBusMethodInvocation *invocation,
+                              EServerSideSource *source)
+{
+       GError *error = NULL;
+
+       /* Note we don't need to verify the source is removable here
+        * since if it isn't, the remove() method won't be available.
+        * Descendants of the source are removed whether they export
+        * a remove() method or not. */
+
+       e_source_remove_sync (E_SOURCE (source), NULL, &error);
+
+       if (error != NULL)
+               g_dbus_method_invocation_take_error (invocation, error);
+       else
+               e_dbus_source_removable_complete_remove (
+                       interface, invocation);
+
+       return TRUE;
+}
+
+static gboolean
+server_side_source_write_cb (EDBusSourceWritable *interface,
+                             GDBusMethodInvocation *invocation,
+                             const gchar *data,
+                             ESource *source)
+{
+       GKeyFile *key_file;
+       GDBusObject *dbus_object;
+       EDBusSource *dbus_source;
+       GError *error = NULL;
+
+       /* Note we don't need to verify the source is writable here
+        * since if it isn't, the write() method won't be available. */
+
+       dbus_object = e_source_ref_dbus_object (source);
+       dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
+
+       /* Validate the raw data before making the changes live. */
+       key_file = g_key_file_new ();
+       server_side_source_parse_data (key_file, data, strlen (data), &error);
+       g_key_file_free (key_file);
+
+       /* Q: How does this trigger data being written to disk?
+        *
+        * A: Here's the sequence of events:
+        *
+        *    1) We set the EDBusSource:data property.
+        *    2) ESource picks up the "notify::data" signal and parses
+        *       the raw data, which triggers an ESource:changed signal.
+        *    3) Our changed() method schedules an idle callback.
+        *    4) The idle callback calls e_source_write_sync().
+        *    5) e_source_write_sync() calls e_dbus_source_dup_data()
+        *       and synchronously writes the resulting string to disk.
+        */
+
+       if (error == NULL)
+               e_dbus_source_set_data (dbus_source, data);
+
+       if (error != NULL)
+               g_dbus_method_invocation_take_error (invocation, error);
+       else
+               e_dbus_source_writable_complete_write (
+                       interface, invocation);
+
+       g_object_unref (dbus_source);
+       g_object_unref (dbus_object);
+
+       return TRUE;
+}
+
+static void
+server_side_source_set_file (EServerSideSource *source,
+                             GFile *file)
+{
+       g_return_if_fail (file == NULL || G_IS_FILE (file));
+       g_return_if_fail (source->priv->file == NULL);
+
+       if (file != NULL)
+               source->priv->file = g_object_ref (file);
+}
+
+static void
+server_side_source_set_server (EServerSideSource *source,
+                               ESourceRegistryServer *server)
+{
+       g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+       g_return_if_fail (source->priv->server == NULL);
+
+       source->priv->server = server;
+
+       g_object_add_weak_pointer (
+               G_OBJECT (server), &source->priv->server);
+}
+
+static void
+server_side_source_set_uid (EServerSideSource *source,
+                            const gchar *uid)
+{
+       g_return_if_fail (source->priv->uid == NULL);
+
+       /* It's okay for this to be NULL, in fact if we're given a
+        * GFile the UID is derived from its basename anyway.  This
+        * is just for memory-only sources in a collection backend,
+        * which don't have a GFile. */
+       source->priv->uid = g_strdup (uid);
+}
+
+static void
+server_side_source_set_property (GObject *object,
+                                 guint property_id,
+                                 const GValue *value,
+                                 GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_ALLOW_AUTH_PROMPT:
+                       e_server_side_source_set_allow_auth_prompt (
+                               E_SERVER_SIDE_SOURCE (object),
+                               g_value_get_boolean (value));
+                       return;
+
+               case PROP_FILE:
+                       server_side_source_set_file (
+                               E_SERVER_SIDE_SOURCE (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_REMOVABLE:
+                       e_server_side_source_set_removable (
+                               E_SERVER_SIDE_SOURCE (object),
+                               g_value_get_boolean (value));
+                       return;
+
+               case PROP_SERVER:
+                       server_side_source_set_server (
+                               E_SERVER_SIDE_SOURCE (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_UID:
+                       server_side_source_set_uid (
+                               E_SERVER_SIDE_SOURCE (object),
+                               g_value_get_string (value));
+                       return;
+
+               case PROP_WRITABLE:
+                       e_server_side_source_set_writable (
+                               E_SERVER_SIDE_SOURCE (object),
+                               g_value_get_boolean (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+server_side_source_get_property (GObject *object,
+                                 guint property_id,
+                                 GValue *value,
+                                 GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_ALLOW_AUTH_PROMPT:
+                       g_value_set_boolean (
+                               value,
+                               e_server_side_source_get_allow_auth_prompt (
+                               E_SERVER_SIDE_SOURCE (object)));
+                       return;
+
+               case PROP_FILE:
+                       g_value_set_object (
+                               value,
+                               e_server_side_source_get_file (
+                               E_SERVER_SIDE_SOURCE (object)));
+                       return;
+
+               case PROP_REMOVABLE:
+                       g_value_set_boolean (
+                               value,
+                               e_source_get_removable (
+                               E_SOURCE (object)));
+                       return;
+
+               case PROP_SERVER:
+                       g_value_set_object (
+                               value,
+                               e_server_side_source_get_server (
+                               E_SERVER_SIDE_SOURCE (object)));
+                       return;
+
+               case PROP_UID:
+                       g_value_take_string (
+                               value,
+                               e_source_dup_uid (
+                               E_SOURCE (object)));
+                       return;
+
+               case PROP_WRITABLE:
+                       g_value_set_boolean (
+                               value,
+                               e_source_get_writable (
+                               E_SOURCE (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+server_side_source_dispose (GObject *object)
+{
+       EServerSideSourcePrivate *priv;
+
+       priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (object);
+
+       if (priv->server != NULL) {
+               g_object_remove_weak_pointer (
+                       G_OBJECT (priv->server), &priv->server);
+               priv->server = NULL;
+       }
+
+       if (priv->file != NULL) {
+               g_object_unref (priv->file);
+               priv->file = NULL;
+       }
+
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_server_side_source_parent_class)->dispose (object);
+}
+
+static void
+server_side_source_finalize (GObject *object)
+{
+       EServerSideSourcePrivate *priv;
+
+       priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (object);
+
+       g_node_unlink (&priv->node);
+
+       g_free (priv->uid);
+       g_free (priv->file_contents);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_server_side_source_parent_class)->finalize (object);
+}
+
+static void
+server_side_source_changed (ESource *source)
+{
+       GDBusObject *dbus_object;
+       EDBusSource *dbus_source;
+       gchar *old_data;
+       gchar *new_data;
+       GError *error = NULL;
+
+       dbus_object = e_source_ref_dbus_object (source);
+       dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
+
+       old_data = e_dbus_source_dup_data (dbus_source);
+       new_data = e_source_to_string (source, NULL);
+
+       /* Setting the "data" property triggers the ESource::changed,
+        * signal, which invokes this callback, which sets the "data"
+        * property, etc.  This breaks an otherwise infinite loop. */
+       if (g_strcmp0 (old_data, new_data) != 0)
+               e_dbus_source_set_data (dbus_source, new_data);
+
+       g_free (old_data);
+       g_free (new_data);
+
+       g_object_unref (dbus_source);
+       g_object_unref (dbus_object);
+
+       /* This writes the "data" property to disk. */
+       e_source_write_sync (source, NULL, &error);
+
+       if (error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               g_error_free (error);
+       }
+}
+
+static gboolean
+server_side_source_remove_sync (ESource *source,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       AsyncClosure *closure;
+       GAsyncResult *result;
+       gboolean success;
+
+       closure = async_closure_new ();
+
+       e_source_remove (
+               source, cancellable, async_closure_callback, closure);
+
+       result = async_closure_wait (closure);
+
+       success = e_source_remove_finish (source, result, error);
+
+       async_closure_free (closure);
+
+       return success;
+}
+
+static void
+server_side_source_remove (ESource *source,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data)
+{
+       EServerSideSourcePrivate *priv;
+       GSimpleAsyncResult *simple;
+       ESourceRegistryServer *server;
+       GQueue queue = G_QUEUE_INIT;
+       GList *list, *link;
+       gboolean success = TRUE;
+       GError *error = NULL;
+
+       /* XXX Yes we block here.  We do this operation
+        *     synchronously to keep the server code simple. */
+
+       priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (source), callback, user_data,
+               server_side_source_remove);
+
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+       /* Collect the source and its descendants into a queue.
+        * Do this before unexporting so we hold references to
+        * all the removed sources. */
+       g_node_traverse (
+               &priv->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+               (GNodeTraverseFunc) server_side_source_traverse_cb, &queue);
+
+       /* Unexport the object and its descendants. */
+       server = E_SOURCE_REGISTRY_SERVER (priv->server);
+       e_source_registry_server_remove_source (server, source);
+
+       list = g_queue_peek_head_link (&queue);
+
+       /* Delete the key file for each source in the queue. */
+       for (link = list; link != NULL; link = g_list_next (link)) {
+               EServerSideSource *child;
+               GFile *file;
+
+               child = E_SERVER_SIDE_SOURCE (link->data);
+               file = e_server_side_source_get_file (child);
+
+               if (file != NULL)
+                       success = g_file_delete (file, cancellable, &error);
+
+               if (!success)
+                       goto exit;
+       }
+
+exit:
+       while (!g_queue_is_empty (&queue))
+               g_object_unref (g_queue_pop_head (&queue));
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+
+       g_simple_async_result_complete_in_idle (simple);
+       g_object_unref (simple);
+}
+
+static gboolean
+server_side_source_remove_finish (ESource *source,
+                                  GAsyncResult *result,
+                                  GError **error)
+{
+       GSimpleAsyncResult *simple;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (source),
+               server_side_source_remove), FALSE);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+
+       /* Assume success unless a GError is set. */
+       return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static gboolean
+server_side_source_write_sync (ESource *source,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+       AsyncClosure *closure;
+       GAsyncResult *result;
+       gboolean success;
+
+       closure = async_closure_new ();
+
+       e_source_write (
+               source, cancellable, async_closure_callback, closure);
+
+       result = async_closure_wait (closure);
+
+       success = e_source_write_finish (source, result, error);
+
+       async_closure_free (closure);
+
+       return success;
+}
+
+static void
+server_side_source_write (ESource *source,
+                          GCancellable *cancellable,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
+{
+       EServerSideSourcePrivate *priv;
+       GSimpleAsyncResult *simple;
+       GDBusObject *dbus_object;
+       EDBusSource *dbus_source;
+       gboolean replace_file;
+       const gchar *old_data;
+       gchar *new_data;
+       GError *error = NULL;
+
+       /* XXX Yes we block here.  We do this operation
+        *     synchronously to keep the server code simple. */
+
+       priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (source), callback, user_data,
+               server_side_source_write);
+
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+       dbus_object = e_source_ref_dbus_object (source);
+       dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
+
+       old_data = priv->file_contents;
+       new_data = e_source_to_string (source, NULL);
+
+       /* When writing source data to disk, we always write to the
+        * user directory even if the key file was originally in the
+        * system-wide directory.  For that reason, we want to avoid
+        * writing unmodified data from the system-wide directory. */
+
+       replace_file =
+               G_IS_FILE (priv->file) &&
+               (g_strcmp0 (old_data, new_data) != 0);
+
+       if (replace_file) {
+               GFile *file;
+               gchar *uid;
+
+               uid = e_server_side_source_uid_from_file (priv->file, NULL);
+               g_warn_if_fail (uid != NULL);  /* this should never fail */
+               file = e_server_side_source_new_user_file (uid);
+               g_free (uid);
+
+               if (!g_file_equal (file, priv->file)) {
+                       g_object_unref (priv->file);
+                       priv->file = g_object_ref (file);
+               }
+
+               server_side_source_print_diff (source, old_data, new_data);
+
+               g_file_replace_contents (
+                       file, new_data, strlen (new_data), NULL, FALSE,
+                       G_FILE_CREATE_NONE, NULL, cancellable, &error);
+
+               if (error == NULL) {
+                       g_free (priv->file_contents);
+                       priv->file_contents = new_data;
+                       new_data = NULL;
+               }
+
+               g_object_unref (file);
+       }
+
+       g_free (new_data);
+
+       g_object_unref (dbus_source);
+       g_object_unref (dbus_object);
+
+       if (error != NULL)
+               g_simple_async_result_take_error (simple, error);
+
+       g_simple_async_result_complete_in_idle (simple);
+       g_object_unref (simple);
+}
+
+static gboolean
+server_side_source_write_finish (ESource *source,
+                                 GAsyncResult *result,
+                                 GError **error)
+{
+       GSimpleAsyncResult *simple;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (source),
+               server_side_source_write), FALSE);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+
+       /* Assume success unless a GError is set. */
+       return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static gboolean
+server_side_source_initable_init (GInitable *initable,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       EServerSideSource *source;
+       GDBusObject *dbus_object;
+       EDBusSource *dbus_source;
+       GFile *file;
+
+       source = E_SERVER_SIDE_SOURCE (initable);
+       file = e_server_side_source_get_file (source);
+
+       if (file != NULL) {
+               g_warn_if_fail (source->priv->uid == NULL);
+               source->priv->uid =
+                       e_server_side_source_uid_from_file (file, error);
+               if (source->priv->uid == NULL)
+                       return FALSE;
+       }
+
+       if (source->priv->uid == NULL)
+               source->priv->uid = e_uid_new ();
+
+       dbus_source = e_dbus_source_skeleton_new ();
+
+       e_dbus_source_set_uid (dbus_source, source->priv->uid);
+
+       dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
+       e_dbus_object_skeleton_set_source (
+               E_DBUS_OBJECT_SKELETON (dbus_object), dbus_source);
+       g_object_unref (dbus_object);
+
+       g_signal_connect (
+               dbus_source, "handle-allow-auth-prompt",
+               G_CALLBACK (server_side_source_allow_auth_prompt_cb), source);
+
+       g_object_unref (dbus_source);
+
+       if (!e_server_side_source_load (source, cancellable, error))
+               return FALSE;
+
+       /* Chain up to parent interface's init() method. */
+       return initable_parent_interface->init (initable, cancellable, error);
+}
+
+static void
+e_server_side_source_class_init (EServerSideSourceClass *class)
+{
+       GObjectClass *object_class;
+       ESourceClass *source_class;
+
+       g_type_class_add_private (class, sizeof (EServerSideSourcePrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = server_side_source_set_property;
+       object_class->get_property = server_side_source_get_property;
+       object_class->dispose = server_side_source_dispose;
+       object_class->finalize = server_side_source_finalize;
+
+       source_class = E_SOURCE_CLASS (class);
+       source_class->changed = server_side_source_changed;
+       source_class->remove_sync = server_side_source_remove_sync;
+       source_class->remove = server_side_source_remove;
+       source_class->remove_finish = server_side_source_remove_finish;
+       source_class->write_sync = server_side_source_write_sync;
+       source_class->write = server_side_source_write;
+       source_class->write_finish = server_side_source_write_finish;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_ALLOW_AUTH_PROMPT,
+               g_param_spec_boolean (
+                       "allow-auth-prompt",
+                       "Allow Auth Prompt",
+                       "Whether authentication sessions may "
+                       "interrupt the user for a password",
+                       TRUE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_FILE,
+               g_param_spec_object (
+                       "file",
+                       "File",
+                       "The key file for the data source",
+                       G_TYPE_FILE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       /* This overrides the "removable" property
+        * in ESourceClass with a writable version. */
+       g_object_class_install_property (
+               object_class,
+               PROP_REMOVABLE,
+               g_param_spec_boolean (
+                       "removable",
+                       "Removable",
+                       "Whether the data source is removable",
+                       FALSE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_SERVER,
+               g_param_spec_object (
+                       "server",
+                       "Server",
+                       "The server to which the data source belongs",
+                       E_TYPE_SOURCE_REGISTRY_SERVER,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       /* This overrides the "uid" property in
+        * ESourceClass with a construct-only version. */
+       g_object_class_install_property (
+               object_class,
+               PROP_UID,
+               g_param_spec_string (
+                       "uid",
+                       "UID",
+                       "The unique identity of the data source",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       /* This overrides the "writable" property
+        * in ESourceClass with a writable version. */
+       g_object_class_install_property (
+               object_class,
+               PROP_WRITABLE,
+               g_param_spec_boolean (
+                       "writable",
+                       "Writable",
+                       "Whether the data source is writable",
+                       FALSE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_server_side_source_initable_init (GInitableIface *interface)
+{
+       initable_parent_interface = g_type_interface_peek_parent (interface);
+
+       interface->init = server_side_source_initable_init;
+}
+
+static void
+e_server_side_source_init (EServerSideSource *source)
+{
+       source->priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
+
+       source->priv->node.data = source;
+}
+
+/**
+ * e_server_side_source_get_user_dir:
+ *
+ * Returns the directory where user-specific data source files are stored.
+ *
+ * Returns: the user-specific data source directory
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_server_side_source_get_user_dir (void)
+{
+       static gchar *dirname = NULL;
+
+       if (G_UNLIKELY (dirname == NULL)) {
+               const gchar *config_dir = e_get_user_config_dir ();
+               dirname = g_build_filename (config_dir, "sources", NULL);
+               g_mkdir_with_parents (dirname, 0700);
+       }
+
+       return dirname;
+}
+
+/**
+ * e_server_side_source_new_user_file:
+ * @uid: unique identifier for a data source, or %NULL
+ *
+ * Generates a unique file name for a new user-specific data source.
+ * If @uid is non-%NULL it will be used in the basename of the file,
+ * otherwise a unique basename will be generated using e_uid_new().
+ *
+ * The returned #GFile can then be passed to e_server_side_source_new().
+ * Unreference the #GFile with g_object_unref() when finished with it.
+ *
+ * Note the data source file itself is not created here, only its name.
+ *
+ * Returns: the #GFile for a new data source
+ *
+ * Since: 3.6
+ **/
+GFile *
+e_server_side_source_new_user_file (const gchar *uid)
+{
+       GFile *file;
+       gchar *safe_uid;
+       gchar *basename;
+       gchar *filename;
+       const gchar *user_dir;
+
+       if (uid == NULL)
+               safe_uid = e_uid_new ();
+       else
+               safe_uid = g_strdup (uid);
+       e_filename_make_safe (safe_uid);
+
+       user_dir = e_server_side_source_get_user_dir ();
+       basename = g_strconcat (safe_uid, ".source", NULL);
+       filename = g_build_filename (user_dir, basename, NULL);
+
+       file = g_file_new_for_path (filename);
+
+       g_free (basename);
+       g_free (filename);
+       g_free (safe_uid);
+
+       return file;
+}
+
+/**
+ * e_server_side_source_uid_from_file:
+ * @file: a #GFile for a data source
+ * @error: return location for a #GError, or %NULL
+ *
+ * Extracts a unique identity string from the base name of @file.
+ * If the base name of @file is missing a '.source' extension, the
+ * function sets @error and returns %NULL.
+ *
+ * Returns: the unique identity string for @file, or %NULL
+ *
+ * Since: 3.6
+ **/
+gchar *
+e_server_side_source_uid_from_file (GFile *file,
+                                    GError **error)
+{
+       gchar *basename;
+       gchar *uid = NULL;
+
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       basename = g_file_get_basename (file);
+
+       if (g_str_has_suffix (basename, ".source")) {
+               /* strlen(".source") --> 7 */
+               uid = g_strndup (basename, strlen (basename) - 7);
+       } else {
+               g_set_error (
+                       error, G_IO_ERROR,
+                       G_IO_ERROR_INVALID_FILENAME,
+                       _("File must have a '.source' extension"));
+       }
+
+       g_free (basename);
+
+       return uid;
+}
+
+/**
+ * e_server_side_source_new:
+ * @server: an #ESourceRegistryServer
+ * @file: a #GFile, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new #EServerSideSource which belongs to @server.  If @file
+ * is non-%NULL and points to an existing file, the #EServerSideSource is
+ * initialized from the file content.  If a read error occurs or the file
+ * contains syntax errors, the function sets @error and returns %NULL.
+ *
+ * Returns: a new #EServerSideSource, or %NULL
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_server_side_source_new (ESourceRegistryServer *server,
+                          GFile *file,
+                          GError **error)
+{
+       EDBusObjectSkeleton *dbus_object;
+       ESource *source;
+
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
+       g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
+
+       /* XXX This is an awkward way of initializing the "dbus-object"
+        *     property, but e_source_ref_dbus_object() needs to work. */
+       dbus_object = e_dbus_object_skeleton_new (DBUS_OBJECT_PATH);
+
+       source = g_initable_new (
+               E_TYPE_SERVER_SIDE_SOURCE, NULL, error,
+               "dbus-object", dbus_object,
+               "file", file, "server", server, NULL);
+
+       g_object_unref (dbus_object);
+
+       return source;
+}
+
+/**
+ * e_server_side_source_new_memory_only:
+ * @server: an #ESourceRegistryServer
+ * @uid: a unique identifier, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a memory-only #EServerSideSource which belongs to @server.
+ * No on-disk key file is created for this data source, so it will not
+ * be remembered across sessions.
+ *
+ * Data source collections are often populated with memory-only data
+ * sources to serve as proxies for resources discovered on a remote server.
+ * These data sources are usually neither #EServerSideSource:writable nor
+ * #EServerSideSource:removable by clients, at least not directly.
+ *
+ * If an error occurs while instantiating the #EServerSideSource, the
+ * function sets @error and returns %NULL.  Although at this time there
+ * are no known error conditions for memory-only data sources.
+ *
+ * Returns: a new memory-only #EServerSideSource, or %NULL
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_server_side_source_new_memory_only (ESourceRegistryServer *server,
+                                      const gchar *uid,
+                                      GError **error)
+{
+       EDBusObjectSkeleton *dbus_object;
+       ESource *source;
+
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
+
+       /* XXX This is an awkward way of initializing the "dbus-object"
+        *     property, but e_source_ref_dbus_object() needs to work. */
+       dbus_object = e_dbus_object_skeleton_new (DBUS_OBJECT_PATH);
+
+       source = g_initable_new (
+               E_TYPE_SERVER_SIDE_SOURCE, NULL, error,
+               "dbus-object", dbus_object,
+               "server", server, "uid", uid, NULL);
+
+       g_object_unref (dbus_object);
+
+       return source;
+}
+
+/**
+ * e_server_side_source_load:
+ * @source: an #EServerSideSource
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Reloads data source content from the file pointed to by the
+ * #EServerSideSource:file property.
+ *
+ * If the #EServerSideSource:file property is %NULL or the file it points
+ * to does not exist, the function does nothing and returns %TRUE.
+ *
+ * If a read error occurs or the file contains syntax errors, the function
+ * sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_server_side_source_load (EServerSideSource *source,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       GDBusObject *dbus_object;
+       EDBusSource *dbus_source;
+       GKeyFile *key_file;
+       GFile *file;
+       gboolean success;
+       gchar *data = NULL;
+       gsize length;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), FALSE);
+
+       file = e_server_side_source_get_file (source);
+
+       if (file != NULL)
+               g_file_load_contents (
+                       file, cancellable, &data,
+                       &length, NULL, &local_error);
+
+       /* Disregard G_IO_ERROR_NOT_FOUND and treat it as a successful load. */
+       if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+               g_error_free (local_error);
+
+       } else if (local_error != NULL) {
+               g_propagate_error (error, local_error);
+               return FALSE;
+
+       } else {
+               source->priv->file_contents = g_strdup (data);
+       }
+
+       if (data == NULL) {
+               /* Create the bare minimum to pass parse_data(). */
+               data = g_strdup_printf ("[%s]", PRIMARY_GROUP_NAME);
+               length = strlen (data);
+       }
+
+       key_file = g_key_file_new ();
+
+       success = server_side_source_parse_data (
+               key_file, data, length, error);
+
+       g_key_file_free (key_file);
+
+       if (!success) {
+               g_free (data);
+               return FALSE;
+       }
+
+       /* Update the D-Bus interface properties. */
+
+       dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
+       dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
+
+       e_dbus_source_set_data (dbus_source, data);
+
+       g_object_unref (dbus_source);
+       g_object_unref (dbus_object);
+
+       g_free (data);
+
+       return TRUE;
+}
+
+/**
+ * e_server_side_source_get_file:
+ * @source: an #EServerSideSource
+ *
+ * Returns the #GFile from which data source content is loaded and to
+ * which changes are saved.  Note the @source may not have a #GFile.
+ *
+ * Returns: the #GFile for @source, or %NULL
+ *
+ * Since: 3.6
+ **/
+GFile *
+e_server_side_source_get_file (EServerSideSource *source)
+{
+       g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
+
+       return source->priv->file;
+}
+
+/**
+ * e_server_side_source_get_node:
+ * @source: an #EServerSideSource
+ *
+ * Returns the #GNode representing the @source's hierarchical placement,
+ * or %NULL if @source has not been placed in the data source hierarchy.
+ * The data member of the #GNode points back to @source.  This is an easy
+ * way to traverse ancestor and descendant data sources.
+ *
+ * Note that accessing other data sources this way is not thread-safe,
+ * and this therefore function may be replaced at some later date.
+ *
+ * Returns: a #GNode, or %NULL
+ *
+ * Since: 3.6
+ **/
+GNode *
+e_server_side_source_get_node (EServerSideSource *source)
+{
+       g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
+
+       return &source->priv->node;
+}
+
+/**
+ * e_server_side_source_get_server:
+ * @source: an #EServerSideSource
+ *
+ * Returns the #ESourceRegistryServer to which @source belongs.
+ *
+ * Returns: the #ESourceRegistryServer for @source
+ *
+ * Since: 3.6
+ **/
+ESourceRegistryServer *
+e_server_side_source_get_server (EServerSideSource *source)
+{
+       g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
+
+       return source->priv->server;
+}
+
+/**
+ * e_server_side_source_get_allow_auth_prompt:
+ * @source: an #EServerSideSource
+ *
+ * Returns whether an authentication prompt is allowed to be shown
+ * for @source.  #EAuthenticationSession will honor this setting by
+ * dismissing the session if it can't find a valid stored password.
+ *
+ * See e_server_side_source_set_allow_auth_prompt() for further
+ * discussion.
+ *
+ * Returns: whether auth prompts are allowed for @source
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_server_side_source_get_allow_auth_prompt (EServerSideSource *source)
+{
+       g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), FALSE);
+
+       return source->priv->allow_auth_prompt;
+}
+
+/**
+ * e_server_side_source_set_allow_auth_prompt:
+ * @source: an #EServerSideSource
+ * @allow_auth_prompt: whether auth prompts are allowed for @source
+ *
+ * Sets whether an authentication prompt is allowed to be shown for @source.
+ * #EAuthenticationSession will honor this setting by dismissing the session
+ * if it can't find a valid stored password.
+ *
+ * If the user declines to provide a password for @source when prompted
+ * by an #EAuthenticationSession, the #ESourceRegistryServer will set this
+ * property to %FALSE to suppress any further prompting, which would likely
+ * annoy the user.  However when an #ESourceRegistry instance is created by
+ * a client application, the first thing it does is reset this property to
+ * %TRUE for all registered data sources.  So suppressing authentication
+ * prompts is only ever temporary.
+ *
+ * Since: 3.6
+ **/
+void
+e_server_side_source_set_allow_auth_prompt (EServerSideSource *source,
+                                            gboolean allow_auth_prompt)
+{
+       g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+       source->priv->allow_auth_prompt = allow_auth_prompt;
+
+       g_object_notify (G_OBJECT (source), "allow-auth-prompt");
+}
+
+/**
+ * e_server_side_source_set_removable:
+ * @source: an #EServerSideSource
+ * @removable: whether to export the Removable interface
+ *
+ * Sets whether to allow registry clients to remove @source and its
+ * descendants.  If %TRUE, the Removable D-Bus interface is exported at
+ * the object path for @source.  If %FALSE, the Removable D-Bus interface
+ * is unexported at the object path for @source, and any attempt by clients
+ * to call e_source_remove() will fail.
+ *
+ * Note this is only enforced for clients of the registry D-Bus service.
+ * The service itself may remove any data source at any time.
+ *
+ * Since: 3.6
+ **/
+void
+e_server_side_source_set_removable (EServerSideSource *source,
+                                    gboolean removable)
+{
+       EDBusSourceRemovable *dbus_source_removable = NULL;
+       GDBusObject *dbus_object;
+       gboolean currently_removable;
+
+       g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+       currently_removable = e_source_get_removable (E_SOURCE (source));
+
+       if (removable == currently_removable)
+               return;
+
+       if (removable) {
+               dbus_source_removable =
+                       e_dbus_source_removable_skeleton_new ();
+
+               g_signal_connect (
+                       dbus_source_removable, "handle-remove",
+                       G_CALLBACK (server_side_source_remove_cb), source);
+       }
+
+       dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
+       e_dbus_object_skeleton_set_source_removable (
+               E_DBUS_OBJECT_SKELETON (dbus_object), dbus_source_removable);
+       g_object_unref (dbus_object);
+
+       if (dbus_source_removable != NULL)
+               g_object_unref (dbus_source_removable);
+
+       g_object_notify (G_OBJECT (source), "removable");
+}
+
+/**
+ * e_server_side_source_set_writable:
+ * @source: an #EServerSideSource
+ * @writable: whether to export the Writable interface
+ *
+ * Sets whether to allow registry clients to alter the content of @source.
+ * If %TRUE, the Writable D-Bus interface is exported at the object path
+ * for @source.  If %FALSE, the Writable D-Bus interface is unexported at
+ * the object path for @source, and any attempt by clients to call
+ * e_source_write() will fail.
+ *
+ * Note this is only enforced for clients of the registry D-Bus service.
+ * The service itself can write to any data source at any time.
+ *
+ * Since: 3.6
+ **/
+void
+e_server_side_source_set_writable (EServerSideSource *source,
+                                   gboolean writable)
+{
+       EDBusSourceWritable *dbus_source_writable = NULL;
+       GDBusObject *dbus_object;
+       gboolean currently_writable;
+
+       g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+       currently_writable = e_source_get_writable (E_SOURCE (source));
+
+       if (writable == currently_writable)
+               return;
+
+       if (writable) {
+               dbus_source_writable =
+                       e_dbus_source_writable_skeleton_new ();
+
+               g_signal_connect (
+                       dbus_source_writable, "handle-write",
+                       G_CALLBACK (server_side_source_write_cb), source);
+       }
+
+       dbus_object = e_source_ref_dbus_object (E_SOURCE (source));
+       e_dbus_object_skeleton_set_source_writable (
+               E_DBUS_OBJECT_SKELETON (dbus_object), dbus_source_writable);
+       g_object_unref (dbus_object);
+
+       if (dbus_source_writable != NULL)
+               g_object_unref (dbus_source_writable);
+
+       g_object_notify (G_OBJECT (source), "writable");
+}
+
diff --git a/libebackend/e-server-side-source.h b/libebackend/e-server-side-source.h
new file mode 100644 (file)
index 0000000..74ca374
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * e-server-side-source.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_SERVER_SIDE_SOURCE_H
+#define E_SERVER_SIDE_SOURCE_H
+
+#include <libedataserver/e-source.h>
+#include <libebackend/e-source-registry-server.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SERVER_SIDE_SOURCE \
+       (e_server_side_source_get_type ())
+#define E_SERVER_SIDE_SOURCE(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSource))
+#define E_SERVER_SIDE_SOURCE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSourceClass))
+#define E_IS_SERVER_SIDE_SOURCE(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_SERVER_SIDE_SOURCE))
+#define E_IS_SERVER_SIDE_SOURCE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_SERVER_SIDE_SOURCE))
+#define E_SERVER_SIDE_SOURCE_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSourceClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EServerSideSource EServerSideSource;
+typedef struct _EServerSideSourceClass EServerSideSourceClass;
+typedef struct _EServerSideSourcePrivate EServerSideSourcePrivate;
+
+/**
+ * EServerSideSource:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.6
+ **/
+struct _EServerSideSource {
+       ESource parent;
+       EServerSideSourcePrivate *priv;
+};
+
+struct _EServerSideSourceClass {
+       ESourceClass parent_class;
+};
+
+GType          e_server_side_source_get_type   (void) G_GNUC_CONST;
+const gchar *  e_server_side_source_get_user_dir
+                                               (void) G_GNUC_CONST;
+GFile *                e_server_side_source_new_user_file
+                                               (const gchar *uid);
+gchar *                e_server_side_source_uid_from_file
+                                               (GFile *file,
+                                                GError **error);
+ESource *      e_server_side_source_new        (ESourceRegistryServer *server,
+                                                GFile *file,
+                                                GError **error);
+ESource *      e_server_side_source_new_memory_only
+                                               (ESourceRegistryServer *server,
+                                                const gchar *uid,
+                                                GError **error);
+gboolean       e_server_side_source_load       (EServerSideSource *source,
+                                                GCancellable *cancellable,
+                                                GError **error);
+GFile *                e_server_side_source_get_file   (EServerSideSource *source);
+GNode *                e_server_side_source_get_node   (EServerSideSource *source);
+ESourceRegistryServer *
+               e_server_side_source_get_server (EServerSideSource *source);
+gboolean       e_server_side_source_get_allow_auth_prompt
+                                               (EServerSideSource *source);
+void           e_server_side_source_set_allow_auth_prompt
+                                               (EServerSideSource *source,
+                                                gboolean allow_auth_prompt);
+void           e_server_side_source_set_removable
+                                               (EServerSideSource *source,
+                                                gboolean removable);
+void           e_server_side_source_set_writable
+                                               (EServerSideSource *source,
+                                                gboolean writable);
+
+G_END_DECLS
+
+#endif /* E_SERVER_SIDE_SOURCE_H */
+
diff --git a/libebackend/e-source-registry-server.c b/libebackend/e-source-registry-server.c
new file mode 100644 (file)
index 0000000..8db22e2
--- /dev/null
@@ -0,0 +1,1801 @@
+/*
+ * e-source-registry-server.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-source-registry-server
+ * @include: libebackend/e-source-registry-server.h
+ * @short_description: Server-side repository for data sources
+ *
+ * The #ESourceRegistryServer is the heart of the registry D-Bus service.
+ * Acting as a global singleton store for all #EServerSideSource instances,
+ * its responsibilities include loading data source content from key files,
+ * exporting data sources to clients over D-Bus, handling authentication
+ * and content change requests from clients, and saving content changes
+ * back to key files.
+ *
+ * It also hosts any number of built-in or 3rd party data source collection
+ * backends, which coordinate with #ESourceRegistryServer to automatically
+ * advertise available data sources on a remote server.
+ **/
+
+#include "e-source-registry-server.h"
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+/* Private D-Bus classes. */
+#include <e-dbus-source.h>
+#include <e-dbus-source-manager.h>
+
+#include <libedataserver/e-uid.h>
+#include <libedataserver/e-marshal.h>
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-source-collection.h>
+
+#include <libebackend/e-authentication-mediator.h>
+#include <libebackend/e-authentication-session.h>
+#include <libebackend/e-server-side-source.h>
+
+#define E_SOURCE_REGISTRY_SERVER_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), E_TYPE_SOURCE_REGISTRY_SERVER, ESourceRegistryServerPrivate))
+
+/* Collection backends get tacked on to
+ * sources with a [Collection] extension. */
+#define BACKEND_DATA_KEY "__e_collection_backend__"
+
+struct _ESourceRegistryServerPrivate {
+       GDBusObjectManagerServer *object_manager;
+       EDBusSourceManager *source_manager;
+
+       GHashTable *sources;  /* sources added to hierarchy */
+       GHashTable *orphans;  /* sources waiting for parent */
+       GHashTable *monitors;
+
+       GMutex *sources_lock;
+       GMutex *orphans_lock;
+
+       /* In pseudo-Python notation:
+        *
+        * auth_queue = [ UID, ... ]
+        * auth_table = { UID : [ authenticator, ... ] }
+        * active_auth_table_queue = auth_table[auth_queue[0]]
+        * active_auth = active_auth_table_queue[0]
+        *
+        * We process all authenticators for a given source UID at once.
+        * The thought being after the first authenticator for a given UID
+        * completes (the first being most likely to trigger a user prompt),
+        * then any other authenticators for that same UID should complete
+        * quickly, hopefully without having to reprompt.  That is unless
+        * the user decides not to cache the secret at all, in which case
+        * he gets what he asked for: lots of annoying prompts.
+        */
+       GQueue *auth_queue;
+       GHashTable *auth_table;
+       GQueue *active_auth_table_queue;
+       EAuthenticationSession *active_auth;
+       GCancellable *auth_cancellable;
+
+       guint authentication_count;
+};
+
+enum {
+       LOAD_ERROR,
+       FILES_LOADED,
+       SOURCE_ADDED,
+       SOURCE_REMOVED,
+       LAST_SIGNAL
+};
+
+/* Forward Declarations */
+static void    source_registry_server_maybe_start_auth_session
+                                               (ESourceRegistryServer *server);
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+       ESourceRegistryServer,
+       e_source_registry_server,
+       E_TYPE_DATA_FACTORY)
+
+/* GDestroyNotify callback for 'sources' values */
+static void
+unref_data_source (ESource *source)
+{
+       /* The breaks the reference cycle with ECollectionBackend. */
+       g_object_set_data (G_OBJECT (source), BACKEND_DATA_KEY, NULL);
+       g_object_unref (source);
+}
+
+/* GDestroyNotify callback for 'auth_table' values */
+static void
+free_auth_queue (GQueue *queue)
+{
+       /* XXX g_queue_clear_full() would be nice here. */
+       while (!g_queue_is_empty (queue))
+               g_object_unref (g_queue_pop_head (queue));
+
+       g_queue_free (queue);
+}
+
+static void
+source_registry_server_sources_insert (ESourceRegistryServer *server,
+                                       ESource *source)
+{
+       const gchar *uid;
+
+       uid = e_source_get_uid (source);
+       g_return_if_fail (uid != NULL);
+
+       g_mutex_lock (server->priv->sources_lock);
+
+       g_hash_table_insert (
+               server->priv->sources,
+               g_strdup (uid), g_object_ref (source));
+
+       g_mutex_unlock (server->priv->sources_lock);
+}
+
+static gboolean
+source_registry_server_sources_remove (ESourceRegistryServer *server,
+                                       ESource *source)
+{
+       const gchar *uid;
+       gboolean removed;
+
+       uid = e_source_get_uid (source);
+       g_return_val_if_fail (uid != NULL, FALSE);
+
+       g_mutex_lock (server->priv->sources_lock);
+
+       removed = g_hash_table_remove (server->priv->sources, uid);
+
+       g_mutex_unlock (server->priv->sources_lock);
+
+       return removed;
+}
+
+static ESource *
+source_registry_server_sources_lookup (ESourceRegistryServer *server,
+                                       const gchar *uid)
+{
+       ESource *source;
+
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       g_mutex_lock (server->priv->sources_lock);
+
+       source = g_hash_table_lookup (server->priv->sources, uid);
+
+       if (source != NULL)
+               g_object_ref (source);
+
+       g_mutex_unlock (server->priv->sources_lock);
+
+       return source;
+}
+
+static GList *
+source_registry_server_sources_get_values (ESourceRegistryServer *server)
+{
+       GList *values;
+
+       g_mutex_lock (server->priv->sources_lock);
+
+       values = g_hash_table_get_values (server->priv->sources);
+
+       g_list_foreach (values, (GFunc) g_object_ref, NULL);
+
+       g_mutex_unlock (server->priv->sources_lock);
+
+       return values;
+}
+
+static void
+source_registry_server_orphans_insert (ESourceRegistryServer *server,
+                                       ESource *orphan_source)
+{
+       GHashTable *orphans;
+       GPtrArray *array;
+       gchar *parent_uid;
+
+       g_mutex_lock (server->priv->orphans_lock);
+
+       orphans = server->priv->orphans;
+
+       parent_uid = e_source_dup_parent (orphan_source);
+
+       /* A top-level object has no parent UID, so we
+        * use a special "empty" key in the hash table. */
+       if (parent_uid == NULL)
+               parent_uid = g_strdup ("");
+
+       array = g_hash_table_lookup (orphans, parent_uid);
+
+       if (array == NULL) {
+               array = g_ptr_array_new_with_free_func (g_object_unref);
+
+               /* Takes ownership of the 'parent_uid' string. */
+               g_hash_table_insert (orphans, parent_uid, array);
+               parent_uid = NULL;
+       }
+
+       g_ptr_array_add (array, g_object_ref (orphan_source));
+
+       g_free (parent_uid);
+
+       g_mutex_unlock (server->priv->orphans_lock);
+}
+
+static gboolean
+source_registry_server_orphans_remove (ESourceRegistryServer *server,
+                                       ESource *orphan_source)
+{
+       GHashTable *orphans;
+       GPtrArray *array;
+       gchar *parent_uid;
+       gboolean removed = FALSE;
+
+       g_mutex_lock (server->priv->orphans_lock);
+
+       orphans = server->priv->orphans;
+
+       parent_uid = e_source_dup_parent (orphan_source);
+
+       /* A top-level object has no parent UID, so we
+        * use a special "empty" key in the hash table. */
+       if (parent_uid == NULL)
+               parent_uid = g_strdup ("");
+
+       array = g_hash_table_lookup (orphans, parent_uid);
+
+       if (array != NULL) {
+               /* Array is not ordered, so use "remove_fast". */
+               removed = g_ptr_array_remove_fast (array, orphan_source);
+       }
+
+       g_free (parent_uid);
+
+       g_mutex_unlock (server->priv->orphans_lock);
+
+       return removed;
+}
+
+static GPtrArray *
+source_registry_server_orphans_steal (ESourceRegistryServer *server,
+                                      ESource *parent_source)
+{
+       GHashTable *orphans;
+       GPtrArray *array;
+       const gchar *parent_uid;
+
+       parent_uid = e_source_get_uid (parent_source);
+       g_return_val_if_fail (parent_uid != NULL, NULL);
+
+       g_mutex_lock (server->priv->orphans_lock);
+
+       orphans = server->priv->orphans;
+
+       array = g_hash_table_lookup (orphans, parent_uid);
+
+       /* g_hash_table_remove() will unreference the array,
+        * so we need to reference it first to keep it alive. */
+       if (array != NULL) {
+               g_ptr_array_ref (array);
+               g_hash_table_remove (orphans, parent_uid);
+       }
+
+       g_mutex_unlock (server->priv->orphans_lock);
+
+       return array;
+}
+
+static GQueue *
+source_registry_server_auth_table_lookup (ESourceRegistryServer *server,
+                                          const gchar *uid)
+{
+       GHashTable *hash_table;
+       GQueue *queue;
+
+       hash_table = server->priv->auth_table;
+       queue = g_hash_table_lookup (hash_table, uid);
+
+       if (queue == NULL) {
+               queue = g_queue_new ();
+               g_hash_table_insert (hash_table, g_strdup (uid), queue);
+       }
+
+       return queue;
+}
+
+static void
+source_registry_server_auth_session_cb (GObject *source_object,
+                                        GAsyncResult *result,
+                                        gpointer user_data)
+{
+       EAuthenticationSession *session;
+       ESourceRegistryServer *server;
+       EAuthenticationSessionResult auth_result;
+       GQueue *queue;
+       const gchar *uid;
+       GError *error = NULL;
+
+       session = E_AUTHENTICATION_SESSION (source_object);
+       server = E_SOURCE_REGISTRY_SERVER (user_data);
+
+       auth_result = e_authentication_session_execute_finish (
+               session, result, &error);
+
+       if (error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               g_error_free (error);
+       }
+
+       uid = e_authentication_session_get_source_uid (session);
+       g_return_if_fail (uid != NULL);
+
+       /* Authentication dismissals may require additional handling. */
+       if (auth_result == E_AUTHENTICATION_SESSION_DISMISSED) {
+               ESourceAuthenticator *authenticator;
+               ESource *source;
+
+               /* If the authenticator is an EAuthenticationMediator,
+                * have it emit a "dismissed" signal to the client. */
+               authenticator =
+                       e_authentication_session_get_authenticator (session);
+               if (E_IS_AUTHENTICATION_MEDIATOR (authenticator))
+                       e_authentication_mediator_dismiss (
+                               E_AUTHENTICATION_MEDIATOR (authenticator));
+
+               /* This will return NULL if the authenticating data source
+                * has not yet been submitted to the D-Bus registry service. */
+               source = e_source_registry_server_ref_source (server, uid);
+               if (source != NULL) {
+                       /* Prevent further user interruptions. */
+                       e_server_side_source_set_allow_auth_prompt (
+                               E_SERVER_SIDE_SOURCE (source), FALSE);
+                       g_object_unref (source);
+               }
+       }
+
+       queue = source_registry_server_auth_table_lookup (server, uid);
+
+       /* Remove the completed auth session from its queue. */
+       if (g_queue_remove (queue, session))
+               g_object_unref (session);
+
+       /* If the completed auth session was the active one,
+        * clear the active pointer for the next auth session. */
+       if (session == server->priv->active_auth)
+               server->priv->active_auth = NULL;
+
+       source_registry_server_maybe_start_auth_session (server);
+
+       g_object_unref (server);
+}
+
+static void
+source_registry_server_maybe_start_auth_session (ESourceRegistryServer *server)
+{
+       GQueue *queue;
+
+       if (server->priv->active_auth != NULL)
+               return;
+
+       if (g_cancellable_is_cancelled (server->priv->auth_cancellable))
+               return;
+
+       /* Check if there's any authenticators left in the active
+        * auth table queue.  If the user provided a valid secret
+        * and elected to save it in the keyring, or if the user
+        * dismissed the authentication prompt, then we should be
+        * able to process the remaining authenticators quickly. */
+       if (server->priv->active_auth_table_queue != NULL &&
+           !g_queue_is_empty (server->priv->active_auth_table_queue)) {
+               queue = server->priv->active_auth_table_queue;
+               server->priv->active_auth = g_queue_peek_head (queue);
+
+       /* Otherwise find the next non-empty auth table queue to
+        * be processed, according to the UIDs in the auth queue. */
+       } else while (!g_queue_is_empty (server->priv->auth_queue)) {
+               gchar *uid;
+
+               uid = g_queue_pop_head (server->priv->auth_queue);
+               queue = source_registry_server_auth_table_lookup (server, uid);
+               g_free (uid);
+
+               if (!g_queue_is_empty (queue)) {
+                       server->priv->active_auth_table_queue = queue;
+                       server->priv->active_auth = g_queue_peek_head (queue);
+                       break;
+               }
+       }
+
+       /* Initiate the new active authenicator.  This signals it to
+        * respond with a cached secret in the keyring if it can, or
+        * else show an authentication prompt and wait for input. */
+       if (server->priv->active_auth != NULL)
+               e_authentication_session_execute (
+                       server->priv->active_auth,
+                       G_PRIORITY_DEFAULT,
+                       server->priv->auth_cancellable,
+                       source_registry_server_auth_session_cb,
+                       g_object_ref (server));
+       else
+               server->priv->active_auth_table_queue = NULL;
+}
+
+static void
+source_registry_server_wait_for_client_cb (GObject *source_object,
+                                           GAsyncResult *result,
+                                           gpointer user_data)
+{
+       EAuthenticationMediator *mediator;
+       EAuthenticationSession *session;
+       GError *error = NULL;
+
+       mediator = E_AUTHENTICATION_MEDIATOR (source_object);
+       session = E_AUTHENTICATION_SESSION (user_data);
+
+       e_authentication_mediator_wait_for_client_finish (
+               mediator, result, &error);
+
+       if (error == NULL) {
+               ESourceRegistryServer *server;
+
+               /* This references the session and adds it to a queue. */
+               server = e_authentication_session_get_server (session);
+               e_source_registry_server_queue_auth_session (server, session);
+
+       } else {
+               /* Most likely the client went dark and the operation
+                * timed out.  Emit a dismissed signal anyway just in
+                * case the client is still alive and listening. */
+               e_authentication_mediator_dismiss (mediator);
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               g_error_free (error);
+       }
+
+       g_object_unref (session);
+}
+
+static gboolean
+source_registry_server_authenticate_cb (EDBusSourceManager *interface,
+                                        GDBusMethodInvocation *invocation,
+                                        const gchar *source_uid,
+                                        const gchar *prompt_title,
+                                        const gchar *prompt_message,
+                                        const gchar *prompt_description,
+                                        ESourceRegistryServer *server)
+{
+       GDBusConnection *connection;
+       EAuthenticationSession *session;
+       ESourceAuthenticator *authenticator;
+       const gchar *base_object_path;
+       const gchar *sender;
+       gchar *auth_object_path;
+       GError *error = NULL;
+
+       /* Export the D-Bus interface to a unique object path.  This
+        * effectively starts a new authentication session with the
+        * method caller. */
+
+       base_object_path = E_SOURCE_REGISTRY_SERVER_OBJECT_PATH;
+       connection = g_dbus_method_invocation_get_connection (invocation);
+       sender = g_dbus_method_invocation_get_sender (invocation);
+
+       auth_object_path = g_strdup_printf (
+               "%s/auth_%u", base_object_path,
+               server->priv->authentication_count++);
+
+       authenticator = e_authentication_mediator_new (
+               connection, auth_object_path, sender, &error);
+
+       if (error != NULL) {
+               g_warn_if_fail (authenticator == NULL);
+               g_dbus_method_invocation_take_error (invocation, error);
+               g_free (auth_object_path);
+               return TRUE;
+       }
+
+       g_return_val_if_fail (
+               E_IS_SOURCE_AUTHENTICATOR (authenticator), FALSE);
+
+       /* Create the authentication session. */
+       session = e_authentication_session_new (
+               server, authenticator, source_uid);
+
+       /* Configure the authentication session. */
+       g_object_set (
+               session,
+               "prompt-title", prompt_title,
+               "prompt-message", prompt_message,
+               "prompt-description", prompt_description,
+               NULL);
+
+       /* Before adding the authentication session to the server we
+        * must handshake with the client requesting authentication.
+        * We do this by returning the object path of the exported
+        * Authenticator interface and then waiting for the client to
+        * acknowledge by calling the Ready() method on the interface.
+        * This indicates the client is ready to receive signals.
+        *
+        * XXX Note this asynchronous operation is not cancellable
+        *     but it does time out on its own after a few minutes. */
+       e_authentication_mediator_wait_for_client (
+               E_AUTHENTICATION_MEDIATOR (authenticator),
+               NULL, source_registry_server_wait_for_client_cb,
+               g_object_ref (session));
+
+       e_dbus_source_manager_complete_authenticate (
+               interface, invocation, auth_object_path);
+
+       g_object_unref (authenticator);
+       g_object_unref (session);
+       g_free (auth_object_path);
+
+       return TRUE;
+}
+
+static gboolean
+source_registry_server_create_source (ESourceRegistryServer *server,
+                                      const gchar *uid,
+                                      const gchar *data,
+                                      GError **error)
+{
+       ESource *source = NULL;
+       GFile *file;
+       GKeyFile *key_file;
+       gboolean success;
+       gsize length;
+
+       g_return_val_if_fail (uid != NULL, FALSE);
+       g_return_val_if_fail (data != NULL, FALSE);
+
+       length = strlen (data);
+
+       /* Make sure the data is syntactically valid. */
+       key_file = g_key_file_new ();
+       success = g_key_file_load_from_data (
+               key_file, data, length, G_KEY_FILE_NONE, error);
+       g_key_file_free (key_file);
+
+       if (!success)
+               return FALSE;
+
+       /* Check that the given unique identifier really is unique.
+        *
+        * XXX There's a valid case to be made that the server should be
+        *     assigning unique identifiers to new sources to avoid this
+        *     error.  That's fine for standalone sources but makes life
+        *     more difficult for clients creating a set or hierarchy of
+        *     sources that cross reference one another, such for a mail
+        *     account.  Having CLIENTS generate new UIDs means they can
+        *     prepare any cross references in advance, then submit each
+        *     source as is without having to make further modifications
+        *     as would be necessary if using server-assigned UIDs.
+        *
+        *     Anyway, if used properly the odds of a UID collision here
+        *     are slim enough that I think it's a reasonable trade-off.
+        */
+       source = e_source_registry_server_ref_source (server, uid);
+       if (source != NULL) {
+               g_set_error (
+                       error, G_IO_ERROR, G_IO_ERROR_EXISTS,
+                       _("UID '%s' is already in use"), uid);
+               g_object_unref (source);
+               return FALSE;
+       }
+
+       file = e_server_side_source_new_user_file (uid);
+
+       /* Write the data to disk.  The file monitor should eventually
+        * notice the new file and call e_source_registry_server_load_file()
+        * per design, but we're going to beat it to the punch since we
+        * need to return the new D-Bus object path back to the caller.
+        * By the time the file monitor gets around to loading the file,
+        * it will simply get back the EDBusSourceObject we've already
+        * created and exported. */
+
+       success = g_file_replace_contents (
+               file, data, length, NULL, FALSE,
+               G_FILE_CREATE_PRIVATE, NULL, NULL, error);
+
+       if (success) {
+               ESourcePermissionFlags flags;
+
+               /* New sources are always writable + removable. */
+               flags = E_SOURCE_PERMISSION_WRITABLE |
+                       E_SOURCE_PERMISSION_REMOVABLE;
+
+               source = e_source_registry_server_load_file (
+                       server, file, flags, error);
+
+               /* We don't need the returned reference. */
+               if (source != NULL)
+                       g_object_unref (source);
+               else
+                       success = FALSE;
+       }
+
+       g_object_unref (file);
+
+       return success;
+}
+
+static gboolean
+source_registry_server_create_sources_cb (EDBusSourceManager *interface,
+                                          GDBusMethodInvocation *invocation,
+                                          GVariant *array,
+                                          ESourceRegistryServer *server)
+{
+       GVariantIter iter;
+       gchar *uid, *data;
+       GError *error = NULL;
+
+       g_variant_iter_init (&iter, array);
+
+       while (g_variant_iter_next (&iter, "{ss}", &uid, &data)) {
+               source_registry_server_create_source (
+                       server, uid, data, &error);
+
+               g_free (uid);
+               g_free (data);
+
+               if (error != NULL)
+                       break;
+       }
+
+       if (error != NULL)
+               g_dbus_method_invocation_take_error (invocation, error);
+
+       e_dbus_source_manager_complete_create_sources (interface, invocation);
+
+       return TRUE;
+}
+
+static gboolean
+source_registry_server_reload_cb (EDBusSourceManager *interface,
+                                  GDBusMethodInvocation *invocation,
+                                  ESourceRegistryServer *server)
+{
+       e_dbus_server_quit (
+               E_DBUS_SERVER (server),
+               E_DBUS_SERVER_EXIT_RELOAD);
+
+       e_dbus_source_manager_complete_reload (interface, invocation);
+
+       return TRUE;
+}
+
+static void
+source_registry_server_monitor_changed_cb (GFileMonitor *monitor,
+                                           GFile *file,
+                                           GFile *other_file,
+                                           GFileMonitorEvent event_type,
+                                           ESourceRegistryServer *server)
+{
+       if (event_type == G_FILE_MONITOR_EVENT_CREATED) {
+               ESource *source;
+               GError *error = NULL;
+
+               source = e_server_side_source_new (server, file, &error);
+
+               /* Sanity check. */
+               g_return_if_fail (
+                       ((source != NULL) && (error == NULL)) ||
+                       ((source == NULL) && (error != NULL)));
+
+               if (error == NULL) {
+                       e_source_registry_server_add_source (server, source);
+                       g_object_unref (source);
+               } else {
+                       e_source_registry_server_load_error (
+                               server, file, error);
+                       g_error_free (error);
+               }
+       }
+
+       if (event_type == G_FILE_MONITOR_EVENT_DELETED) {
+               ESource *source;
+               gchar *uid;
+
+               uid = e_server_side_source_uid_from_file (file, NULL);
+
+               if (uid == NULL)
+                       return;
+
+               source = e_source_registry_server_ref_source (server, uid);
+
+               g_free (uid);
+
+               if (source == NULL)
+                       return;
+
+               /* If the key file for a non-removable source was
+                * somehow deleted, disregard the event and leave
+                * the source object in memory. */
+               if (e_source_get_removable (source))
+                       e_source_registry_server_remove_source (server, source);
+
+               g_object_unref (source);
+       }
+}
+
+static gboolean
+source_registry_server_traverse_cb (GNode *node,
+                                    GQueue *queue)
+{
+       g_queue_push_tail (queue, g_object_ref (node->data));
+
+       return FALSE;
+}
+
+static void
+source_registry_server_queue_subtree (ESource *source,
+                                      GQueue *queue)
+{
+       GNode *node;
+
+       node = e_server_side_source_get_node (E_SERVER_SIDE_SOURCE (source));
+
+       g_node_traverse (
+               node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+               (GNodeTraverseFunc) source_registry_server_traverse_cb, queue);
+}
+
+static gboolean
+source_registry_server_find_parent (ESourceRegistryServer *server,
+                                    ESource *source)
+{
+       ESource *parent;
+       const gchar *parent_uid;
+
+       /* If the given source references a parent source and the
+        * parent source is not present in the hierarchy, the given
+        * source is added to an orphan table until the referenced
+        * parent is added to the hierarchy. */
+
+       parent_uid = e_source_get_parent (source);
+
+       if (parent_uid == NULL || *parent_uid == '\0')
+               return TRUE;
+
+       parent = g_hash_table_lookup (server->priv->sources, parent_uid);
+
+       if (parent != NULL) {
+               GNode *parent_node;
+               GNode *object_node;
+
+               parent_node = e_server_side_source_get_node (
+                       E_SERVER_SIDE_SOURCE (parent));
+               object_node = e_server_side_source_get_node (
+                       E_SERVER_SIDE_SOURCE (source));
+               g_node_append (parent_node, object_node);
+
+               return TRUE;
+       }
+
+       source_registry_server_orphans_insert (server, source);
+
+       return FALSE;
+}
+
+static void
+source_registry_server_adopt_orphans (ESourceRegistryServer *server,
+                                      ESource *source)
+{
+       GPtrArray *array;
+
+       /* Check if a newly-added source has any orphan sources
+        * that are waiting for it.  The orphans can now be added
+        * to the hierarchy as children of the newly-added source. */
+
+       array = source_registry_server_orphans_steal (server, source);
+
+       if (array != NULL) {
+               guint ii;
+
+               for (ii = 0; ii < array->len; ii++) {
+                       ESource *orphan = array->pdata[ii];
+                       e_source_registry_server_add_source (server, orphan);
+               }
+
+               g_ptr_array_unref (array);
+       }
+}
+
+static void
+source_registry_server_dispose (GObject *object)
+{
+       ESourceRegistryServerPrivate *priv;
+
+       priv = E_SOURCE_REGISTRY_SERVER_GET_PRIVATE (object);
+
+       if (priv->object_manager != NULL) {
+               g_object_unref (priv->object_manager);
+               priv->object_manager = NULL;
+       }
+
+       if (priv->source_manager != NULL) {
+               g_object_unref (priv->source_manager);
+               priv->source_manager = NULL;
+       }
+
+       g_hash_table_remove_all (priv->sources);
+       g_hash_table_remove_all (priv->orphans);
+       g_hash_table_remove_all (priv->monitors);
+
+       g_hash_table_remove_all (priv->auth_table);
+
+       if (priv->auth_cancellable != NULL) {
+               g_object_unref (priv->auth_cancellable);
+               priv->auth_cancellable = NULL;
+       }
+
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_source_registry_server_parent_class)->
+               dispose (object);
+}
+
+static void
+source_registry_server_finalize (GObject *object)
+{
+       ESourceRegistryServerPrivate *priv;
+
+       priv = E_SOURCE_REGISTRY_SERVER_GET_PRIVATE (object);
+
+       g_hash_table_destroy (priv->sources);
+       g_hash_table_destroy (priv->orphans);
+       g_hash_table_destroy (priv->monitors);
+
+       g_mutex_free (priv->sources_lock);
+       g_mutex_free (priv->orphans_lock);
+
+       g_queue_free_full (priv->auth_queue, (GDestroyNotify) g_free);
+       g_hash_table_destroy (priv->auth_table);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_source_registry_server_parent_class)->
+               finalize (object);
+}
+
+static void
+source_registry_server_bus_acquired (EDBusServer *server,
+                                     GDBusConnection *connection)
+{
+       ESourceRegistryServerPrivate *priv;
+       GError *error = NULL;
+
+       priv = E_SOURCE_REGISTRY_SERVER_GET_PRIVATE (server);
+
+       g_dbus_object_manager_server_set_connection (
+               priv->object_manager, connection);
+
+       g_dbus_interface_skeleton_export (
+               G_DBUS_INTERFACE_SKELETON (priv->source_manager),
+               connection, E_SOURCE_REGISTRY_SERVER_OBJECT_PATH, &error);
+
+       /* Terminate the server if we can't export the interface. */
+       if (error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               e_dbus_server_quit (server, E_DBUS_SERVER_EXIT_NORMAL);
+               g_error_free (error);
+       }
+
+       /* Chain up to parent's bus_acquired() method. */
+       E_DBUS_SERVER_CLASS (e_source_registry_server_parent_class)->
+               bus_acquired (server, connection);
+}
+
+static void
+source_registry_server_quit_server (EDBusServer *server,
+                                    EDBusServerExitCode code)
+{
+       ESourceRegistryServerPrivate *priv;
+
+       priv = E_SOURCE_REGISTRY_SERVER_GET_PRIVATE (server);
+
+       /* Cancel any active authentication session. */
+       g_cancellable_cancel (priv->auth_cancellable);
+
+       /* This makes the object manager unexport all objects. */
+       g_dbus_object_manager_server_set_connection (
+               priv->object_manager, NULL);
+
+       g_dbus_interface_skeleton_unexport (
+               G_DBUS_INTERFACE_SKELETON (priv->source_manager));
+
+       /* Chain up to parent's quit_server() method. */
+       E_DBUS_SERVER_CLASS (e_source_registry_server_parent_class)->
+               quit_server (server, code);
+}
+
+static void
+source_registry_server_source_added (ESourceRegistryServer *server,
+                                     ESource *source)
+{
+       GDBusObject *dbus_object;
+       GDBusObject *g_dbus_object;
+       const gchar *uid;
+       const gchar *object_name;
+       const gchar *object_path;
+       const gchar *extension_name;
+
+       /* Instantiate an ECollectionBackend if appropriate.
+        *
+        * Do this BEFORE exporting so backends have a chance
+        * to make any last-minute tweaks to the data source. */
+
+       extension_name = E_SOURCE_EXTENSION_COLLECTION;
+       if (e_source_has_extension (source, extension_name)) {
+               EBackend *backend;
+               ESourceBackend *extension;
+               const gchar *backend_name;
+
+               extension = e_source_get_extension (source, extension_name);
+               backend_name = e_source_backend_get_backend_name (extension);
+
+               /* For convenience, we attach the EBackend to the ESource
+                * itself, which creates a reference cycle.  The cycle is
+                * explicitly broken when the ESource is removed from the
+                * 'sources' hash table (see unref_data_source() above). */
+               backend = e_data_factory_ref_backend (
+                       E_DATA_FACTORY (server), backend_name, source);
+               if (backend != NULL) {
+                       g_object_set_data_full (
+                               G_OBJECT (source),
+                               BACKEND_DATA_KEY, backend,
+                               (GDestroyNotify) g_object_unref);
+               } else {
+                       g_warning (
+                               "No collection backend '%s' for %s",
+                               backend_name, e_source_get_uid (source));
+               }
+       }
+
+       /* Export the data source to clients over D-Bus. */
+
+       dbus_object = e_source_ref_dbus_object (source);
+
+       g_dbus_object_manager_server_export_uniquely (
+               server->priv->object_manager,
+               G_DBUS_OBJECT_SKELETON (dbus_object));
+
+       uid = e_source_get_uid (source);
+
+       g_dbus_object = G_DBUS_OBJECT (dbus_object);
+       object_path = g_dbus_object_get_object_path (g_dbus_object);
+       object_name = strrchr (object_path, '/') + 1;
+
+       g_print ("Adding %s ('%s')\n", uid, object_name);
+
+       g_object_unref (dbus_object);
+}
+
+static void
+source_registry_server_source_removed (ESourceRegistryServer *server,
+                                       ESource *source)
+{
+       GDBusObject *dbus_object;
+       const gchar *uid;
+       const gchar *object_name;
+       const gchar *object_path;
+
+       uid = e_source_get_uid (source);
+
+       dbus_object = e_source_ref_dbus_object (source);
+
+       object_path = g_dbus_object_get_object_path (dbus_object);
+       object_name = strrchr (object_path, '/') + 1;
+
+       g_print ("Removing %s ('%s')\n", uid, object_name);
+
+       g_dbus_object_manager_server_unexport (
+               server->priv->object_manager, object_path);
+
+       g_object_unref (dbus_object);
+}
+
+static void
+e_source_registry_server_class_init (ESourceRegistryServerClass *class)
+{
+       GObjectClass *object_class;
+       EDBusServerClass *dbus_server_class;
+       EDataFactoryClass *data_factory_class;
+       GType backend_factory_type;
+
+       g_type_class_add_private (class, sizeof (ESourceRegistryServerPrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->dispose = source_registry_server_dispose;
+       object_class->finalize = source_registry_server_finalize;
+
+       dbus_server_class = E_DBUS_SERVER_CLASS (class);
+       dbus_server_class->bus_name = SOURCES_DBUS_SERVICE_NAME;
+       dbus_server_class->module_directory = MODULE_DIRECTORY;
+       dbus_server_class->bus_acquired = source_registry_server_bus_acquired;
+       dbus_server_class->quit_server = source_registry_server_quit_server;
+
+       data_factory_class = E_DATA_FACTORY_CLASS (class);
+       backend_factory_type = E_TYPE_COLLECTION_BACKEND_FACTORY;
+       data_factory_class->backend_factory_type = backend_factory_type;
+
+       class->source_added = source_registry_server_source_added;
+       class->source_removed = source_registry_server_source_removed;
+
+       /**
+        * ESourceRegistryServer::load-error:
+        * @server: the #ESourceRegistryServer which emitted the signal
+        * @file: the #GFile being loaded
+        * @error: a #GError describing the error
+        *
+        * Emitted when an error occurs while loading or parsing a
+        * data source key file.
+        **/
+       signals[LOAD_ERROR] = g_signal_new (
+               "load-error",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (ESourceRegistryServerClass, load_error),
+               NULL, NULL,
+               e_marshal_VOID__OBJECT_BOXED,
+               G_TYPE_NONE, 2,
+               G_TYPE_FILE,
+               G_TYPE_ERROR | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+       /**
+        * ESourceRegistryServer::files-loaded:
+        * @server: the #ESourceRegistryServer which emitted the signal
+        *
+        * Emitted after all data source key files are loaded on startup.
+        * Extensions can connect to this signal to perform any additional
+        * work prior to running the main loop.
+        **/
+       signals[FILES_LOADED] = g_signal_new (
+               "files-loaded",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (ESourceRegistryServerClass, files_loaded),
+               NULL, NULL,
+               g_cclosure_marshal_VOID__VOID,
+               G_TYPE_NONE, 0);
+
+       /**
+        * ESourceRegistryServer::source-added:
+        * @server: the #ESourceRegistryServer which emitted the signal
+        * @source: the newly-added #EServerSideSource
+        *
+        * Emitted when an #EServerSideSource is added to @server.
+        **/
+       signals[SOURCE_ADDED] = g_signal_new (
+               "source-added",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (ESourceRegistryServerClass, source_added),
+               NULL, NULL,
+               g_cclosure_marshal_VOID__OBJECT,
+               G_TYPE_NONE, 1,
+               E_TYPE_SERVER_SIDE_SOURCE);
+
+       /**
+        * ESourceRegistryServer::source-removed:
+        * @server: the #ESourceRegistryServer when emitted the signal
+        * @source: the #EServerSideSource that got removed
+        *
+        * Emitted when an #EServerSideSource is removed from @server.
+        **/
+       signals[SOURCE_REMOVED] = g_signal_new (
+               "source-removed",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (ESourceRegistryServerClass, source_removed),
+               NULL, NULL,
+               g_cclosure_marshal_VOID__OBJECT,
+               G_TYPE_NONE, 1,
+               E_TYPE_SERVER_SIDE_SOURCE);
+}
+
+static void
+e_source_registry_server_init (ESourceRegistryServer *server)
+{
+       GDBusObjectManagerServer *object_manager;
+       EDBusSourceManager *source_manager;
+       GHashTable *sources;
+       GHashTable *orphans;
+       GHashTable *monitors;
+       GHashTable *auth_table;
+       const gchar *object_path;
+
+       object_path = E_SOURCE_REGISTRY_SERVER_OBJECT_PATH;
+       object_manager = g_dbus_object_manager_server_new (object_path);
+       source_manager = e_dbus_source_manager_skeleton_new ();
+
+       /* UID string -> ESource */
+       sources = g_hash_table_new_full (
+               (GHashFunc) g_str_hash,
+               (GEqualFunc) g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) unref_data_source);
+
+       /* Parent UID string -> GPtrArray of ESources */
+       orphans = g_hash_table_new_full (
+               (GHashFunc) g_str_hash,
+               (GEqualFunc) g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) g_ptr_array_unref);
+
+       /* GFile -> GFileMonitor */
+       monitors = g_hash_table_new_full (
+               (GHashFunc) g_file_hash,
+               (GEqualFunc) g_file_equal,
+               (GDestroyNotify) g_object_unref,
+               (GDestroyNotify) g_object_unref);
+
+       auth_table = g_hash_table_new_full (
+               (GHashFunc) g_str_hash,
+               (GEqualFunc) g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) free_auth_queue);
+
+       server->priv = E_SOURCE_REGISTRY_SERVER_GET_PRIVATE (server);
+       server->priv->object_manager = object_manager;
+       server->priv->source_manager = source_manager;
+       server->priv->sources = sources;
+       server->priv->orphans = orphans;
+       server->priv->monitors = monitors;
+       server->priv->sources_lock = g_mutex_new ();
+       server->priv->orphans_lock = g_mutex_new ();
+       server->priv->auth_queue = g_queue_new ();
+       server->priv->auth_table = auth_table;
+       server->priv->auth_cancellable = g_cancellable_new ();
+
+       g_signal_connect (
+               source_manager, "handle-authenticate",
+               G_CALLBACK (source_registry_server_authenticate_cb),
+               server);
+
+       g_signal_connect (
+               source_manager, "handle-create-sources",
+               G_CALLBACK (source_registry_server_create_sources_cb),
+               server);
+
+       g_signal_connect (
+               source_manager, "handle-reload",
+               G_CALLBACK (source_registry_server_reload_cb),
+               server);
+}
+
+/**
+ * e_source_registry_server_new:
+ *
+ * Creates a new instance of #ESourceRegistryServer.
+ *
+ * Returns: a new instance of #ESourceRegistryServer
+ *
+ * Since: 3.6
+ **/
+EDBusServer *
+e_source_registry_server_new (void)
+{
+       return g_object_new (E_TYPE_SOURCE_REGISTRY_SERVER, NULL);
+}
+
+/**
+ * e_source_registry_server_add_source:
+ * @server: an #ESourceRegistryServer
+ * @source: an #ESource
+ *
+ * Adds @source to @server.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_registry_server_add_source (ESourceRegistryServer *server,
+                                     ESource *source)
+{
+       GDBusObject *dbus_object;
+       EDBusSource *dbus_source;
+       const gchar *extension_name;
+       const gchar *uid;
+       gchar *data;
+
+       g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+       g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+       uid = e_source_get_uid (source);
+       g_return_if_fail (uid != NULL);
+
+       g_mutex_lock (server->priv->sources_lock);
+
+       /* Check if we already have this object in the hierarchy. */
+       if (g_hash_table_lookup (server->priv->sources, uid) != NULL) {
+               g_mutex_unlock (server->priv->sources_lock);
+               return;
+       }
+
+       /* Make sure the parent object (if any) is in the hierarchy. */
+       if (!source_registry_server_find_parent (server, source)) {
+               g_mutex_unlock (server->priv->sources_lock);
+               return;
+       }
+
+       g_mutex_unlock (server->priv->sources_lock);
+
+       /* Before we emit, make sure the EDBusSource's "data" property
+        * is up-to-date.  ESource changes get propagated to the "data"
+        * property from an idle callback, which may still be pending. */
+
+       dbus_object = e_source_ref_dbus_object (source);
+       dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
+
+       data = e_source_to_string (source, NULL);
+       e_dbus_source_set_data (dbus_source, data);
+       g_free (data);
+
+       g_object_unref (dbus_source);
+       g_object_unref (dbus_object);
+
+       /* If the added source has a [Collection] extension but the
+        * corresponding ECollectionBackendFactory is not available,
+        * the source gets permanently inserted in the orphans table
+        * to prevent it from being exported to client applications. */
+
+       extension_name = E_SOURCE_EXTENSION_COLLECTION;
+       if (e_source_has_extension (source, extension_name)) {
+               ECollectionBackendFactory *backend_factory;
+
+               backend_factory =
+                       e_source_registry_server_ref_backend_factory (
+                               server, source);
+               if (backend_factory == NULL) {
+                       source_registry_server_orphans_insert (server, source);
+                       return;
+               }
+               g_object_unref (backend_factory);
+       }
+
+       source_registry_server_sources_insert (server, source);
+
+       g_signal_emit (server, signals[SOURCE_ADDED], 0, source);
+
+       /* Adopt any orphans that have been waiting for this object. */
+       source_registry_server_adopt_orphans (server, source);
+}
+
+/* Helper for e_source_registry_server_remove_object() */
+static void
+source_registry_server_remove_object (ESourceRegistryServer *server,
+                                      ESource *source)
+{
+       g_object_ref (source);
+
+       if (source_registry_server_sources_remove (server, source)) {
+               EServerSideSource *ss_source;
+
+               ss_source = E_SERVER_SIDE_SOURCE (source);
+               source_registry_server_orphans_insert (server, source);
+               g_node_unlink (e_server_side_source_get_node (ss_source));
+               g_signal_emit (server, signals[SOURCE_REMOVED], 0, source);
+       }
+
+       g_object_unref (source);
+}
+
+/**
+ * e_source_registry_server_remove_source:
+ * @server: an #ESourceRegistryServer
+ * @source: an #ESource
+ *
+ * Removes @source and all of its descendants from @server.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_registry_server_remove_source (ESourceRegistryServer *server,
+                                        ESource *source)
+{
+       ESource *child;
+       ESource *exported;
+       GQueue queue = G_QUEUE_INIT;
+       const gchar *uid;
+
+       g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+       g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+       uid = e_source_get_uid (source);
+
+       /* If the removed source is in the server hierarchy, gather
+        * it and all of its descendants into a queue in "post-order"
+        * so we're always processing leaf nodes as we pop sources off
+        * the head of the queue. */
+       exported = e_source_registry_server_ref_source (server, uid);
+       if (exported != NULL) {
+               source_registry_server_queue_subtree (source, &queue);
+               g_object_unref (exported);
+       }
+
+       /* Move the queued descendants to the orphan table, and emit a
+        * "source-removed" signal for each source.  This will include
+        * the removed source unless the source was already an orphan,
+        * in which case the queue will be empty. */
+       while ((child = g_queue_pop_head (&queue)) != NULL) {
+               source_registry_server_remove_object (server, child);
+               g_object_unref (child);
+       }
+
+       /* The removed source should be in the orphan table now. */
+       source_registry_server_orphans_remove (server, source);
+}
+
+/**
+ * e_source_registry_server_queue_auth_session:
+ * @server: an #ESourceRegistryServer
+ * @session: an #EDBusSourceAuthenticator
+ *
+ * Queues an authentication session.  When its turn comes, and if necessary,
+ * the user will be prompted for a secret.  Sessions are queued this way to
+ * prevent user prompts from piling up on the screen.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_registry_server_queue_auth_session (ESourceRegistryServer *server,
+                                             EAuthenticationSession *session)
+{
+       const gchar *uid;
+       GQueue *queue;
+
+       g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+       g_return_if_fail (E_IS_AUTHENTICATION_SESSION (session));
+
+       uid = e_authentication_session_get_source_uid (session);
+       g_return_if_fail (uid != NULL);
+
+       /* Add the session to the appropriate queue. */
+       queue = source_registry_server_auth_table_lookup (server, uid);
+       g_queue_push_tail (queue, g_object_ref (session));
+
+       /* Blindly push the UID onto the processing queue. */
+       g_queue_push_tail (server->priv->auth_queue, g_strdup (uid));
+
+       source_registry_server_maybe_start_auth_session (server);
+}
+
+/**
+ * e_source_registry_server_load_all:
+ * @server: an #ESourceRegistryServer
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads data source key files from standard system-wide and user-specific
+ * locations.  Because multiple errors can occur when loading multiple files,
+ * @error is only set if a directory can not be opened.  If a data source key
+ * file fails to load, the error is broadcast through the
+ * #ESourceRegistryServer::load-error signal.
+ *
+ * Returns: %TRUE if the standard directories were successfully opened,
+ *          but this does not imply the key files were successfully loaded
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_source_registry_server_load_all (ESourceRegistryServer *server,
+                                   GError **error)
+{
+       ESourcePermissionFlags flags;
+       const gchar *directory;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), FALSE);
+
+       /* Load the user's sources directory first so that user-specific
+        * data sources overshadow predefined data sources with identical
+        * UIDs.  The 'local' data source is one such example. */
+
+       directory = e_server_side_source_get_user_dir ();
+       flags = E_SOURCE_PERMISSION_REMOVABLE |
+               E_SOURCE_PERMISSION_WRITABLE;
+       success = e_source_registry_server_load_directory (
+               server, directory, flags, error);
+       g_prefix_error (error, "%s: ", directory);
+
+       if (!success)
+               return FALSE;
+
+       directory = SYSTEM_WIDE_RO_SOURCES_DIRECTORY;
+       flags = E_SOURCE_PERMISSION_NONE;
+       success = e_source_registry_server_load_directory (
+               server, directory, flags, error);
+       g_prefix_error (error, "%s: ", directory);
+
+       if (!success)
+               return FALSE;
+
+       directory = SYSTEM_WIDE_RW_SOURCES_DIRECTORY;
+       flags = E_SOURCE_PERMISSION_WRITABLE;
+       success = e_source_registry_server_load_directory (
+               server, directory, flags, error);
+       g_prefix_error (error, "%s: ", directory);
+
+       if (!success)
+               return FALSE;
+
+       /* Signal that all files are now loaded. */
+       g_signal_emit (server, signals[FILES_LOADED], 0);
+
+       return TRUE;
+}
+
+/**
+ * e_source_registry_server_load_directory:
+ * @server: an #ESourceRegistryServer
+ * @path: the path to the directory to load
+ * @flags: permission flags for files loaded from @path
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads data source key files in @path.  Because multiple errors can
+ * occur when loading multiple files, @error is only set if @path can
+ * not be opened.  If a key file fails to load, the error is broadcast
+ * through the #ESourceRegistryServer::load-error signal.
+ *
+ * If the #E_DBUS_LOAD_DIRECTORY_REMOVABLE flag is given, then the @server
+ * will emit signals on the D-Bus interface when key files are created or
+ * deleted in @path.
+ *
+ * Returns: %TRUE if @path was successfully opened, but this
+ *          does not imply the key files were successfully loaded
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_source_registry_server_load_directory (ESourceRegistryServer *server,
+                                         const gchar *path,
+                                         ESourcePermissionFlags flags,
+                                         GError **error)
+{
+       GDir *dir;
+       GFile *file;
+       const gchar *name;
+       gboolean removable;
+
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), FALSE);
+       g_return_val_if_fail (path != NULL, FALSE);
+
+       removable = ((flags & E_SOURCE_PERMISSION_REMOVABLE) != 0);
+
+       /* If the directory doesn't exist then there's nothing to load.
+        * Note we do not use G_FILE_TEST_DIR here.  If the given path
+        * exists but is not a directory then we let g_dir_open() fail. */
+       if (!g_file_test (path, G_FILE_TEST_EXISTS))
+               return TRUE;
+
+       dir = g_dir_open (path, 0, error);
+       if (dir == NULL)
+               return FALSE;
+
+       file = g_file_new_for_path (path);
+
+       while ((name = g_dir_read_name (dir)) != NULL) {
+               ESource *source;
+               GFile *child;
+               GError *local_error = NULL;
+
+               /* Ignore files with no ".source" suffix. */
+               if (!g_str_has_suffix (name, ".source"))
+                       continue;
+
+               child = g_file_get_child (file, name);
+
+               source = e_source_registry_server_load_file (
+                       server, child, flags, &local_error);
+
+               /* We don't need the returned reference. */
+               if (source != NULL)
+                       g_object_unref (source);
+
+               if (local_error != NULL) {
+                       e_source_registry_server_load_error (
+                               server, child, local_error);
+                       g_error_free (local_error);
+               }
+
+               g_object_unref (child);
+       }
+
+       g_dir_close (dir);
+
+       /* Only data source files in the user's
+        * sources directory should be removable. */
+       if (removable) {
+               GFileMonitor *monitor;
+
+               monitor = g_file_monitor_directory (
+                       file, G_FILE_MONITOR_NONE, NULL, error);
+               if (monitor == NULL)
+                       return FALSE;
+
+               g_signal_connect (
+                       monitor, "changed",
+                       G_CALLBACK (source_registry_server_monitor_changed_cb),
+                       server);
+
+               g_hash_table_insert (
+                       server->priv->monitors,
+                       g_object_ref (file), monitor);
+       }
+
+       g_object_unref (file);
+
+       return TRUE;
+}
+
+/**
+ * e_source_registry_server_load_file:
+ * @server: an #ESourceRegistryServer
+ * @file: the data source key file to load
+ * @flags: initial permission flags for the data source
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates an #ESource for a native key file and adds it to @server.
+ * If an error occurs, the function returns %NULL and sets @error.
+ *
+ * The returned #ESource is referenced for thread-safety.  Unreference
+ * the #ESource with g_object_unref() when finished with it.
+ *
+ * Returns: the newly-added #ESource, or %NULL on error
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_source_registry_server_load_file (ESourceRegistryServer *server,
+                                    GFile *file,
+                                    ESourcePermissionFlags flags,
+                                    GError **error)
+{
+       ESource *source;
+       gboolean writable;
+       gboolean removable;
+       gchar *uid;
+
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+       writable = ((flags & E_SOURCE_PERMISSION_WRITABLE) != 0);
+       removable = ((flags & E_SOURCE_PERMISSION_REMOVABLE) != 0);
+
+       uid = e_server_side_source_uid_from_file (file, error);
+
+       if (uid == NULL)
+               return NULL;
+
+       /* Check if we already have this file loaded. */
+       source = e_source_registry_server_ref_source (server, uid);
+
+       g_free (uid);
+
+       if (source == NULL)
+               source = e_server_side_source_new (server, file, error);
+
+       if (source == NULL)
+               return NULL;
+
+       /* Set the data source's initial permissions, which
+        * determines which D-Bus methods it exports: write()
+        * if writable, remove() if removable.  We apply these
+        * before adding the source to the server because some
+        * "source-added" signal handlers may wish to override
+        * the initial permissions.
+        *
+        * Note that we apply the initial permission flags even
+        * if the data source has already been loaded.  That is
+        * intentional.  That is why the load_all() function loads
+        * the user directory before loading system-wide directories.
+        * If there's a UID collision between a data source in the
+        * user's directory and a data source in a system-wide
+        * directory, the permission flags for the system-wide
+        * directory should win.
+        *
+        * Consider an example:
+        *
+        * The built-in 'local' data source should always be
+        * writable but not removable.
+        *
+        * Suppose the user temporarily disables the 'local'
+        * data source.  The altered 'local' data source file
+        * (with Enabled=false) is saved in the user's sources
+        * directory.
+        *
+        * On the next startup, the altered 'local' file is
+        * first loaded from the user's source directory and
+        * given removable + writable permissions.
+        *
+        * We then load data sources from the 'rw-sources'
+        * system directory containing the unaltered 'local'
+        * file (with Enabled=true), which is not removable.
+        *
+        * We keep the contents of the altered 'local' file
+        * (Enabled=false), but override its permissions to
+        * just be writable, not removable.
+        */
+       e_server_side_source_set_writable (
+               E_SERVER_SIDE_SOURCE (source), writable);
+       e_server_side_source_set_removable (
+               E_SERVER_SIDE_SOURCE (source), removable);
+
+       /* This does nothing if the source is already added. */
+       e_source_registry_server_add_source (server, source);
+
+       return source;
+}
+
+/**
+ * e_source_registry_server_load_error:
+ * @server: an #EBusSourceServer
+ * @file: the #GFile that failed to load
+ * @error: a #GError describing the load error
+ *
+ * Emits the #ESourceRegistryServer::load-error signal.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_registry_server_load_error (ESourceRegistryServer *server,
+                                     GFile *file,
+                                     const GError *error)
+{
+       g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+       g_return_if_fail (G_IS_FILE (file));
+       g_return_if_fail (error != NULL);
+
+       g_signal_emit (server, signals[LOAD_ERROR], 0, file, error);
+}
+
+/**
+ * e_source_registry_server_ref_source:
+ * @server: an #ESourceRegistryServer
+ * @uid: a unique identifier string
+ *
+ * Looks up an #ESource in @server by its unique identifier string.
+ *
+ * The returned #ESource is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: an #ESource, or %NULL if no match was found
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_source_registry_server_ref_source (ESourceRegistryServer *server,
+                                     const gchar *uid)
+{
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       return source_registry_server_sources_lookup (server, uid);
+}
+
+/**
+ * e_source_registry_server_list_sources:
+ * @server: an #ESourceRegistryServer
+ * @extension_name: an extension name, or %NULL
+ *
+ * Returns a list of registered sources, sorted by display name.  If
+ * @extension_name is given, restrict the list to sources having that
+ * extension name.
+ *
+ * The sources returned in the list are referenced for thread-safety.
+ * They must each be unreferenced with g_object_unref() when finished
+ * with them.  Free the returned #GList itself with g_list_free().
+ *
+ * An easy way to free the list properly in one step is as follows:
+ *
+ * |[
+ *   g_list_free_full (list, g_object_unref);
+ * ]|
+ *
+ * Returns: a sorted list of sources
+ *
+ * Since: 3.6
+ **/
+GList *
+e_source_registry_server_list_sources (ESourceRegistryServer *server,
+                                       const gchar *extension_name)
+{
+       GList *list, *link;
+       GQueue trash = G_QUEUE_INIT;
+
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
+
+       list = g_list_sort (
+               source_registry_server_sources_get_values (server),
+               (GCompareFunc) e_source_compare_by_display_name);
+
+       if (extension_name == NULL)
+               return list;
+
+       for (link = list; link != NULL; link = g_list_next (link)) {
+               ESource *source = E_SOURCE (link->data);
+
+               if (!e_source_has_extension (source, extension_name)) {
+                       g_queue_push_tail (&trash, link);
+                       g_object_unref (source);
+               }
+       }
+
+       /* We do want pop_head() here, not pop_head_link(). */
+       while ((link = g_queue_pop_head (&trash)) != NULL)
+               list = g_list_delete_link (list, link);
+
+       return list;
+}
+
+/**
+ * e_source_registry_server_ref_backend_factory:
+ * @server: an #ESourceRegistryServer
+ * @source: an #ESource
+ *
+ * Returns the #ECollectionBackendFactory for @source, if available.
+ * If @source does not have an #ESourceCollection extension, or if the
+ * #ESourceCollection extension names a #ESourceBackend:backend-name for
+ * which there is no corresponding #ECollectionBackendFactory, the function
+ * returns %NULL.
+ *
+ * The returned #ECollectionBackendFactory is referenced for thread-safety.
+ * Unreference the #ECollectionBackendFactory with g_object_unref() when
+ * finished with it.
+ *
+ * Returns: the #ECollectionBackendFactory for @source, or %NULL
+ *
+ * Since: 3.6
+ **/
+ECollectionBackendFactory *
+e_source_registry_server_ref_backend_factory (ESourceRegistryServer *server,
+                                              ESource *source)
+{
+       EBackendFactory *factory;
+       ESourceBackend *extension;
+       const gchar *backend_name;
+       const gchar *extension_name;
+
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
+       g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+       /* XXX Should we also check ancestor sources for a collection
+        *     extension so this function works for ANY source in the
+        *     collection?  Gonna refrain til a real use case emerges
+        *     but it's something to keep in mind. */
+
+       extension_name = E_SOURCE_EXTENSION_COLLECTION;
+       if (!e_source_has_extension (source, extension_name))
+               return NULL;
+
+       extension = e_source_get_extension (source, extension_name);
+       backend_name = e_source_backend_get_backend_name (extension);
+
+       factory = e_data_factory_ref_backend_factory (
+               E_DATA_FACTORY (server), backend_name);
+
+       if (factory == NULL)
+               return NULL;
+
+       /* The factory *should* be an ECollectionBackendFactory.
+        * We specify this in source_registry_server_class_init(). */
+       return E_COLLECTION_BACKEND_FACTORY (factory);
+}
+
diff --git a/libebackend/e-source-registry-server.h b/libebackend/e-source-registry-server.h
new file mode 100644 (file)
index 0000000..9568e5e
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * e-source-registry-server.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_SOURCE_REGISTRY_SERVER_H
+#define E_SOURCE_REGISTRY_SERVER_H
+
+#include <libebackend/e-authentication-session.h>
+#include <libebackend/e-backend-enums.h>
+#include <libebackend/e-data-factory.h>
+#include <libebackend/e-collection-backend-factory.h>
+#include <libedataserver/e-source.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_REGISTRY_SERVER \
+       (e_source_registry_server_get_type ())
+#define E_SOURCE_REGISTRY_SERVER(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_SOURCE_REGISTRY_SERVER, ESourceRegistryServer))
+#define E_SOURCE_REGISTRY_SERVER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_SOURCE_REGISTRY_SERVER, ESourceRegistryServerClass))
+#define E_IS_SOURCE_REGISTRY_SERVER(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_SOURCE_REGISTRY_SERVER))
+#define E_IS_SOURCE_REGISTRY_SERVER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_SOURCE_REGISTRY_SERVER))
+#define E_SOURCE_REGISTRY_SERVER_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_SOURCE_REGISTRY_SERVER, ESourceRegistryServerClass))
+
+/**
+ * E_SOURCE_REGISTRY_SERVER_OBJECT_PATH:
+ *
+ * D-Bus object path of the data source server.
+ *
+ * Since: 3.6
+ **/
+#define E_SOURCE_REGISTRY_SERVER_OBJECT_PATH \
+       "/org/gnome/evolution/dataserver/SourceManager"
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceRegistryServer ESourceRegistryServer;
+typedef struct _ESourceRegistryServerClass ESourceRegistryServerClass;
+typedef struct _ESourceRegistryServerPrivate ESourceRegistryServerPrivate;
+
+/**
+ * ESourceRegistryServer:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.6
+ **/
+struct _ESourceRegistryServer {
+       EDataFactory parent;
+       ESourceRegistryServerPrivate *priv;
+};
+
+struct _ESourceRegistryServerClass {
+       EDataFactoryClass parent_class;
+
+       /* Signals */
+       void            (*load_error)           (ESourceRegistryServer *server,
+                                                GFile *file,
+                                                const GError *error);
+       void            (*files_loaded)         (ESourceRegistryServer *server);
+       void            (*source_added)         (ESourceRegistryServer *server,
+                                                ESource *source);
+       void            (*source_removed)       (ESourceRegistryServer *server,
+                                                ESource *source);
+};
+
+GType          e_source_registry_server_get_type
+                                               (void) G_GNUC_CONST;
+EDBusServer *  e_source_registry_server_new    (void);
+void           e_source_registry_server_add_source
+                                               (ESourceRegistryServer *server,
+                                                ESource *source);
+void           e_source_registry_server_remove_source
+                                               (ESourceRegistryServer *server,
+                                                ESource *source);
+void           e_source_registry_server_queue_auth_session
+                                               (ESourceRegistryServer *server,
+                                                EAuthenticationSession *session);
+gboolean       e_source_registry_server_load_all
+                                               (ESourceRegistryServer *server,
+                                                GError **error);
+gboolean       e_source_registry_server_load_directory
+                                               (ESourceRegistryServer *server,
+                                                const gchar *path,
+                                                ESourcePermissionFlags flags,
+                                                GError **error);
+ESource *      e_source_registry_server_load_file
+                                               (ESourceRegistryServer *server,
+                                                GFile *file,
+                                                ESourcePermissionFlags flags,
+                                                GError **error);
+void           e_source_registry_server_load_error
+                                               (ESourceRegistryServer *server,
+                                                GFile *file,
+                                                const GError *error);
+ESource *      e_source_registry_server_ref_source
+                                               (ESourceRegistryServer *server,
+                                                const gchar *uid);
+GList *                e_source_registry_server_list_sources
+                                               (ESourceRegistryServer *server,
+                                                const gchar *extension_name);
+ECollectionBackendFactory *
+               e_source_registry_server_ref_backend_factory
+                                               (ESourceRegistryServer *server,
+                                                ESource *source);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_REGISTRY_SERVER_H */
index bb661ea..931b931 100644 (file)
@@ -6,6 +6,7 @@ datarootdir=@datarootdir@
 datadir=@datadir@
 
 privincludedir=@privincludedir@
+moduledir=@moduledir@
 
 Name: libebackend
 Description: Utility library for Evolution Data Server Backends
index 1750bfa..551a440 100644 (file)
@@ -3,6 +3,7 @@ NULL =
 SUBDIRS = \
        evolution-addressbook-factory \
        evolution-calendar-factory \
+       evolution-source-registry \
        $(NULL)
 
 -include $(top_srcdir)/git.mk
index 0d99ff7..beafb50 100644 (file)
@@ -19,15 +19,16 @@ evolution_addressbook_factory_CPPFLAGS = \
        -I$(top_builddir) \
        -I$(top_builddir)/addressbook \
        $(EVOLUTION_ADDRESSBOOK_CFLAGS) \
+       $(GNOME_KEYRING_CFLAGS) \
        $(FACTORY_GTK_CFLAGS) \
        $(DBUS_GLIB_CFLAGS) \
+       $(SOUP_CFLAGS) \
        $(GOA_CFLAGS) \
        $(CODE_COVERAGE_CFLAGS) \
        $(NULL)
 
 evolution_addressbook_factory_SOURCES = \
        evolution-addressbook-factory.c \
-       evolution-addressbook-factory-migrate-basedir.c \
        $(NULL)
 
 evolution_addressbook_factory_LDADD = \
@@ -36,8 +37,10 @@ evolution_addressbook_factory_LDADD = \
        $(top_builddir)/libebackend/libebackend-1.2.la \
        $(top_builddir)/libedataserver/libedataserver-1.2.la \
        $(EVOLUTION_ADDRESSBOOK_LIBS) \
+       $(GNOME_KEYRING_LIBS) \
        $(FACTORY_GTK_LIBS) \
        $(DBUS_GLIB_LIBS) \
+       $(SOUP_LIBS) \
        $(GOA_LIBS) \
        $(NULL)
 
diff --git a/services/evolution-addressbook-factory/evolution-addressbook-factory-migrate-basedir.c b/services/evolution-addressbook-factory/evolution-addressbook-factory-migrate-basedir.c
deleted file mode 100644 (file)
index e8ed74c..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * evolution-addressbook-factory-migrate-basedir.c
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with the program; if not, see <http://www.gnu.org/licenses/>
- *
- */
-
-#include <errno.h>
-#include <glib/gstdio.h>
-#include <libedataserver/e-data-server-util.h>
-
-/* Forward Declarations */
-void evolution_addressbook_factory_migrate_basedir (void);
-
-static gboolean
-migrate_rename (const gchar *old_filename,
-                const gchar *new_filename)
-{
-       gboolean old_filename_is_dir;
-       gboolean old_filename_exists;
-       gboolean new_filename_exists;
-       gboolean success = TRUE;
-
-       old_filename_is_dir = g_file_test (old_filename, G_FILE_TEST_IS_DIR);
-       old_filename_exists = g_file_test (old_filename, G_FILE_TEST_EXISTS);
-       new_filename_exists = g_file_test (new_filename, G_FILE_TEST_EXISTS);
-
-       if (!old_filename_exists)
-               return TRUE;
-
-       g_print ("  mv %s %s\n", old_filename, new_filename);
-
-       /* It's safe to go ahead and move directories because rename()
-        * will fail if the new directory already exists with content.
-        * With regular files we have to be careful not to overwrite
-        * new files with old files. */
-       if (old_filename_is_dir || !new_filename_exists) {
-               if (g_rename (old_filename, new_filename) < 0) {
-                       g_printerr ("  FAILED: %s\n", g_strerror (errno));
-                       success = FALSE;
-               }
-       } else {
-               g_printerr ("  FAILED: Destination file already exists\n");
-               success = FALSE;
-       }
-
-       return success;
-}
-
-static gboolean
-migrate_rmdir (const gchar *dirname)
-{
-       GDir *dir = NULL;
-       gboolean success = TRUE;
-
-       if (g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
-               g_print ("  rmdir %s\n", dirname);
-               if (g_rmdir (dirname) < 0) {
-                       g_printerr ("  FAILED: %s", g_strerror (errno));
-                       if (errno == ENOTEMPTY) {
-                               dir = g_dir_open (dirname, 0, NULL);
-                               g_printerr (" (contents follows)");
-                       }
-                       g_printerr ("\n");
-                       success = FALSE;
-               }
-       }
-
-       /* List the directory's contents to aid debugging. */
-       if (dir != NULL) {
-               const gchar *basename;
-
-               /* Align the filenames beneath the error message. */
-               while ((basename = g_dir_read_name (dir)) != NULL)
-                       g_print ("          %s\n", basename);
-
-               g_dir_close (dir);
-       }
-
-       return success;
-}
-
-static void
-migrate_process_corrections (GHashTable *corrections)
-{
-       GHashTableIter iter;
-       gpointer old_filename;
-       gpointer new_filename;
-
-       g_hash_table_iter_init (&iter, corrections);
-
-       while (g_hash_table_iter_next (&iter, &old_filename, &new_filename)) {
-               migrate_rename (old_filename, new_filename);
-               g_hash_table_iter_remove (&iter);
-       }
-}
-
-static gboolean
-migrate_move_contents (const gchar *src_directory,
-                       const gchar *dst_directory)
-{
-       GDir *dir;
-       GHashTable *corrections;
-       const gchar *basename;
-
-       dir = g_dir_open (src_directory, 0, NULL);
-       if (dir == NULL)
-               return FALSE;
-
-       /* This is to avoid renaming files while we're iterating over the
-        * directory.  POSIX says the outcome of that is unspecified. */
-       corrections = g_hash_table_new_full (
-               g_str_hash, g_str_equal,
-               (GDestroyNotify) g_free,
-               (GDestroyNotify) g_free);
-
-       g_mkdir_with_parents (dst_directory, 0700);
-
-       while ((basename = g_dir_read_name (dir)) != NULL) {
-               gchar *old_filename;
-               gchar *new_filename;
-
-               old_filename = g_build_filename (src_directory, basename, NULL);
-               new_filename = g_build_filename (dst_directory, basename, NULL);
-
-               g_hash_table_insert (corrections, old_filename, new_filename);
-       }
-
-       g_dir_close (dir);
-
-       migrate_process_corrections (corrections);
-       g_hash_table_destroy (corrections);
-
-       /* It's tempting to want to remove the source directory here.
-        * Don't.  We might be iterating over the source directory's
-        * parent directory, and removing the source directory would
-        * screw up the iteration. */
-
-       return TRUE;
-}
-
-static void
-migrate_fix_groupwise_bug (const gchar *old_base_dir)
-{
-       GDir *dir;
-       GHashTable *corrections;
-       const gchar *basename;
-       gchar *old_data_dir;
-       gchar *old_cache_dir;
-
-       /* The groupwise backend mistakenly put its addressbook
-        * cache files in ~/.evolution/addressbook instead of
-        * ~/.evolution/cache/addressbook.  Fix that before
-        * we migrate the cache directory. */
-
-       old_data_dir = g_build_filename (old_base_dir, "addressbook", NULL);
-       old_cache_dir = g_build_filename (old_base_dir, "cache", "addressbook", NULL);
-
-       dir = g_dir_open (old_data_dir, 0, NULL);
-       if (dir == NULL)
-               goto exit;
-
-       /* This is to avoid renaming files while we're iterating over the
-        * directory.  POSIX says the outcome of that is unspecified. */
-       corrections = g_hash_table_new_full (
-               g_str_hash, g_str_equal,
-               (GDestroyNotify) g_free,
-               (GDestroyNotify) g_free);
-
-       while ((basename = g_dir_read_name (dir)) != NULL) {
-               gchar *old_filename;
-               gchar *new_filename;
-
-               if (!g_str_has_prefix (basename, "groupwise___"))
-                       continue;
-
-               old_filename = g_build_filename (old_data_dir, basename, NULL);
-               new_filename = g_build_filename (old_cache_dir, basename, NULL);
-
-               g_hash_table_insert (corrections, old_filename, new_filename);
-       }
-
-       g_dir_close (dir);
-
-       migrate_process_corrections (corrections);
-       g_hash_table_destroy (corrections);
-
-exit:
-       g_free (old_data_dir);
-       g_free (old_cache_dir);
-}
-
-static void
-migrate_to_user_cache_dir (const gchar *old_base_dir)
-{
-       const gchar *new_cache_dir;
-       gchar *old_cache_dir;
-       gchar *src_directory;
-       gchar *dst_directory;
-
-       old_cache_dir = g_build_filename (old_base_dir, "cache", NULL);
-       new_cache_dir = e_get_user_cache_dir ();
-
-       g_print ("Migrating cached backend data\n");
-
-       /* We don't want to move the source directory directly because the
-        * destination directory may already exist with content.  Instead
-        * we want to merge the content of the source directory into the
-        * destination directory.
-        *
-        * For example, given:
-        *
-        *    $(src_directory)/A   and   $(dst_directory)/B
-        *    $(src_directory)/C
-        *
-        * we want to end up with:
-        *
-        *    $(dst_directory)/A
-        *    $(dst_directory)/B
-        *    $(dst_directory)/C
-        *
-        * Any name collisions will be left in the source directory.
-        */
-
-       src_directory = g_build_filename (old_cache_dir, "addressbook", NULL);
-       dst_directory = g_build_filename (new_cache_dir, "addressbook", NULL);
-
-       migrate_move_contents (src_directory, dst_directory);
-       migrate_rmdir (src_directory);
-
-       g_free (src_directory);
-       g_free (dst_directory);
-
-       /* Try to remove the old cache directory.  Good chance this will
-        * fail on the first try, since Evolution puts stuff here too. */
-       migrate_rmdir (old_cache_dir);
-
-       g_free (old_cache_dir);
-}
-
-static void
-migrate_to_user_data_dir (const gchar *old_base_dir)
-{
-       const gchar *new_data_dir;
-       gchar *src_directory;
-       gchar *dst_directory;
-
-       new_data_dir = e_get_user_data_dir ();
-
-       g_print ("Migrating local backend data\n");
-
-       /* We don't want to move the source directory directly because the
-        * destination directory may already exist with content.  Instead
-        * we want to merge the content of the source directory into the
-        * destination directory.
-        *
-        * For example, given:
-        *
-        *    $(src_directory)/A   and   $(dst_directory)/B
-        *    $(src_directory)/C
-        *
-        * we want to end up with:
-        *
-        *    $(dst_directory)/A
-        *    $(dst_directory)/B
-        *    $(dst_directory)/C
-        *
-        * Any name collisions will be left in the source directory.
-        */
-
-       src_directory = g_build_filename (old_base_dir, "addressbook", "local", NULL);
-       dst_directory = g_build_filename (new_data_dir, "addressbook", NULL);
-
-       migrate_move_contents (src_directory, dst_directory);
-       migrate_rmdir (src_directory);
-
-       g_free (src_directory);
-       g_free (dst_directory);
-}
-
-void
-evolution_addressbook_factory_migrate_basedir (void)
-{
-       const gchar *home_dir;
-       gchar *old_base_dir;
-
-       /* XXX This blocks, but it's all just local directory
-        *     renames so it should be nearly instantaneous. */
-
-       home_dir = g_get_home_dir ();
-       old_base_dir = g_build_filename (home_dir, ".evolution", NULL);
-
-       /* Is there even anything to migrate? */
-       if (!g_file_test (old_base_dir, G_FILE_TEST_IS_DIR))
-               goto exit;
-
-       migrate_fix_groupwise_bug (old_base_dir);
-
-       migrate_to_user_cache_dir (old_base_dir);
-       migrate_to_user_data_dir (old_base_dir);
-
-       /* Try to remove the old base directory.  Good chance this will
-        * fail on the first try, since Evolution puts stuff here too. */
-       migrate_rmdir (old_base_dir);
-
-exit:
-       g_free (old_base_dir);
-}
index aa1080d..9450c81 100644 (file)
@@ -53,9 +53,6 @@ static GOptionEntry entries[] = {
        { NULL }
 };
 
-/* Forward Declarations */
-void evolution_addressbook_factory_migrate_basedir (void);
-
 gint
 main (gint argc,
       gchar **argv)
@@ -119,9 +116,6 @@ main (gint argc,
                exit (EXIT_FAILURE);
        }
 
-       /* Migrate user data from ~/.evolution to XDG base directories. */
-       evolution_addressbook_factory_migrate_basedir ();
-
        e_gdbus_templates_init_main_thread ();
 
        server = e_data_book_factory_new (NULL, &error);
index ba4d3b8..6c205d5 100644 (file)
@@ -19,14 +19,15 @@ evolution_calendar_factory_CPPFLAGS = \
        -I$(top_builddir) \
        -I$(top_builddir)/calendar \
        $(EVOLUTION_CALENDAR_CFLAGS) \
+       $(GNOME_KEYRING_CFLAGS) \
        $(FACTORY_GTK_CFLAGS) \
        $(DBUS_GLIB_CFLAGS) \
+       $(SOUP_CFLAGS) \
        $(CODE_COVERAGE_CFLAGS) \
        $(NULL)
 
 evolution_calendar_factory_SOURCES = \
        evolution-calendar-factory.c \
-       evolution-calendar-factory-migrate-basedir.c \
        $(NULL)
 
 evolution_calendar_factory_LDADD = \
@@ -35,8 +36,10 @@ evolution_calendar_factory_LDADD = \
        $(top_builddir)/libebackend/libebackend-1.2.la \
        $(top_builddir)/libedataserver/libedataserver-1.2.la \
        $(EVOLUTION_CALENDAR_LIBS) \
+       $(GNOME_KEYRING_LIBS) \
        $(FACTORY_GTK_LIBS) \
        $(DBUS_GLIB_LIBS) \
+       $(SOUP_LIBS) \
        $(NULL)
 
 evolution_calendar_factory_LDFLAGS = \
index a044e62..aa76072 100644 (file)
@@ -57,9 +57,6 @@ static GOptionEntry entries[] = {
        { NULL }
 };
 
-/* Forward Declarations */
-void evolution_calendar_factory_migrate_basedir (void);
-
 gint
 main (gint argc,
       gchar **argv)
@@ -127,9 +124,6 @@ main (gint argc,
        ical_set_unknown_token_handling_setting (ICAL_DISCARD_TOKEN);
 #endif
 
-       /* Migrate user data from ~/.evolution to XDG base directories. */
-       evolution_calendar_factory_migrate_basedir ();
-
        e_gdbus_templates_init_main_thread ();
 
        server = e_data_cal_factory_new (NULL, &error);
diff --git a/services/evolution-source-registry/Makefile.am b/services/evolution-source-registry/Makefile.am
new file mode 100644 (file)
index 0000000..4e65ea2
--- /dev/null
@@ -0,0 +1,41 @@
+NULL =
+
+service_in_files = org.gnome.evolution.dataserver.Sources.service.in
+servicedir = $(datadir)/dbus-1/services
+service_DATA = $(service_in_files:.service.in=.service)
+@EVO_SUBST_SERVICE_RULE@
+
+CLEANFILES = $(service_DATA)
+EXTRA_DIST = $(service_in_files)
+
+libexec_PROGRAMS = evolution-source-registry
+
+evolution_source_registry_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       -I$(top_srcdir) \
+       -I$(top_builddir) \
+       -DG_LOG_DOMAIN=\"evolution-source-registry\" \
+       -DLOCALEDIR=\"$(localedir)\" \
+       $(E_DATA_SERVER_CFLAGS) \
+       $(GNOME_KEYRING_CFLAGS) \
+       $(CAMEL_CFLAGS) \
+       $(SOUP_CFLAGS) \
+       $(NULL)
+
+evolution_source_registry_SOURCES = \
+       evolution-source-registry.c \
+       evolution-source-registry-migrate-basedir.c \
+       evolution-source-registry-migrate-sources.c \
+       $(NULL)
+
+evolution_source_registry_LDADD = \
+       $(top_builddir)/libebackend/libebackend-1.2.la \
+       $(top_builddir)/libedataserver/libedataserver-1.2.la \
+       $(top_builddir)/camel/libcamel-1.2.la \
+       $(E_DATA_SERVER_LIBS) \
+       $(GNOME_KEYRING_LIBS) \
+       $(CAMEL_LIBS) \
+       $(SOUP_LIBS) \
+       $(NULL)
+
+-include $(top_srcdir)/git.mk
@@ -1,5 +1,5 @@
 /*
- * evolution-calendar-factory-migrate-basedir.c
+ * evolution-addressbook-factory-migrate-basedir.c
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,7 +21,7 @@
 #include <libedataserver/e-data-server-util.h>
 
 /* Forward Declarations */
-void evolution_calendar_factory_migrate_basedir (void);
+void evolution_source_registry_migrate_basedir (void);
 
 static gboolean
 migrate_rename (const gchar *old_filename,
@@ -221,6 +221,57 @@ migrate_fix_memos_cache_bug (const gchar *old_base_dir)
 }
 
 static void
+migrate_fix_groupwise_bug (const gchar *old_base_dir)
+{
+       GDir *dir;
+       GHashTable *corrections;
+       const gchar *basename;
+       gchar *old_data_dir;
+       gchar *old_cache_dir;
+
+       /* The groupwise backend mistakenly put its addressbook
+        * cache files in ~/.evolution/addressbook instead of
+        * ~/.evolution/cache/addressbook.  Fix that before
+        * we migrate the cache directory. */
+
+       old_data_dir = g_build_filename (old_base_dir, "addressbook", NULL);
+       old_cache_dir = g_build_filename (old_base_dir, "cache", "addressbook", NULL);
+
+       dir = g_dir_open (old_data_dir, 0, NULL);
+       if (dir == NULL)
+               goto exit;
+
+       /* This is to avoid renaming files while we're iterating over the
+        * directory.  POSIX says the outcome of that is unspecified. */
+       corrections = g_hash_table_new_full (
+               g_str_hash, g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) g_free);
+
+       while ((basename = g_dir_read_name (dir)) != NULL) {
+               gchar *old_filename;
+               gchar *new_filename;
+
+               if (!g_str_has_prefix (basename, "groupwise___"))
+                       continue;
+
+               old_filename = g_build_filename (old_data_dir, basename, NULL);
+               new_filename = g_build_filename (old_cache_dir, basename, NULL);
+
+               g_hash_table_insert (corrections, old_filename, new_filename);
+       }
+
+       g_dir_close (dir);
+
+       migrate_process_corrections (corrections);
+       g_hash_table_destroy (corrections);
+
+exit:
+       g_free (old_data_dir);
+       g_free (old_cache_dir);
+}
+
+static void
 migrate_to_user_cache_dir (const gchar *old_base_dir)
 {
        const gchar *new_cache_dir;
@@ -252,6 +303,15 @@ migrate_to_user_cache_dir (const gchar *old_base_dir)
         * Any name collisions will be left in the source directory.
         */
 
+       src_directory = g_build_filename (old_cache_dir, "addressbook", NULL);
+       dst_directory = g_build_filename (new_cache_dir, "addressbook", NULL);
+
+       migrate_move_contents (src_directory, dst_directory);
+       migrate_rmdir (src_directory);
+
+       g_free (src_directory);
+       g_free (dst_directory);
+
        src_directory = g_build_filename (old_cache_dir, "calendar", NULL);
        dst_directory = g_build_filename (new_cache_dir, "calendar", NULL);
 
@@ -316,6 +376,15 @@ migrate_to_user_data_dir (const gchar *old_base_dir)
         * Any name collisions will be left in the source directory.
         */
 
+       src_directory = g_build_filename (old_base_dir, "addressbook", "local", NULL);
+       dst_directory = g_build_filename (new_data_dir, "addressbook", NULL);
+
+       migrate_move_contents (src_directory, dst_directory);
+       migrate_rmdir (src_directory);
+
+       g_free (src_directory);
+       g_free (dst_directory);
+
        src_directory = g_build_filename (old_base_dir, "calendar", "local", NULL);
        dst_directory = g_build_filename (new_data_dir, "calendar", NULL);
 
@@ -359,12 +428,12 @@ migrate_to_user_data_dir (const gchar *old_base_dir)
 }
 
 void
-evolution_calendar_factory_migrate_basedir (void)
+evolution_source_registry_migrate_basedir (void)
 {
        const gchar *home_dir;
        gchar *old_base_dir;
 
-       /* XXX This blocks, but it's all just local file
+       /* XXX This blocks, but it's all just local directory
         *     renames so it should be nearly instantaneous. */
 
        home_dir = g_get_home_dir ();
@@ -374,8 +443,10 @@ evolution_calendar_factory_migrate_basedir (void)
        if (!g_file_test (old_base_dir, G_FILE_TEST_IS_DIR))
                goto exit;
 
+       /* Miscellaneous tweaks before we start. */
        migrate_fix_exchange_bug (old_base_dir);
        migrate_fix_memos_cache_bug (old_base_dir);
+       migrate_fix_groupwise_bug (old_base_dir);
 
        migrate_to_user_cache_dir (old_base_dir);
        migrate_to_user_data_dir (old_base_dir);
diff --git a/services/evolution-source-registry/evolution-source-registry-migrate-sources.c b/services/evolution-source-registry/evolution-source-registry-migrate-sources.c
new file mode 100644 (file)
index 0000000..3b304b7
--- /dev/null
@@ -0,0 +1,3493 @@
+/*
+ * migrate-from-gconf.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <glib/gstdio.h>
+#include <camel/camel.h>
+#include <libsoup/soup.h>
+#include <gnome-keyring.h>
+
+#include <libedataserver/e-source-address-book.h>
+#include <libedataserver/e-source-alarms.h>
+#include <libedataserver/e-source-authentication.h>
+#include <libedataserver/e-source-autocomplete.h>
+#include <libedataserver/e-source-calendar.h>
+#include <libedataserver/e-source-camel.h>
+#include <libedataserver/e-source-collection.h>
+#include <libedataserver/e-source-mail-account.h>
+#include <libedataserver/e-source-mail-composition.h>
+#include <libedataserver/e-source-mail-identity.h>
+#include <libedataserver/e-source-mail-signature.h>
+#include <libedataserver/e-source-mail-submission.h>
+#include <libedataserver/e-source-mail-transport.h>
+#include <libedataserver/e-source-mdn.h>
+#include <libedataserver/e-source-offline.h>
+#include <libedataserver/e-source-openpgp.h>
+#include <libedataserver/e-source-refresh.h>
+#include <libedataserver/e-source-security.h>
+#include <libedataserver/e-source-smime.h>
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-uid.h>
+
+#include <libebackend/e-server-side-source.h>
+
+/* These constants are collected from various e-source-*.h files
+ * throughout evolution-data-server and known extension packages. */
+#define E_SOURCE_GROUP_NAME                    "Data Source"
+#define E_SOURCE_EXTENSION_CONTACTS_BACKEND    "Contacts Backend"
+#define E_SOURCE_EXTENSION_LDAP_BACKEND                "LDAP Backend"
+#define E_SOURCE_EXTENSION_LOCAL_BACKEND       "Local Backend"
+#define E_SOURCE_EXTENSION_VCF_BACKEND         "VCF Backend"
+#define E_SOURCE_EXTENSION_WEATHER_BACKEND     "Weather Backend"
+#define E_SOURCE_EXTENSION_WEBDAV_BACKEND      "WebDAV Backend"
+
+/* These constants are copied from e-source-password.c. */
+#define KEYRING_ITEM_ATTRIBUTE_NAME            "e-source-uid"
+#define KEYRING_ITEM_DISPLAY_FORMAT            "Evolution Data Source %s"
+
+typedef struct _ParseData ParseData;
+
+typedef void           (*PropertyFunc)         (ParseData *parse_data,
+                                                const gchar *property_name,
+                                                const gchar *property_value);
+
+typedef enum {
+       PARSE_TYPE_MAIL,
+       PARSE_TYPE_ADDRESSBOOK,
+       PARSE_TYPE_CALENDAR,
+       PARSE_TYPE_TASKS,
+       PARSE_TYPE_MEMOS
+} ParseType;
+
+typedef enum {
+       PARSE_STATE_INITIAL,
+
+       PARSE_STATE_IN_GCONF,                   /* GConf XML */
+       PARSE_STATE_IN_ACCOUNTS_ENTRY,          /* GConf XML */
+       PARSE_STATE_IN_ACCOUNTS_VALUE,          /* GConf XML */
+       PARSE_STATE_IN_SIGNATURES_ENTRY,        /* GConf XML */
+       PARSE_STATE_IN_SIGNATURES_VALUE,        /* GConf XML */
+       PARSE_STATE_IN_SOURCES_ENTRY,           /* GConf XML */
+       PARSE_STATE_IN_SOURCES_VALUE,           /* GConf XML */
+
+       PARSE_STATE_IN_ACCOUNT,                 /* EAccount XML */
+       PARSE_STATE_IN_IDENTITY,                /* EAccount XML */
+       PARSE_STATE_IN_IDENTITY_NAME,           /* EAccount XML */
+       PARSE_STATE_IN_IDENTITY_ADDR_SPEC,      /* EAccount XML */
+       PARSE_STATE_IN_IDENTITY_REPLY_TO,       /* EAccount XML */
+       PARSE_STATE_IN_IDENTITY_ORGANIZATION,   /* EAccount XML */
+       PARSE_STATE_IN_IDENTITY_SIGNATURE,      /* EAccount XML */
+       PARSE_STATE_IN_MAIL_SOURCE,             /* EAccount XML */
+       PARSE_STATE_IN_MAIL_SOURCE_URL,         /* EAccount XML */
+       PARSE_STATE_IN_MAIL_TRANSPORT,          /* EAccount XML */
+       PARSE_STATE_IN_MAIL_TRANSPORT_URL,      /* EAccount XML */
+       PARSE_STATE_IN_AUTO_CC,                 /* EAccount XML */
+       PARSE_STATE_IN_AUTO_CC_RECIPIENTS,      /* EAccount XML */
+       PARSE_STATE_IN_AUTO_BCC,                /* EAccount XML */
+       PARSE_STATE_IN_AUTO_BCC_RECIPIENTS,     /* EAccount XML */
+       PARSE_STATE_IN_DRAFTS_FOLDER,           /* EAccount XML */
+       PARSE_STATE_IN_SENT_FOLDER,             /* EAccount XML */
+       PARSE_STATE_IN_RECEIPT_POLICY,          /* EAccount XML */
+       PARSE_STATE_IN_PGP,                     /* EAccount XML */
+       PARSE_STATE_IN_PGP_KEY_ID,              /* EAccount XML */
+       PARSE_STATE_IN_SMIME,                   /* EAccount XML */
+       PARSE_STATE_IN_SMIME_SIGN_KEY_ID,       /* EAccount XML */
+       PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID,    /* EAccount XML */
+
+       PARSE_STATE_IN_SIGNATURE,               /* ESignature XML */
+       PARSE_STATE_IN_FILENAME,                /* ESignature XML */
+
+       PARSE_STATE_IN_GROUP,                   /* ESource XML */
+       PARSE_STATE_IN_SOURCE,                  /* ESource XML */
+       PARSE_STATE_IN_PROPERTIES               /* ESource XML */
+} ParseState;
+
+struct _ParseData {
+       ParseType type;
+       ParseState state;
+
+       /* Whether to skip writing a file
+        * for this account information. */
+       gboolean skip;
+
+       /* Set by <account>, <source> and <signature> tags. */
+       GFile *file;
+       GKeyFile *key_file;
+
+       /* Set by <account>/<source> tags. */
+       gboolean auto_bcc;
+       gboolean auto_cc;
+
+       /* Set by <identity> tags. */
+       GFile *identity_file;
+       GKeyFile *identity_key_file;
+
+       /* Set by <transport> tags. */
+       GFile *transport_file;
+       GKeyFile *transport_key_file;
+
+       /* Set by <account> tags. */
+       GFile *collection_file;
+       GKeyFile *collection_key_file;
+
+       /* Set by <signature> tags. */
+       GFile *signature_file;
+       gboolean is_script;
+
+       /* Set by <group> tags. */
+       gchar *base_uri;
+
+       /* Set by <source> tags. */
+       gchar *mangled_uri;
+       SoupURI *soup_uri;
+       PropertyFunc property_func;
+};
+
+static GnomeKeyringPasswordSchema schema = {
+       GNOME_KEYRING_ITEM_GENERIC_SECRET,
+       {
+               { KEYRING_ITEM_ATTRIBUTE_NAME,
+                 GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+               { NULL, 0 }
+       }
+};
+
+/* Forward Declarations */
+void evolution_source_registry_migrate_sources (void);
+
+static ParseData *
+parse_data_new (ParseType parse_type)
+{
+       ParseData *parse_data;
+
+       parse_data = g_slice_new0 (ParseData);
+       parse_data->type = parse_type;
+       parse_data->state = PARSE_STATE_INITIAL;
+
+       return parse_data;
+}
+
+static void
+parse_data_free (ParseData *parse_data)
+{
+       /* Normally the allocated data in ParseData is freed and the
+        * pointers are cleared before we get here.  But if an error
+        * occurred we may leave data behind.  This cleans it up. */
+
+       if (parse_data->file != NULL)
+               g_object_unref (parse_data->file);
+
+       if (parse_data->key_file != NULL)
+               g_key_file_free (parse_data->key_file);
+
+       if (parse_data->identity_file != NULL)
+               g_object_unref (parse_data->identity_file);
+
+       if (parse_data->identity_key_file != NULL)
+               g_key_file_free (parse_data->identity_key_file);
+
+       if (parse_data->transport_file != NULL)
+               g_object_unref (parse_data->transport_file);
+
+       if (parse_data->transport_key_file != NULL)
+               g_key_file_free (parse_data->transport_key_file);
+
+       if (parse_data->collection_file != NULL)
+               g_object_unref (parse_data->collection_file);
+
+       if (parse_data->collection_key_file != NULL)
+               g_key_file_free (parse_data->collection_key_file);
+
+       if (parse_data->signature_file != NULL)
+               g_object_unref (parse_data->signature_file);
+
+       g_free (parse_data->base_uri);
+       g_free (parse_data->mangled_uri);
+
+       if (parse_data->soup_uri != NULL)
+               soup_uri_free (parse_data->soup_uri);
+
+       g_slice_free (ParseData, parse_data);
+}
+
+static gboolean
+is_true (const gchar *string)
+{
+       return  (g_ascii_strcasecmp (string, "1") == 0) ||
+               (g_ascii_strcasecmp (string, "true") == 0);
+}
+
+static gboolean
+is_false (const gchar *string)
+{
+       return  (g_ascii_strcasecmp (string, "0") == 0) ||
+               (g_ascii_strcasecmp (string, "false") == 0);
+}
+
+static gboolean
+base_uri_is_groupware (const gchar *base_uri)
+{
+       /* Well-known scheme names from various groupware packages. */
+
+       /* We use a limited string comparsion here because the
+        * base_uri string may be 'scheme://' or just 'scheme'. */
+
+       g_return_val_if_fail (base_uri != NULL, FALSE);
+
+       if (g_ascii_strncasecmp (base_uri, "ews", 3) == 0)
+               return TRUE;
+
+       if (g_ascii_strncasecmp (base_uri, "exchange", 8) == 0)
+               return TRUE;
+
+       if (g_ascii_strncasecmp (base_uri, "groupwise", 9) == 0)
+               return TRUE;
+
+       if (g_ascii_strncasecmp (base_uri, "kolab", 5) == 0)
+               return TRUE;
+
+       if (g_ascii_strncasecmp (base_uri, "mapi", 4) == 0)
+               return TRUE;
+
+       return FALSE;
+}
+
+static void
+migrate_keyring_entry (const gchar *uid,
+                       const gchar *user,
+                       const gchar *server,
+                       const gchar *protocol)
+{
+       GnomeKeyringAttributeList *attributes;
+       GList *found_list = NULL;
+       gchar *display_name;
+
+       /* This is a best-effort routine, so we don't really care about
+        * errors.  We leave the old keyring entry in place since it may
+        * be reused for address book or calendar migration. */
+
+       display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
+
+       attributes = gnome_keyring_attribute_list_new ();
+
+       gnome_keyring_attribute_list_append_string (
+               attributes, "application", "Evolution");
+       if (user != NULL)
+               gnome_keyring_attribute_list_append_string (
+                       attributes, "user", user);
+       if (server != NULL)
+               gnome_keyring_attribute_list_append_string (
+                       attributes, "server", server);
+       if (protocol != NULL)
+               gnome_keyring_attribute_list_append_string (
+                       attributes, "protocol", protocol);
+
+       gnome_keyring_find_items_sync (
+               GNOME_KEYRING_ITEM_NETWORK_PASSWORD, attributes, &found_list);
+
+       /* Pick the first match we find. */
+       if (found_list != NULL) {
+               GnomeKeyringFound *found = found_list->data;
+
+               /* Sanity check. */
+               g_return_if_fail (found->secret != NULL);
+
+               gnome_keyring_store_password_sync (
+                       &schema, GNOME_KEYRING_DEFAULT, display_name,
+                       found->secret, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+       }
+
+       gnome_keyring_attribute_list_free (attributes);
+       gnome_keyring_found_list_free (found_list);
+
+       g_free (display_name);
+}
+
+static gboolean
+migrate_parse_commit_changes (ParseType parse_type,
+                              GFile *file,
+                              GKeyFile *key_file,
+                              const gchar *mangled_uri,
+                              GError **error)
+{
+       const gchar *data_dir;
+       const gchar *cache_dir;
+       const gchar *component;
+       gchar *old_directory;
+       gchar *new_directory;
+       gchar *contents;
+       gchar *uid;
+       gsize length;
+       gboolean success;
+       gboolean old_directory_exists;
+       gboolean new_directory_exists;
+
+       data_dir = e_get_user_data_dir ();
+       cache_dir = e_get_user_cache_dir ();
+
+       uid = e_server_side_source_uid_from_file (file, error);
+
+       if (uid == NULL)
+               return FALSE;
+
+       g_print ("  * Source: %s\n", uid);
+
+       g_print ("    Writing key file...\n");
+
+       /* Save the key file contents to disk. */
+       contents = g_key_file_to_data (key_file, &length, NULL);
+       success = g_file_replace_contents (
+               file, contents, length, NULL, FALSE,
+               G_FILE_CREATE_PRIVATE, NULL, NULL, error);
+       g_free (contents);
+
+       if (!success)
+               goto exit;
+
+       /* Rename the source's local cache directory from its mangled
+        * URI to its UID.  The key file's basename contains the UID.
+        * All source types but "local" should have cache directories. */
+
+       /* Mail cache directories already use UIDs. */
+       switch (parse_type) {
+               case PARSE_TYPE_ADDRESSBOOK:
+                       component = "addressbook";
+                       break;
+               case PARSE_TYPE_CALENDAR:
+                       component = "calendar";
+                       break;
+               case PARSE_TYPE_TASKS:
+                       component = "tasks";
+                       break;
+               case PARSE_TYPE_MEMOS:
+                       component = "memos";
+                       break;
+               default:
+                       goto exit;
+       }
+
+       g_assert (mangled_uri != NULL);
+
+       old_directory = g_build_filename (
+               cache_dir, component, mangled_uri, NULL);
+
+       new_directory = g_build_filename (
+               cache_dir, component, uid, NULL);
+
+       old_directory_exists = g_file_test (old_directory, G_FILE_TEST_EXISTS);
+       new_directory_exists = g_file_test (new_directory, G_FILE_TEST_EXISTS);
+
+       g_print (
+               "    Checking for old cache dir '%s'... %s\n",
+               old_directory,
+               old_directory_exists ?  "found" : "not found");
+
+       if (old_directory_exists) {
+               g_print (
+                       "    Checking for new cache dir '%s'... %s\n",
+                       new_directory,
+                       new_directory_exists ? "found" : "not found");
+
+               if (new_directory_exists)
+                       g_print ("    Skipping cache directory rename.\n");
+               else {
+                       g_print ("    Renaming old cache directory...\n");
+                       if (g_rename (old_directory, new_directory) < 0) {
+                               g_set_error (
+                                       error, G_FILE_ERROR,
+                                       g_file_error_from_errno (errno),
+                                       "%s", g_strerror (errno));
+                               success = FALSE;
+                       }
+               }
+       }
+
+       g_free (old_directory);
+       g_free (new_directory);
+
+       if (!success)
+               goto exit;
+
+       /* Rename the source's local data directory from its mangled
+        * URI to its UID.  The key file's basename contains the UID.
+        * Only "local" sources have local data directores. */
+
+       old_directory = g_build_filename (
+               data_dir, component, mangled_uri, NULL);
+
+       new_directory = g_build_filename (
+               data_dir, component, uid, NULL);
+
+       old_directory_exists = g_file_test (old_directory, G_FILE_TEST_EXISTS);
+       new_directory_exists = g_file_test (new_directory, G_FILE_TEST_EXISTS);
+
+       g_print (
+               "    Checking for old data dir '%s'... %s\n",
+               old_directory,
+               old_directory_exists ?  "found" : "not found");
+
+       if (old_directory_exists) {
+               g_print (
+                       "    Checking for new data dir '%s'... %s\n",
+                       new_directory,
+                       new_directory_exists ? "found" : "not found");
+
+               if (new_directory_exists)
+                       g_print ("    Skipping data directory rename.\n");
+               else {
+                       g_print ("    Renaming old data directory...\n");
+                       if (g_rename (old_directory, new_directory) < 0) {
+                               g_set_error (
+                                       error, G_FILE_ERROR,
+                                       g_file_error_from_errno (errno),
+                                       "%s", g_strerror (errno));
+                               success = FALSE;
+                       }
+               }
+       }
+
+       g_free (old_directory);
+       g_free (new_directory);
+
+exit:
+       g_free (uid);
+
+       return success;
+}
+
+static void
+migrate_setup_collection (ParseData *parse_data,
+                          CamelURL *url)
+{
+       gchar *collection_uid;
+       gchar *display_name;
+       gboolean enabled;
+
+       g_return_if_fail (parse_data->key_file != NULL);
+       g_return_if_fail (parse_data->identity_key_file != NULL);
+       g_return_if_fail (parse_data->transport_key_file != NULL);
+
+       parse_data->collection_file = e_server_side_source_new_user_file (NULL);
+       parse_data->collection_key_file = g_key_file_new ();
+
+       collection_uid = e_server_side_source_uid_from_file (
+               parse_data->collection_file, NULL);
+
+       /* Copy the display name from the mail account source. */
+
+       display_name = g_key_file_get_string (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME, "DisplayName", NULL);
+
+       g_key_file_set_string (
+               parse_data->collection_key_file,
+               E_SOURCE_GROUP_NAME, "DisplayName", display_name);
+
+       /* Copy the enabled state from the mail account source. */
+
+       enabled = g_key_file_get_boolean (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME, "Enabled", NULL);
+
+       g_key_file_set_boolean (
+               parse_data->collection_key_file,
+               E_SOURCE_GROUP_NAME, "Enabled", enabled);
+
+       /* Collection sources are always top-level sources. */
+
+       g_key_file_set_string (
+               parse_data->collection_key_file,
+               E_SOURCE_GROUP_NAME, "Parent", "");
+
+       /* Collection backend name should match the CamelURL protocol. */
+
+       g_key_file_set_string (
+               parse_data->collection_key_file,
+               E_SOURCE_EXTENSION_COLLECTION,
+               "BackendName", url->protocol);
+
+       g_key_file_set_boolean (
+               parse_data->collection_key_file,
+               E_SOURCE_EXTENSION_COLLECTION,
+               "CalendarEnabled", TRUE);
+
+       g_key_file_set_boolean (
+               parse_data->collection_key_file,
+               E_SOURCE_EXTENSION_COLLECTION,
+               "ContactsEnabled", TRUE);
+
+       g_key_file_set_boolean (
+               parse_data->collection_key_file,
+               E_SOURCE_EXTENSION_COLLECTION,
+               "MailEnabled", TRUE);
+
+       /* Enable all mail sources since we set "MailEnabled=true" above. */
+
+       g_key_file_set_boolean (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME, "Enabled", TRUE);
+
+       g_key_file_set_boolean (
+               parse_data->identity_key_file,
+               E_SOURCE_GROUP_NAME, "Enabled", TRUE);
+
+       g_key_file_set_boolean (
+               parse_data->transport_key_file,
+               E_SOURCE_GROUP_NAME, "Enabled", TRUE);
+
+       /* The other mail sources are children of the collection source. */
+
+       g_key_file_set_string (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME,
+               "Parent", collection_uid);
+
+       g_key_file_set_string (
+               parse_data->identity_key_file,
+               E_SOURCE_GROUP_NAME,
+               "Parent", collection_uid);
+
+       g_key_file_set_string (
+               parse_data->transport_key_file,
+               E_SOURCE_GROUP_NAME,
+               "Parent", collection_uid);
+
+       /* The collection identity has to be determined case-by-case.
+        * Some are based on user name, some are based on email address. */
+
+       if (g_strcmp0 (url->protocol, "ews") == 0)
+               g_key_file_set_string (
+                       parse_data->collection_key_file,
+                       E_SOURCE_EXTENSION_COLLECTION,
+                       "Identity", url->user);
+
+       g_free (collection_uid);
+       g_free (display_name);
+}
+
+static void
+migrate_parse_account (ParseData *parse_data,
+                       const gchar *element_name,
+                       const gchar **attribute_names,
+                       const gchar **attribute_values,
+                       GError **error)
+{
+       const gchar *uid;
+       const gchar *name;
+       gchar *identity_uid;
+       gchar *transport_uid;
+       gboolean enabled;
+       gboolean success;
+
+       success = g_markup_collect_attributes (
+               element_name,
+               attribute_names,
+               attribute_values,
+               error,
+               G_MARKUP_COLLECT_STRING,
+               "uid", &uid,
+               G_MARKUP_COLLECT_STRING,
+               "name", &name,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "enabled", &enabled,
+               G_MARKUP_COLLECT_INVALID);
+
+       if (!success)
+               return;
+
+       parse_data->file = e_server_side_source_new_user_file (uid);
+
+       /* If the file already exists, skip this source.  It may be that we
+        * already migrated it, in which case we don't want to overwrite it. */
+       if (g_file_query_exists (parse_data->file, NULL))
+               return;
+
+       parse_data->key_file = g_key_file_new ();
+
+       parse_data->identity_file = e_server_side_source_new_user_file (NULL);
+       parse_data->identity_key_file = g_key_file_new ();
+
+       parse_data->transport_file = e_server_side_source_new_user_file (NULL);
+       parse_data->transport_key_file = g_key_file_new ();
+
+       identity_uid = e_server_side_source_uid_from_file (
+               parse_data->identity_file, NULL);
+
+       transport_uid = e_server_side_source_uid_from_file (
+               parse_data->transport_file, NULL);
+
+       g_key_file_set_string (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME,
+               "DisplayName", name);
+
+       g_key_file_set_boolean (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME,
+               "Enabled", enabled);
+
+       /* Mail account source references the identity source. */
+       g_key_file_set_string (
+               parse_data->key_file,
+               E_SOURCE_EXTENSION_MAIL_ACCOUNT,
+               "IdentityUid", identity_uid);
+
+       /* Mail account source references the transport source. */
+       g_key_file_set_string (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_MAIL_SUBMISSION,
+               "TransportUid", transport_uid);
+
+       /* Identity source gets the same display name. */
+       g_key_file_set_string (
+               parse_data->identity_key_file,
+               E_SOURCE_GROUP_NAME,
+               "DisplayName", name);
+
+       /* Identity source is a child of the mail account. */
+       g_key_file_set_string (
+               parse_data->identity_key_file,
+               E_SOURCE_GROUP_NAME,
+               "Parent", uid);
+
+       /* Transport source gets the same display name. */
+       g_key_file_set_string (
+               parse_data->transport_key_file,
+               E_SOURCE_GROUP_NAME,
+               "DisplayName", name);
+
+       /* Transport source is a child of the mail account. */
+       g_key_file_set_string (
+               parse_data->transport_key_file,
+               E_SOURCE_GROUP_NAME,
+               "Parent", uid);
+
+       g_free (identity_uid);
+       g_free (transport_uid);
+}
+
+static void
+migrate_parse_pgp (ParseData *parse_data,
+                   const gchar *element_name,
+                   const gchar **attribute_names,
+                   const gchar **attribute_values,
+                   GError **error)
+{
+       const gchar *hash_algo;
+       gboolean always_sign;
+       gboolean always_trust;
+       gboolean encrypt_to_self;
+       gboolean no_imip_sign;
+       gboolean success;
+
+       success = g_markup_collect_attributes (
+               element_name,
+               attribute_names,
+               attribute_values,
+               error,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "always-sign", &always_sign,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "always-trust", &always_trust,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "encrypt-to-self", &encrypt_to_self,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "no-imip-sign", &no_imip_sign,
+               G_MARKUP_COLLECT_STRING |
+               G_MARKUP_COLLECT_OPTIONAL,
+               "hash-algo", &hash_algo,
+               G_MARKUP_COLLECT_INVALID);
+
+       if (!success)
+               return;
+
+       g_key_file_set_boolean (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_OPENPGP,
+               "AlwaysSign", always_sign);
+
+       g_key_file_set_boolean (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_OPENPGP,
+               "AlwaysTrust", always_trust);
+
+       g_key_file_set_boolean (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_OPENPGP,
+               "EncryptToSelf", encrypt_to_self);
+
+       if (hash_algo != NULL && *hash_algo != '\0')
+               g_key_file_set_string (
+                       parse_data->identity_key_file,
+                       E_SOURCE_EXTENSION_OPENPGP,
+                       "SigningAlgorithm", hash_algo);
+
+       /* XXX Don't know why this is under the <pgp>
+        *     element, it applies to S/MIME as well.
+        *     Also note we're inverting the setting. */
+       g_key_file_set_boolean (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_MAIL_COMPOSITION,
+               "SignImip", !no_imip_sign);
+}
+
+static void
+migrate_parse_recipients (ParseData *parse_data,
+                          const gchar *key,
+                          const gchar *recipients)
+{
+       CamelAddress *address;
+       CamelInternetAddress *inet_address;
+       gchar **string_list;
+       gint ii, length;
+       gsize index = 0;
+
+       if (recipients == NULL || *recipients == '\0')
+               return;
+
+       inet_address = camel_internet_address_new ();
+       address = CAMEL_ADDRESS (inet_address);
+
+       if (camel_address_decode (address, recipients) == -1)
+               goto exit;
+
+       length = camel_address_length (address);
+       string_list = g_new0 (gchar *, length + 1);
+
+       for (ii = 0; ii < length; ii++) {
+               const gchar *name, *addr;
+
+               if (!camel_internet_address_get (
+                       inet_address, ii, &name, &addr))
+                       continue;
+
+               string_list[index++] =
+                       camel_internet_address_format_address (name, addr);
+       }
+
+       g_key_file_set_string_list (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_MAIL_COMPOSITION, key,
+               (const gchar *const *) string_list, index);
+
+       g_strfreev (string_list);
+
+exit:
+       g_object_unref (inet_address);
+}
+
+static void
+migrate_parse_smime (ParseData *parse_data,
+                     const gchar *element_name,
+                     const gchar **attribute_names,
+                     const gchar **attribute_values,
+                     GError **error)
+{
+       const gchar *hash_algo;
+       gboolean encrypt_default;
+       gboolean encrypt_to_self;
+       gboolean sign_default;
+       gboolean success;
+
+       success = g_markup_collect_attributes (
+               element_name,
+               attribute_names,
+               attribute_values,
+               error,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "encrypt-default", &encrypt_default,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "encrypt-to-self", &encrypt_to_self,
+               G_MARKUP_COLLECT_STRING |
+               G_MARKUP_COLLECT_OPTIONAL,
+               "hash-algo", &hash_algo,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "sign-default", &sign_default,
+               G_MARKUP_COLLECT_INVALID);
+
+       if (!success)
+               return;
+
+       g_key_file_set_boolean (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_SMIME,
+               "EncryptByDefault", encrypt_default);
+
+       g_key_file_set_boolean (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_SMIME,
+               "EncryptToSelf", encrypt_to_self);
+
+       if (hash_algo != NULL && *hash_algo != '\0')
+               g_key_file_set_string (
+                       parse_data->identity_key_file,
+                       E_SOURCE_EXTENSION_SMIME,
+                       "SigningAlgorithm", hash_algo);
+
+       g_key_file_set_boolean (
+               parse_data->identity_key_file,
+               E_SOURCE_EXTENSION_SMIME,
+               "SignByDefault", sign_default);
+}
+
+static void
+migrate_parse_mail_source (ParseData *parse_data,
+                           const gchar *element_name,
+                           const gchar **attribute_names,
+                           const gchar **attribute_values,
+                           GError **error)
+{
+       const gchar *auto_check_timeout;
+       glong interval_minutes = 0;
+       gboolean auto_check;
+       gboolean success;
+
+       /* Disregard "keep-on-server" and "save-passwd" attributes. */
+       success = g_markup_collect_attributes (
+               element_name,
+               attribute_names,
+               attribute_values,
+               error,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "auto-check", &auto_check,
+               G_MARKUP_COLLECT_STRING,
+               "auto-check-timeout", &auto_check_timeout,
+               G_MARKUP_COLLECT_BOOLEAN |
+               G_MARKUP_COLLECT_OPTIONAL,
+               "keep-on-server", NULL,
+               G_MARKUP_COLLECT_BOOLEAN |
+               G_MARKUP_COLLECT_OPTIONAL,
+               "save-passwd", NULL,
+               G_MARKUP_COLLECT_INVALID);
+
+       if (!success)
+               return;
+
+       if (auto_check_timeout != NULL)
+               interval_minutes = strtol (auto_check_timeout, NULL, 10);
+
+       g_key_file_set_boolean (
+               parse_data->key_file,
+               E_SOURCE_EXTENSION_REFRESH,
+               "Enabled", auto_check);
+
+       if (interval_minutes > 0)
+               g_key_file_set_integer (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_REFRESH,
+                       "IntervalMinutes", interval_minutes);
+}
+
+static void
+migrate_parse_url_rename_params (CamelURL *url)
+{
+       /* This list includes known URL parameters from built-in providers
+        * in Camel, as well as from evolution-exchange/groupwise/mapi/ews.
+        * Add more as needed. */
+       static struct {
+               const gchar *url_parameter;
+               const gchar *property_name;
+       } camel_url_conversion[] = {
+               { "account_uid",                "account-uid" },
+               { "ad_auth",                    "gc-auth-method" },
+               { "ad_browse",                  "gc-allow-browse" },
+               { "ad_expand_groups",           "gc-expand-groups" },
+               { "ad_limit",                   "gc-results-limit" },
+               { "ad_server",                  "gc-server-name" },
+               { "all_headers",                "fetch-headers" },
+               { "basic_headers",              "fetch-headers" },
+               { "cachedconn"                  "concurrent-connections" },
+               { "check_all",                  "check-all" },
+               { "check_lsub",                 "check-subscribed" },
+               { "command",                    "shell-command" },
+               { "delete_after",               "delete-after-days" },
+               { "delete_expunged",            "delete-expunged" },
+               { "disable_extensions",         "disable-extensions" },
+               { "dotfolders",                 "use-dot-folders" },
+               { "filter",                     "filter-inbox" },
+               { "filter_junk",                "filter-junk" },
+               { "filter_junk_inbox",          "filter-junk-inbox" },
+               { "folder_hierarchy_relative",  "folder-hierarchy-relative" },
+               { "imap_custom_headers",        "fetch-headers-extra" },
+               { "keep_on_server",             "keep-on-server" },
+               { "oab_offline",                "oab-offline" },
+               { "oal_selected",               "oal-selected" },
+               { "offline_sync",               "stay-synchronized" },
+               { "override_namespace",         "use-namespace" },
+               { "owa_path",                   "owa-path" },
+               { "owa_url",                    "owa-url" },
+               { "password_exp_warn_period",   "password-exp-warn-period" },
+               { "real_junk_path",             "real-junk-path" },
+               { "real_trash_path",            "real-trash-path" },
+               { "show_short_notation",        "short-folder-names" },
+               { "soap_port",                  "soap-port" },
+               { "ssl",                        "security-method" },
+               { "sync_offline",               "stay-synchronized" },
+               { "use_command",                "use-shell-command" },
+               { "use_idle",                   "use-idle" },
+               { "use_lsub",                   "use-subscriptions" },
+               { "use_qresync",                "use-qresync" },
+               { "use_ssl",                    "security-method" },
+               { "xstatus",                    "use-xstatus-headers" }
+       };
+
+       const gchar *param;
+       const gchar *use_param;
+       gint ii;
+
+       for (ii = 0; ii < G_N_ELEMENTS (camel_url_conversion); ii++) {
+               const gchar *key;
+               gpointer value;
+
+               key = camel_url_conversion[ii].url_parameter;
+               value = g_datalist_get_data (&url->params, key);
+
+               if (value == NULL)
+                       continue;
+
+               g_datalist_remove_no_notify (&url->params, key);
+
+               key = camel_url_conversion[ii].property_name;
+
+               /* Deal with a few special enum cases where
+                * the parameter value also needs renamed. */
+
+               if (strcmp (key, "all_headers") == 0) {
+                       GEnumClass *enum_class;
+                       GEnumValue *enum_value;
+
+                       enum_class = g_type_class_ref (
+                               CAMEL_TYPE_FETCH_HEADERS_TYPE);
+                       enum_value = g_enum_get_value (
+                               enum_class, CAMEL_FETCH_HEADERS_ALL);
+                       if (enum_value != NULL) {
+                               g_free (value);
+                               value = g_strdup (enum_value->value_nick);
+                       } else
+                               g_warn_if_reached ();
+                       g_type_class_unref (enum_class);
+               }
+
+               if (strcmp (key, "basic_headers") == 0) {
+                       GEnumClass *enum_class;
+                       GEnumValue *enum_value;
+
+                       enum_class = g_type_class_ref (
+                               CAMEL_TYPE_FETCH_HEADERS_TYPE);
+                       enum_value = g_enum_get_value (
+                               enum_class, CAMEL_FETCH_HEADERS_BASIC);
+                       if (enum_value != NULL) {
+                               g_free (value);
+                               value = g_strdup (enum_value->value_nick);
+                       } else
+                               g_warn_if_reached ();
+                       g_type_class_unref (enum_class);
+               }
+
+               if (strcmp (key, "imap_custom_headers") == 0)
+                       g_strdelimit (value, " ", ',');
+
+               if (strcmp (key, "security-method") == 0) {
+                       CamelNetworkSecurityMethod method;
+                       GEnumClass *enum_class;
+                       GEnumValue *enum_value;
+
+                       if (strcmp (value, "always") == 0)
+                               method = CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT;
+                       else if (strcmp (value, "1") == 0)
+                               method = CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT;
+                       else if (strcmp (value, "when-possible") == 0)
+                               method = CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT;
+                       else
+                               method = CAMEL_NETWORK_SECURITY_METHOD_NONE;
+
+                       enum_class = g_type_class_ref (
+                               CAMEL_TYPE_NETWORK_SECURITY_METHOD);
+                       enum_value = g_enum_get_value (enum_class, method);
+                       if (enum_value != NULL) {
+                               g_free (value);
+                               value = g_strdup (enum_value->value_nick);
+                       } else
+                               g_warn_if_reached ();
+                       g_type_class_unref (enum_class);
+               }
+
+               g_datalist_set_data_full (&url->params, key, value, g_free);
+       }
+
+       /* A few more adjustments...
+        *
+        * These are all CAMEL_PROVIDER_CONF_CHECKSPIN settings.  The spin
+        * button value is bound to "param" and the checkbox state is bound
+        * to "use-param".  The "use-param" settings are new.  If "param"
+        * exists but no "use-param", then set "use-param" to "true". */
+
+       param = g_datalist_get_data (&url->params, "gc-results-limit");
+       use_param = g_datalist_get_data (&url->params, "use-gc-results-limit");
+       if (param != NULL && *param != '\0' && use_param == NULL) {
+               g_datalist_set_data_full (
+                       &url->params, "use-gc-results-limit",
+                       g_strdup ("true"), (GDestroyNotify) g_free);
+       }
+
+       param = g_datalist_get_data (&url->params, "kerberos");
+       if (g_strcmp0 (param, "required") == 0) {
+               g_datalist_set_data_full (
+                       &url->params, "kerberos",
+                       g_strdup ("true"), (GDestroyNotify) g_free);
+       }
+
+       param = g_datalist_get_data (
+               &url->params, "password-exp-warn-period");
+       use_param = g_datalist_get_data (
+               &url->params, "use-password-exp-warn-period");
+       if (param != NULL && *param != '\0' && use_param == NULL) {
+               g_datalist_set_data_full (
+                       &url->params, "use-password-exp-warn-period",
+                       g_strdup ("true"), (GDestroyNotify) g_free);
+       }
+
+       param = g_datalist_get_data (&url->params, "real-junk-path");
+       use_param = g_datalist_get_data (&url->params, "use-real-junk-path");
+       if (param != NULL && *param != '\0' && use_param == NULL) {
+               g_datalist_set_data_full (
+                       &url->params, "use-real-junk-path",
+                       g_strdup ("true"), (GDestroyNotify) g_free);
+       }
+
+       param = g_datalist_get_data (&url->params, "real-trash-path");
+       use_param = g_datalist_get_data (&url->params, "use-real-trash-path");
+       if (param != NULL && *param != '\0' && use_param == NULL) {
+               g_datalist_set_data_full (
+                       &url->params, "use-real-trash-path",
+                       g_strdup ("true"), (GDestroyNotify) g_free);
+       }
+}
+
+static void
+migrate_parse_url_foreach (GQuark key_id,
+                           const gchar *value,
+                           gpointer user_data)
+{
+       const gchar *param_name;
+       const gchar *key;
+
+       struct {
+               GKeyFile *key_file;
+               const gchar *group_name;
+       } *foreach_data = user_data;
+
+       g_return_if_fail (value != NULL);
+
+       param_name = g_quark_to_string (key_id);
+       key = e_source_parameter_to_key (param_name);
+
+       /* If the value is empty, then the mere
+        * presence of the parameter implies TRUE. */
+       if (*value == '\0')
+               value = "true";
+
+       g_key_file_set_string (
+               foreach_data->key_file,
+               foreach_data->group_name,
+               key, value);
+}
+
+static void
+migrate_parse_url (ParseData *parse_data,
+                   GKeyFile *key_file,
+                   GFile *file,
+                   const gchar *group_name,
+                   const gchar *url_string,
+                   GError **error)
+{
+       CamelURL *url;
+       GKeyFile *backend_key_file;
+       GFile *backend_file;
+       const gchar *value;
+       gboolean setup_collection;
+       gchar *uid;
+
+       struct {
+               GKeyFile *key_file;
+               const gchar *group_name;
+       } foreach_data;
+
+       url = camel_url_new (url_string, error);
+       if (url == NULL || url->protocol == NULL)
+               return;
+
+       /* Rename URL params as necessary to match
+        * their ESourceExtension property names. */
+       migrate_parse_url_rename_params (url);
+
+       setup_collection =
+               (key_file == parse_data->key_file) &&
+               base_uri_is_groupware (url->protocol);
+
+       if (setup_collection)
+               migrate_setup_collection (parse_data, url);
+
+       /* Store backend settings in the collection GKeyFile, if one is
+        * defined.  Otherwise store them in the GKeyFile we were passed.
+        * Same goes for the keyring entry, which uses the GFile. */
+       if (parse_data->collection_key_file != NULL) {
+               backend_key_file = parse_data->collection_key_file;
+               backend_file = parse_data->collection_file;
+       } else {
+               backend_key_file = key_file;
+               backend_file = file;
+       }
+
+       /* This is not a backend setting. */
+       g_key_file_set_string (
+               key_file, group_name,
+               "BackendName", url->protocol);
+
+       /* Set authentication details. */
+
+       if (url->host != NULL)
+               g_key_file_set_string (
+                       backend_key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Host", url->host);
+
+       if (url->authmech != NULL)
+               g_key_file_set_string (
+                       backend_key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Method", url->authmech);
+
+       if (url->port > 0)
+               g_key_file_set_integer (
+                       backend_key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Port", url->port);
+
+       if (url->user != NULL)
+               g_key_file_set_string (
+                       backend_key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "User", url->user);
+
+       /* Pick out particular URL parameters we know about. */
+
+       /* If set, this should be "true" or "false",
+        * but we'll just write it like it's a string. */
+       value = g_datalist_get_data (&url->params, "stay-synchronized");
+       if (value != NULL)
+               g_key_file_set_string (
+                       backend_key_file,
+                       E_SOURCE_EXTENSION_OFFLINE,
+                       "StaySynchronized", value);
+       g_datalist_set_data (&url->params, "stay-synchronized", NULL);
+
+       value = g_datalist_get_data (&url->params, "security-method");
+       if (value != NULL)
+               g_key_file_set_string (
+                       backend_key_file,
+                       E_SOURCE_EXTENSION_SECURITY,
+                       "Method", value);
+       g_datalist_set_data (&url->params, "security-method", NULL);
+
+       /* If we see a "goa-account-id" parameter, skip the entire
+        * account and let the online-accounts module recreate it. */
+       value = g_datalist_get_data (&url->params, "goa-account-id");
+       if (value != NULL && *value != '\0')
+               parse_data->skip = TRUE;
+
+       /* The rest of the URL parameters go in the backend group. */
+
+       group_name = e_source_camel_get_extension_name (url->protocol);
+
+       foreach_data.key_file = backend_key_file;
+       foreach_data.group_name = group_name;
+
+       g_datalist_foreach (
+               &url->params, (GDataForeachFunc)
+               migrate_parse_url_foreach, &foreach_data);
+
+       uid = e_server_side_source_uid_from_file (backend_file, error);
+
+       if (uid != NULL) {
+               migrate_keyring_entry (
+                       uid, url->user, url->host, url->protocol);
+               g_free (uid);
+       }
+
+       camel_url_free (url);
+}
+
+static void
+migrate_parse_account_xml_start_element (GMarkupParseContext *context,
+                                         const gchar *element_name,
+                                         const gchar **attribute_names,
+                                         const gchar **attribute_values,
+                                         gpointer user_data,
+                                         GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (g_strcmp0 (element_name, "account") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNTS_VALUE)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_ACCOUNT;
+
+               migrate_parse_account (
+                       parse_data,
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "addr-spec") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_IDENTITY_ADDR_SPEC;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "auto-bcc") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_AUTO_BCC;
+
+               g_markup_collect_attributes (
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error,
+                       G_MARKUP_COLLECT_BOOLEAN,
+                       "always", &parse_data->auto_bcc,
+                       G_MARKUP_COLLECT_INVALID);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "auto-cc") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_AUTO_CC;
+
+               g_markup_collect_attributes (
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error,
+                       G_MARKUP_COLLECT_BOOLEAN,
+                       "always", &parse_data->auto_cc,
+                       G_MARKUP_COLLECT_INVALID);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "drafts-folder") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_DRAFTS_FOLDER;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "encrypt-key-id") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_SMIME)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "identity") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_IDENTITY;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "key-id") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_PGP)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_PGP_KEY_ID;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "name") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_IDENTITY_NAME;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "reply-to") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_IDENTITY_REPLY_TO;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "organization") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_IDENTITY_ORGANIZATION;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "pgp") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_PGP;
+
+               migrate_parse_pgp (
+                       parse_data,
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "receipt-policy") == 0) {
+               const gchar *policy;
+               gboolean success;
+
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_RECEIPT_POLICY;
+
+               success = g_markup_collect_attributes (
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error,
+                       G_MARKUP_COLLECT_STRING,
+                       "policy", &policy,
+                       G_MARKUP_COLLECT_INVALID);
+
+               /* The new enum strings match the old ones. */
+               if (success && policy != NULL)
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_MDN,
+                               "ResponsePolicy", policy);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "recipients") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_AUTO_BCC) {
+                       parse_data->state = PARSE_STATE_IN_AUTO_BCC_RECIPIENTS;
+                       return;
+               }
+
+               if (parse_data->state == PARSE_STATE_IN_AUTO_CC) {
+                       parse_data->state = PARSE_STATE_IN_AUTO_CC_RECIPIENTS;
+                       return;
+               }
+
+               goto invalid_content;
+       }
+
+       if (g_strcmp0 (element_name, "sent-folder") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_SENT_FOLDER;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "signature") == 0) {
+               const gchar *uid;
+               gboolean success;
+
+               if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_IDENTITY_SIGNATURE;
+
+               success = g_markup_collect_attributes (
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error,
+                       G_MARKUP_COLLECT_STRING,
+                       "uid", &uid,
+                       G_MARKUP_COLLECT_INVALID);
+
+               if (success && uid != NULL)
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_MAIL_IDENTITY,
+                               "SignatureUid", uid);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "sign-key-id") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_SMIME)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_SMIME_SIGN_KEY_ID;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "smime") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_SMIME;
+
+               migrate_parse_smime (
+                       parse_data,
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "source") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_MAIL_SOURCE;
+
+               migrate_parse_mail_source (
+                       parse_data,
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "transport") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "url") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE) {
+                       parse_data->state = PARSE_STATE_IN_MAIL_SOURCE_URL;
+                       return;
+               }
+
+               if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT) {
+                       parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT_URL;
+                       return;
+               }
+
+               goto invalid_content;
+       }
+
+       g_set_error (
+               error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+               "Unknown element <%s>", element_name);
+
+       return;
+
+invalid_content:
+
+       g_set_error (
+               error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+               "Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_account_xml_end_element (GMarkupParseContext *context,
+                                       const gchar *element_name,
+                                       gpointer user_data,
+                                       GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (g_strcmp0 (element_name, "account") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_ACCOUNT) {
+                       parse_data->state = PARSE_STATE_IN_ACCOUNTS_VALUE;
+
+                       /* Clean up <account> tag data. */
+
+                       /* The key file will be NULL if we decided to skip it.
+                        * e.g. A file with the same UID may already exist. */
+                       if (parse_data->key_file != NULL) {
+                               GError *local_error = NULL;
+
+                               if (!parse_data->skip)
+                                       migrate_parse_commit_changes (
+                                               parse_data->type,
+                                               parse_data->file,
+                                               parse_data->key_file,
+                                               NULL, &local_error);
+
+                               if (local_error != NULL) {
+                                       g_printerr (
+                                               "  FAILED: %s\n",
+                                               local_error->message);
+                                       g_error_free (local_error);
+                               }
+
+                               g_key_file_free (parse_data->key_file);
+                               parse_data->key_file = NULL;
+                       }
+
+                       /* Same deal for the identity key file. */
+                       if (parse_data->identity_key_file != NULL) {
+                               GError *local_error = NULL;
+
+                               if (!parse_data->skip)
+                                       migrate_parse_commit_changes (
+                                               parse_data->type,
+                                               parse_data->identity_file,
+                                               parse_data->identity_key_file,
+                                               NULL, &local_error);
+
+                               if (local_error != NULL) {
+                                       g_printerr (
+                                               "  FAILED: %s\n",
+                                               local_error->message);
+                                       g_error_free (local_error);
+                               }
+
+                               g_key_file_free (parse_data->identity_key_file);
+                               parse_data->identity_key_file = NULL;
+                       }
+
+                       /* Same deal for the transport key file. */
+                       if (parse_data->transport_key_file != NULL) {
+                               GError *local_error = NULL;
+
+                               if (!parse_data->skip)
+                                       migrate_parse_commit_changes (
+                                               parse_data->type,
+                                               parse_data->transport_file,
+                                               parse_data->transport_key_file,
+                                               NULL, &local_error);
+
+                               if (local_error != NULL) {
+                                       g_printerr (
+                                               "  FAILED: %s\n",
+                                               local_error->message);
+                                       g_error_free (local_error);
+                               }
+
+                               g_key_file_free (parse_data->transport_key_file);
+                               parse_data->transport_key_file = NULL;
+                       }
+
+                       /* The collection key file is optional anyway. */
+                       if (parse_data->collection_key_file != NULL) {
+                               GError *local_error = NULL;
+
+                               if (!parse_data->skip)
+                                       migrate_parse_commit_changes (
+                                               parse_data->type,
+                                               parse_data->collection_file,
+                                               parse_data->collection_key_file,
+                                               NULL, &local_error);
+
+                               if (local_error != NULL) {
+                                       g_printerr (
+                                               "  FAILED: %s\n",
+                                               local_error->message);
+                                       g_error_free (local_error);
+                               }
+
+                               g_key_file_free (parse_data->collection_key_file);
+                               parse_data->collection_key_file = NULL;
+                       }
+
+                       if (parse_data->file != NULL) {
+                               g_object_unref (parse_data->file);
+                               parse_data->file = NULL;
+                       }
+
+                       if (parse_data->identity_file != NULL) {
+                               g_object_unref (parse_data->identity_file);
+                               parse_data->identity_file = NULL;
+                       }
+
+                       if (parse_data->transport_file != NULL) {
+                               g_object_unref (parse_data->transport_file);
+                               parse_data->transport_file = NULL;
+                       }
+
+                       if (parse_data->collection_file != NULL) {
+                               g_object_unref (parse_data->collection_file);
+                               parse_data->collection_file = NULL;
+                       }
+
+                       parse_data->skip = FALSE;
+               }
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "addr-spec") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_IDENTITY_ADDR_SPEC)
+                       parse_data->state = PARSE_STATE_IN_IDENTITY;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "auto-bcc") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_AUTO_BCC)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "auto-cc") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_AUTO_CC)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "drafts-folder") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_DRAFTS_FOLDER)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "encrypt-key-id") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID)
+                       parse_data->state = PARSE_STATE_IN_SMIME;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "identity") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_IDENTITY)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "key-id") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_PGP_KEY_ID)
+                       parse_data->state = PARSE_STATE_IN_PGP;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "name") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_IDENTITY_NAME)
+                       parse_data->state = PARSE_STATE_IN_IDENTITY;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "organization") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_IDENTITY_ORGANIZATION)
+                       parse_data->state = PARSE_STATE_IN_IDENTITY;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "pgp") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_PGP)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "receipt-policy") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_RECEIPT_POLICY)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "recipients") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_AUTO_BCC_RECIPIENTS)
+                       parse_data->state = PARSE_STATE_IN_AUTO_BCC;
+               if (parse_data->state == PARSE_STATE_IN_AUTO_CC_RECIPIENTS)
+                       parse_data->state = PARSE_STATE_IN_AUTO_CC;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "reply-to") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_IDENTITY_REPLY_TO)
+                       parse_data->state = PARSE_STATE_IN_IDENTITY;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "sent-folder") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_SENT_FOLDER)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "signature") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_IDENTITY_SIGNATURE)
+                       parse_data->state = PARSE_STATE_IN_IDENTITY;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "sign-key-id") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_SMIME_SIGN_KEY_ID)
+                       parse_data->state = PARSE_STATE_IN_SMIME;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "smime") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_SMIME)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "source") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "transport") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNT;
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "url") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE_URL)
+                       parse_data->state = PARSE_STATE_IN_MAIL_SOURCE;
+               if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT_URL)
+                       parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT;
+               return;
+       }
+}
+
+static void
+migrate_parse_account_xml_text (GMarkupParseContext *context,
+                                const gchar *text,
+                                gsize text_len,
+                                gpointer user_data,
+                                GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       switch (parse_data->state) {
+               case PARSE_STATE_IN_AUTO_BCC_RECIPIENTS:
+                       /* Disregard the recipient list if
+                        * we're not going to auto-BCC them. */
+                       if (parse_data->auto_bcc)
+                               migrate_parse_recipients (
+                                       parse_data, "Bcc", text);
+                       break;
+
+               case PARSE_STATE_IN_AUTO_CC_RECIPIENTS:
+                       /* Disregard the recipient list if
+                        * we're not going to auto-CC them. */
+                       if (parse_data->auto_cc)
+                               migrate_parse_recipients (
+                                       parse_data, "Cc", text);
+                       break;
+
+               case PARSE_STATE_IN_DRAFTS_FOLDER:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_MAIL_COMPOSITION,
+                               "DraftsFolder", text);
+                       break;
+
+               case PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_SMIME,
+                               "EncryptionCertificate", text);
+                       break;
+
+               case PARSE_STATE_IN_IDENTITY_ADDR_SPEC:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_MAIL_IDENTITY,
+                               "Address", text);
+                       break;
+
+               case PARSE_STATE_IN_IDENTITY_NAME:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_MAIL_IDENTITY,
+                               "Name", text);
+                       break;
+
+               case PARSE_STATE_IN_IDENTITY_ORGANIZATION:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_MAIL_IDENTITY,
+                               "Organization", text);
+                       break;
+
+               case PARSE_STATE_IN_IDENTITY_REPLY_TO:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_MAIL_IDENTITY,
+                               "ReplyTo", text);
+                       break;
+
+               case PARSE_STATE_IN_MAIL_SOURCE_URL:
+                       migrate_parse_url (
+                               parse_data,
+                               parse_data->key_file,
+                               parse_data->file,
+                               E_SOURCE_EXTENSION_MAIL_ACCOUNT,
+                               text, error);
+                       break;
+
+               case PARSE_STATE_IN_MAIL_TRANSPORT_URL:
+                       migrate_parse_url (
+                               parse_data,
+                               parse_data->transport_key_file,
+                               parse_data->transport_file,
+                               E_SOURCE_EXTENSION_MAIL_TRANSPORT,
+                               text, error);
+                       break;
+
+               case PARSE_STATE_IN_PGP_KEY_ID:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_OPENPGP,
+                               "KeyId", text);
+                       break;
+
+               case PARSE_STATE_IN_SENT_FOLDER:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_MAIL_SUBMISSION,
+                               "SentFolder", text);
+                       break;
+
+               case PARSE_STATE_IN_SMIME_SIGN_KEY_ID:
+                       g_key_file_set_string (
+                               parse_data->identity_key_file,
+                               E_SOURCE_EXTENSION_SMIME,
+                               "SigningCertificate", text);
+                       break;
+
+               default:
+                       break;
+       }
+}
+
+static GMarkupParser account_xml_parser = {
+       migrate_parse_account_xml_start_element,
+       migrate_parse_account_xml_end_element,
+       migrate_parse_account_xml_text,
+       NULL,  /* passthrough */
+       NULL   /* error */
+};
+
+static void
+migrate_parse_signature (ParseData *parse_data,
+                         const gchar *element_name,
+                         const gchar **attribute_names,
+                         const gchar **attribute_values,
+                         GError **error)
+{
+       const gchar *uid;
+       const gchar *name;
+       const gchar *format;
+       const gchar *config_dir;
+       gchar *directory;
+       gchar *absolute_path;
+       gboolean autogenerated;
+       gboolean success;
+
+       success = g_markup_collect_attributes (
+               element_name,
+               attribute_names,
+               attribute_values,
+               error,
+               G_MARKUP_COLLECT_STRING,
+               "uid", &uid,
+               G_MARKUP_COLLECT_STRING,
+               "name", &name,
+               G_MARKUP_COLLECT_BOOLEAN,
+               "auto", &autogenerated,
+               G_MARKUP_COLLECT_STRING |
+               G_MARKUP_COLLECT_OPTIONAL,
+               "format", &format,
+               G_MARKUP_COLLECT_INVALID);
+
+       if (!success)
+               return;
+
+       /* Skip the "autogenerated" signature. */
+       if (autogenerated)
+               return;
+
+       parse_data->file = e_server_side_source_new_user_file (uid);
+
+       config_dir = e_get_user_config_dir ();
+       directory = g_build_filename (config_dir, "signatures", NULL);
+       absolute_path = g_build_filename (directory, uid, NULL);
+       parse_data->signature_file = g_file_new_for_path (absolute_path);
+       g_mkdir_with_parents (directory, 0700);
+       g_free (absolute_path);
+       g_free (directory);
+
+       /* If the file already exists, skip this source.  It may be that we
+        * already migrated it, in which case we don't want to overwrite it. */
+       if (g_file_query_exists (parse_data->file, NULL))
+               return;
+
+       parse_data->key_file = g_key_file_new ();
+
+       g_key_file_set_string (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME,
+               "DisplayName", name);
+
+       g_key_file_set_string (
+               parse_data->key_file,
+               E_SOURCE_EXTENSION_MAIL_SIGNATURE,
+               "MimeType", format);
+}
+
+static void
+migrate_parse_signature_xml_start_element (GMarkupParseContext *context,
+                                           const gchar *element_name,
+                                           const gchar **attribute_names,
+                                           const gchar **attribute_values,
+                                           gpointer user_data,
+                                           GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (g_strcmp0 (element_name, "filename") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_SIGNATURE)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_FILENAME;
+
+               g_markup_collect_attributes (
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error,
+                       G_MARKUP_COLLECT_BOOLEAN |
+                       G_MARKUP_COLLECT_OPTIONAL,
+                       "script", &parse_data->is_script,
+                       G_MARKUP_COLLECT_INVALID);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "signature") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_SIGNATURES_VALUE)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_SIGNATURE;
+
+               migrate_parse_signature (
+                       parse_data,
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error);
+
+               return;
+       }
+
+       g_set_error (
+               error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+               "Unknown element <%s>", element_name);
+
+       return;
+
+invalid_content:
+
+       g_set_error (
+               error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+               "Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_signature_xml_end_element (GMarkupParseContext *context,
+                                         const gchar *element_name,
+                                         gpointer user_data,
+                                         GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (g_strcmp0 (element_name, "filename") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_FILENAME) {
+                       parse_data->state = PARSE_STATE_IN_SIGNATURE;
+
+                       return;
+               }
+       }
+
+       if (g_strcmp0 (element_name, "signature") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_SIGNATURE) {
+                       parse_data->state = PARSE_STATE_IN_SIGNATURES_VALUE;
+
+                       /* Clean up <signature> tag data. */
+
+                       /* The key file will be NULL if we decided to skip it.
+                        * e.g. A file with the same UID may already exist. */
+                       if (parse_data->key_file != NULL) {
+                               GError *local_error = NULL;
+
+                               if (!parse_data->skip)
+                                       migrate_parse_commit_changes (
+                                               parse_data->type,
+                                               parse_data->file,
+                                               parse_data->key_file,
+                                               NULL, &local_error);
+
+                               if (local_error != NULL) {
+                                       g_printerr (
+                                               "  FAILED: %s\n",
+                                               local_error->message);
+                                       g_error_free (local_error);
+                               }
+
+                               g_key_file_free (parse_data->key_file);
+                               parse_data->key_file = NULL;
+                       }
+
+                       if (parse_data->file != NULL) {
+                               g_object_unref (parse_data->file);
+                               parse_data->file = NULL;
+                       }
+
+                       parse_data->skip = FALSE;
+
+                       return;
+               }
+       }
+}
+
+static void
+migrate_parse_signature_xml_text (GMarkupParseContext *context,
+                                  const gchar *text,
+                                  gsize text_len,
+                                  gpointer user_data,
+                                  GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (parse_data->state == PARSE_STATE_IN_FILENAME) {
+               GFile *old_signature_file;
+               GFile *new_signature_file;
+               const gchar *data_dir;
+               gchar *absolute_path;
+
+               /* Note we're moving the signature files
+                * from $XDG_DATA_HOME to $XDG_CONFIG_HOME. */
+               data_dir = e_get_user_data_dir ();
+
+               /* Text should be either an absolute file name
+                * or a base file name with no path components. */
+               if (g_path_is_absolute (text))
+                       absolute_path = g_strdup (text);
+               else
+                       absolute_path = g_build_filename (
+                               data_dir, "signatures", text, NULL);
+
+               old_signature_file = g_file_new_for_path (absolute_path);
+               new_signature_file = parse_data->signature_file;
+               parse_data->signature_file = NULL;
+
+               /* If the signature is a script, we symlink to it.
+                * Otherwise we move and rename the regular file. */
+               if (parse_data->is_script)
+                       g_file_make_symbolic_link (
+                               new_signature_file,
+                               absolute_path, NULL, error);
+               else
+                       g_file_move (
+                               old_signature_file,
+                               new_signature_file,
+                               G_FILE_COPY_NONE,
+                               NULL, NULL, NULL, error);
+
+               g_object_unref (old_signature_file);
+               g_object_unref (new_signature_file);
+               g_free (absolute_path);
+       }
+}
+
+static GMarkupParser signature_xml_parser = {
+       migrate_parse_signature_xml_start_element,
+       migrate_parse_signature_xml_end_element,
+       migrate_parse_signature_xml_text,
+       NULL,  /* passthrough */
+       NULL   /* error */
+};
+
+static void
+migrate_parse_local_calendar_property (ParseData *parse_data,
+                                       const gchar *property_name,
+                                       const gchar *property_value)
+{
+       if (g_strcmp0 (property_name, "custom-file") == 0) {
+               gchar *uri;
+
+               /* Property value is a local filename.  Convert it to a
+                * "file://" URI.
+                *
+                * Note: The key is named "CustomFile" instead of, say,
+                * "CustomURI" because the corresponding ESourceExtension
+                * property is a GFile.  The fact that ESource saves GFile
+                * properties as URI strings is an implementation detail. */
+               uri = g_filename_to_uri (property_value, NULL, NULL);
+               if (uri != NULL) {
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_LOCAL_BACKEND,
+                               "CustomFile", uri);
+                       g_free (uri);
+               }
+       }
+}
+
+static void
+migrate_parse_local_source (ParseData *parse_data)
+{
+       if (parse_data->type != PARSE_TYPE_ADDRESSBOOK)
+               parse_data->property_func =
+                       migrate_parse_local_calendar_property;
+
+       /* Local ADDRESS BOOK Backend has no special properties to parse. */
+}
+
+static void
+migrate_parse_caldav_property (ParseData *parse_data,
+                               const gchar *property_name,
+                               const gchar *property_value)
+{
+       if (g_strcmp0 (property_name, "autoschedule") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+                       "CalendarAutoSchedule",
+                       is_true (property_value));
+
+       } else if (g_strcmp0 (property_name, "usermail") == 0) {
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+                       "EmailAddress", property_value);
+       }
+}
+
+static void
+migrate_parse_caldav_source (ParseData *parse_data)
+{
+       if (parse_data->soup_uri->host != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Host", parse_data->soup_uri->host);
+
+       /* We may override this later if we see an "ssl" property. */
+       if (parse_data->soup_uri->port == 0)
+               parse_data->soup_uri->port = 80;
+
+       g_key_file_set_integer (
+               parse_data->key_file,
+               E_SOURCE_EXTENSION_AUTHENTICATION,
+               "Port", parse_data->soup_uri->port);
+
+       if (parse_data->soup_uri->user != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "User", parse_data->soup_uri->user);
+
+       if (parse_data->soup_uri->path != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+                       "ResourcePath", parse_data->soup_uri->path);
+
+       parse_data->property_func = migrate_parse_caldav_property;
+}
+
+static void
+migrate_parse_google_calendar_property (ParseData *parse_data,
+                                        const gchar *property_name,
+                                        const gchar *property_value)
+{
+       if (g_strcmp0 (property_name, "username") == 0) {
+               gchar *path;
+
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "User", property_value);
+
+               path = g_strdup_printf (
+                       "/calendar/dav/%s/events", property_value);
+
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+                       "ResourcePath", path);
+
+               g_free (path);
+       }
+}
+
+static void
+migrate_parse_google_contacts_property (ParseData *parse_data,
+                                        const gchar *property_name,
+                                        const gchar *property_value)
+{
+       if (g_strcmp0 (property_name, "refresh-interval") == 0) {
+               guint64 interval_seconds;
+
+               interval_seconds =
+                       g_ascii_strtoull (property_value, NULL, 10);
+
+               if (interval_seconds >= 60) {
+                       g_key_file_set_boolean (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_REFRESH,
+                               "Enabled", TRUE);
+                       g_key_file_set_uint64 (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_REFRESH,
+                               "IntervalMinutes",
+                               interval_seconds / 60);
+               }
+
+       } else if (g_strcmp0 (property_name, "username") == 0) {
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "User", property_value);
+       }
+}
+
+static void
+migrate_parse_google_source (ParseData *parse_data)
+{
+       if (parse_data->type == PARSE_TYPE_ADDRESSBOOK)
+               parse_data->property_func =
+                       migrate_parse_google_contacts_property;
+
+       else {
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Host", "www.google.com");
+
+               g_key_file_set_integer (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Port", 443);
+
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_SECURITY,
+                       "Method", "tls");
+
+               parse_data->property_func =
+                       migrate_parse_google_calendar_property;
+       }
+}
+
+static void
+migrate_parse_ldap_property (ParseData *parse_data,
+                             const gchar *property_name,
+                             const gchar *property_value)
+{
+       if (g_strcmp0 (property_name, "can-browse") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_LDAP_BACKEND,
+                       "CanBrowse",
+                       is_true (property_value));
+
+       /* This is an integer value, but we can use the string as is. */
+       } else if (g_strcmp0 (property_name, "limit") == 0) {
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_LDAP_BACKEND,
+                       "Limit", property_value);
+       }
+}
+
+static void
+migrate_parse_ldap_source (ParseData *parse_data)
+{
+       if (parse_data->soup_uri->host != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Host", parse_data->soup_uri->host);
+
+       if (parse_data->soup_uri->port != 0)
+               g_key_file_set_integer (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Port", parse_data->soup_uri->port);
+
+       if (parse_data->soup_uri->user != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "User", parse_data->soup_uri->user);
+
+       /* Skip the leading slash on the URI path to get the RootDn. */
+       if (parse_data->soup_uri->path != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_LDAP_BACKEND,
+                       "RootDn", parse_data->soup_uri->path + 1);
+
+       if (g_strcmp0 (parse_data->soup_uri->query, "?sub?") == 0)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_LDAP_BACKEND,
+                       "Scope", "subtree");
+
+       if (g_strcmp0 (parse_data->soup_uri->query, "?one?") == 0)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_LDAP_BACKEND,
+                       "Scope", "onelevel");
+
+       parse_data->property_func = migrate_parse_ldap_property;
+}
+
+static void
+migrate_parse_vcf_source (ParseData *parse_data)
+{
+       if (parse_data->soup_uri->path != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_VCF_BACKEND,
+                       "Path", parse_data->soup_uri->path);
+
+       /* VCF Backend has no special properties to parse. */
+}
+
+static void
+migrate_parse_weather_property (ParseData *parse_data,
+                                const gchar *property_name,
+                                const gchar *property_value)
+{
+       /* XXX Temperature property was replaced by units... I think. */
+       if (g_strcmp0 (property_name, "temperature") == 0) {
+               gboolean metric;
+
+               metric = (g_strcmp0 (property_value, "fahrenheit") != 0);
+
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEATHER_BACKEND,
+                       "Units", metric ? "metric" : "imperial");
+
+       } else if (g_strcmp0 (property_name, "units") == 0) {
+               gboolean metric;
+
+               metric = (g_strcmp0 (property_value, "metric") == 0);
+
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEATHER_BACKEND,
+                       "Units", metric ? "metric" : "imperial");
+       }
+}
+
+static void
+migrate_parse_weather_source (ParseData *parse_data)
+{
+       /* Oh man, we actually try to shove a weather location into
+        * a URI!  The station code winds up as the host component,
+        * and the location name winds up as the path component. */
+       if (parse_data->soup_uri->host != NULL) {
+               gchar *location;
+
+               if (parse_data->soup_uri->path != NULL)
+                       location = g_strconcat (
+                               parse_data->soup_uri->host,
+                               parse_data->soup_uri->path, NULL);
+               else
+                       location = g_strdup (parse_data->soup_uri->host);
+
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEATHER_BACKEND,
+                       "Location", location);
+
+               g_free (location);
+       }
+
+
+       parse_data->property_func = migrate_parse_weather_property;
+}
+
+static void
+migrate_parse_webcal_source (ParseData *parse_data)
+{
+       if (parse_data->soup_uri->host != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Host", parse_data->soup_uri->host);
+
+       /* We may override this later if we see an "ssl" property. */
+       if (parse_data->soup_uri->port == 0)
+               parse_data->soup_uri->port = 80;
+
+       g_key_file_set_integer (
+               parse_data->key_file,
+               E_SOURCE_EXTENSION_AUTHENTICATION,
+               "Port", parse_data->soup_uri->port);
+
+       if (parse_data->soup_uri->user != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "User", parse_data->soup_uri->user);
+
+       if (parse_data->soup_uri->path != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+                       "ResourcePath", parse_data->soup_uri->path);
+
+       /* Webcal Backend has no special properties to parse. */
+}
+
+static void
+migrate_parse_webdav_property (ParseData *parse_data,
+                               const gchar *property_name,
+                               const gchar *property_value)
+{
+       if (g_strcmp0 (property_name, "avoid_ifmatch") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+                       "AvoidIfmatch",
+                       is_true (property_value));
+       }
+}
+
+static void
+migrate_parse_webdav_source (ParseData *parse_data)
+{
+       if (parse_data->soup_uri->host != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Host", parse_data->soup_uri->host);
+
+       if (parse_data->soup_uri->port != 0)
+               g_key_file_set_integer (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "Port", parse_data->soup_uri->port);
+
+       if (parse_data->soup_uri->user != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "User", parse_data->soup_uri->user);
+
+       if (parse_data->soup_uri->path != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+                       "ResourcePath", parse_data->soup_uri->path);
+
+       parse_data->property_func = migrate_parse_webdav_property;
+}
+
+static void
+migrate_parse_group (ParseData *parse_data,
+                     const gchar *element_name,
+                     const gchar **attribute_names,
+                     const gchar **attribute_values,
+                     GError **error)
+{
+       const gchar *base_uri;
+       gboolean success;
+
+       success = g_markup_collect_attributes (
+               element_name,
+               attribute_names,
+               attribute_values,
+               error,
+               G_MARKUP_COLLECT_STRING,
+               "uid", NULL,
+               G_MARKUP_COLLECT_STRING,
+               "name", NULL,
+               G_MARKUP_COLLECT_STRING,
+               "base_uri", &base_uri,
+               G_MARKUP_COLLECT_BOOLEAN |
+               G_MARKUP_COLLECT_OPTIONAL,
+               "readonly", NULL,
+               G_MARKUP_COLLECT_INVALID);
+
+       if (!success)
+               return;
+
+       /* Convert "file://" schemes to "local:". */
+       if (g_strcmp0 (base_uri, "file://") == 0)
+               base_uri = "local:";
+
+       parse_data->base_uri = g_strdup (base_uri);
+}
+
+static void
+migrate_parse_source (ParseData *parse_data,
+                      const gchar *element_name,
+                      const gchar **attribute_names,
+                      const gchar **attribute_values,
+                      GError **error)
+{
+       const gchar *uid;
+       const gchar *name;
+       const gchar *color_spec;
+       const gchar *group_name;
+       const gchar *absolute_uri;
+       const gchar *relative_uri;
+       gchar *backend_name;
+       gchar *parent_name;
+       gchar *uri_string;
+       gchar *cp;
+       gboolean success;
+       gboolean is_google_calendar;
+
+       success = g_markup_collect_attributes (
+               element_name,
+               attribute_names,
+               attribute_values,
+               error,
+               G_MARKUP_COLLECT_STRING,
+               "uid", &uid,
+               G_MARKUP_COLLECT_STRING,
+               "name", &name,
+               G_MARKUP_COLLECT_STRING |
+               G_MARKUP_COLLECT_OPTIONAL,
+               "color_spec", &color_spec,
+               G_MARKUP_COLLECT_STRING,
+               "relative_uri", &relative_uri,
+               G_MARKUP_COLLECT_STRING |
+               G_MARKUP_COLLECT_OPTIONAL,
+               "uri", &absolute_uri,
+               G_MARKUP_COLLECT_INVALID);
+
+       if (!success)
+               return;
+
+       /* Don't try and migrate the "system" sources, as
+        * we'll defer to the built-in "system-*" key files. */
+       if (g_strcmp0 (relative_uri, "system") == 0)
+               return;
+
+       /* Also skip any sources with a "contacts://" base URI, which
+        * should just be "Birthdays & Anniversaries".  We'll reset to
+        * the built-in key file. */
+       if (g_strcmp0 (parse_data->base_uri, "contacts://") == 0)
+               return;
+
+       /* Also skip any sources for groupware extensions, as these are
+        * no longer saved to disk.  We do have a mechanism in place for
+        * remembering the UIDs of memory-only sources so that cached
+        * data can be reused, but let's not bother with it here.  Let
+        * the sources be set up fresh. */
+       if (base_uri_is_groupware (parse_data->base_uri))
+               return;
+
+       switch (parse_data->type) {
+               case PARSE_TYPE_ADDRESSBOOK:
+                       group_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+                       break;
+               case PARSE_TYPE_CALENDAR:
+                       group_name = E_SOURCE_EXTENSION_CALENDAR;
+                       break;
+               case PARSE_TYPE_TASKS:
+                       group_name = E_SOURCE_EXTENSION_TASK_LIST;
+                       break;
+               case PARSE_TYPE_MEMOS:
+                       group_name = E_SOURCE_EXTENSION_MEMO_LIST;
+                       break;
+               default:
+                       g_return_if_reached ();
+       }
+
+       parse_data->file = e_server_side_source_new_user_file (uid);
+
+       /* If the file already exists, skip this source.  It may be that we
+        * already migrated it, in which case we don't want to overwrite it. */
+       if (g_file_query_exists (parse_data->file, NULL))
+               return;
+
+       parse_data->key_file = g_key_file_new ();
+
+       /* Trim ':' or '://' off the base_uri to get the backend name. */
+       backend_name = g_strdup (parse_data->base_uri);
+       if ((cp = strchr (backend_name, ':')) != NULL)
+               *cp = '\0';
+
+       /* The parent name is generally the backend name + "-stub". */
+       parent_name = g_strdup_printf ("%s-stub", backend_name);
+
+       g_key_file_set_string (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME,
+               "DisplayName", name);
+
+       g_key_file_set_string (
+               parse_data->key_file,
+               E_SOURCE_GROUP_NAME,
+               "Parent", parent_name);
+
+       if (color_spec != NULL)
+               g_key_file_set_string (
+                       parse_data->key_file, group_name,
+                       "Color", color_spec);
+
+       is_google_calendar =
+               (parse_data->type == PARSE_TYPE_CALENDAR) &&
+               (g_strcmp0 (parse_data->base_uri, "google://") == 0);
+
+       /* For Google Calendar sources we override the backend name. */
+       if (is_google_calendar)
+               g_key_file_set_string (
+                       parse_data->key_file, group_name,
+                       "BackendName", "caldav");
+       else
+               g_key_file_set_string (
+                       parse_data->key_file, group_name,
+                       "BackendName", backend_name);
+
+       g_free (backend_name);
+       g_free (parent_name);
+
+       /* Prefer absolute URIs over relative URIs.  All these
+        * other strange rules are for backward-compatibility. */
+       if (absolute_uri != NULL)
+               uri_string = g_strdup (absolute_uri);
+       else if (g_str_has_suffix (parse_data->base_uri, "/"))
+               uri_string = g_strconcat (
+                       parse_data->base_uri, relative_uri, NULL);
+       else if (g_strcmp0 (parse_data->base_uri, "local:") == 0)
+               uri_string = g_strconcat (
+                       parse_data->base_uri, relative_uri, NULL);
+       else
+               uri_string = g_strconcat (
+                       parse_data->base_uri, "/", relative_uri, NULL);
+
+       parse_data->soup_uri = soup_uri_new (uri_string);
+
+       /* Mangle the URI to not contain invalid characters.  We'll need
+        * this later to rename the source's cache and data directories. */
+       parse_data->mangled_uri = g_strdelimit (uri_string, ":/", '_');
+
+       /* g_strdelimit() modifies the input string in place, so ParseData
+        * now owns 'uri_string'.  Clear the pointer to emphasize that. */
+       uri_string = NULL;
+
+       if (parse_data->soup_uri == NULL) {
+               g_warning (
+                       "  Failed to parse source URI: %s",
+                       (absolute_uri != NULL) ? absolute_uri : relative_uri);
+               g_key_file_free (parse_data->key_file);
+               parse_data->key_file = NULL;
+               return;
+       }
+
+       if (g_strcmp0 (parse_data->base_uri, "local:") == 0)
+               migrate_parse_local_source (parse_data);
+
+       else if (g_strcmp0 (parse_data->base_uri, "caldav://") == 0)
+               migrate_parse_caldav_source (parse_data);
+
+       else if (g_strcmp0 (parse_data->base_uri, "google://") == 0)
+               migrate_parse_google_source (parse_data);
+
+       else if (g_strcmp0 (parse_data->base_uri, "ldap://") == 0)
+               migrate_parse_ldap_source (parse_data);
+
+       else if (g_strcmp0 (parse_data->base_uri, "vcf://") == 0)
+               migrate_parse_vcf_source (parse_data);
+
+       else if (g_strcmp0 (parse_data->base_uri, "weather://") == 0)
+               migrate_parse_weather_source (parse_data);
+
+       else if (g_strcmp0 (parse_data->base_uri, "webcal://") == 0)
+               migrate_parse_webcal_source (parse_data);
+
+       else if (g_strcmp0 (parse_data->base_uri, "webdav://") == 0)
+               migrate_parse_webdav_source (parse_data);
+
+       migrate_keyring_entry (
+               uid,
+               parse_data->soup_uri->user,
+               parse_data->soup_uri->host,
+               parse_data->soup_uri->scheme);
+}
+
+static void
+migrate_parse_property (ParseData *parse_data,
+                        const gchar *element_name,
+                        const gchar **attribute_names,
+                        const gchar **attribute_values,
+                        GError **error)
+{
+       const gchar *property_name;
+       const gchar *property_value;
+       gboolean success;
+
+       success = g_markup_collect_attributes (
+               element_name,
+               attribute_names,
+               attribute_values,
+               error,
+               G_MARKUP_COLLECT_STRING,
+               "name", &property_name,
+               G_MARKUP_COLLECT_STRING,
+               "value", &property_value,
+               G_MARKUP_COLLECT_INVALID);
+
+       if (!success)
+               return;
+
+       if (g_strcmp0 (property_name, "alarm") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_ALARMS,
+                       "IncludeMe",
+                       is_true (property_value));
+
+       } else if (g_strcmp0 (property_name, "auth") == 0) {
+               if (is_true (property_value))
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_AUTHENTICATION,
+                               "Method", "plain/password");
+               else if (is_false (property_value))
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_AUTHENTICATION,
+                               "Method", "none");
+               else
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_AUTHENTICATION,
+                               "Method", property_value);
+
+       } else if (g_strcmp0 (property_name, "completion") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTOCOMPLETE,
+                       "IncludeMe",
+                       is_true (property_value));
+
+       /* If we see a "goa-account-id" property, skip the entire
+        * source and let the online-accounts module recreate it. */
+       } else if (g_strcmp0 (property_name, "goa-account-id") == 0) {
+               parse_data->skip = TRUE;
+
+       } else if (g_strcmp0 (property_name, "last-notified") == 0) {
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_ALARMS,
+                       "LastNotified", property_value);
+
+       } else if (g_strcmp0 (property_name, "offline_sync") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_OFFLINE,
+                       "StaySynchronized",
+                       is_true (property_value));
+
+       } else if (g_strcmp0 (property_name, "refresh") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_REFRESH,
+                       "Enabled", TRUE);
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_REFRESH,
+                       "IntervalMinutes", property_value);
+
+       } else if (g_strcmp0 (property_name, "remember_password") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                       "RememberPassword",
+                       is_true (property_value));
+
+       } else if (g_strcmp0 (property_name, "ssl") == 0) {
+               if (is_true (property_value))
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_SECURITY,
+                               "Method", "tls");
+               else if (is_false (property_value))
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_SECURITY,
+                               "Method", "none");
+
+               /* These next two are LDAP-specific. */
+               else if (g_strcmp0 (property_value, "always") == 0)
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_SECURITY,
+                               "Method", "starttls");
+               else if (g_strcmp0 (property_value, "whenever_possible") == 0)
+                       g_key_file_set_string (
+                               parse_data->key_file,
+                               E_SOURCE_EXTENSION_SECURITY,
+                               "Method", "ldaps");
+
+               /* For WebDAV-based backends we set the port to 80
+                * (http://) by default.  If we see that and we're
+                * using a secure connection, bump the port to 443
+                * (https://). */
+               if (parse_data->soup_uri->port == 80)
+                       if (is_true (property_value))
+                               g_key_file_set_integer (
+                                       parse_data->key_file,
+                                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                                       "Port", 443);
+
+       } else if (g_strcmp0 (property_name, "use_ssl") == 0) {
+               g_key_file_set_string (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_SECURITY,
+                       "Method",
+                       is_true (property_value) ?
+                       "tls" : "none");
+
+               /* For WebDAV-based backends we set the port to 80
+                * (http://) by default.  If we see that and we're
+                * using a secure connection, bump the port to 443
+                * (https://). */
+               if (parse_data->soup_uri->port == 80)
+                       if (is_true (property_value))
+                               g_key_file_set_integer (
+                                       parse_data->key_file,
+                                       E_SOURCE_EXTENSION_AUTHENTICATION,
+                                       "Port", 443);
+
+       } else if (g_strcmp0 (property_name, "use-in-contacts-calendar") == 0) {
+               g_key_file_set_boolean (
+                       parse_data->key_file,
+                       E_SOURCE_EXTENSION_CONTACTS_BACKEND,
+                       "IncludeMe",
+                       is_true (property_value));
+
+       } else if (parse_data->property_func != NULL) {
+               parse_data->property_func (
+                       parse_data, property_name, property_value);
+       }
+}
+
+static void
+migrate_parse_source_xml_start_element (GMarkupParseContext *context,
+                                        const gchar *element_name,
+                                        const gchar **attribute_names,
+                                        const gchar **attribute_values,
+                                        gpointer user_data,
+                                        GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (g_strcmp0 (element_name, "group") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_SOURCES_VALUE)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_GROUP;
+
+               migrate_parse_group (
+                       parse_data,
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "source") == 0) {
+               if (parse_data->state != PARSE_STATE_IN_GROUP)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_SOURCE;
+
+               migrate_parse_source (
+                       parse_data,
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error);
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "properties") == 0) {
+               /* Disregard group properties, we're only
+                * interested in source properties. */
+               if (parse_data->state == PARSE_STATE_IN_GROUP)
+                       return;
+
+               if (parse_data->state != PARSE_STATE_IN_SOURCE)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_PROPERTIES;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "property") == 0) {
+               /* Disregard group properties, we're only
+                * interested in source properties. */
+               if (parse_data->state == PARSE_STATE_IN_GROUP)
+                       return;
+
+               if (parse_data->state != PARSE_STATE_IN_PROPERTIES)
+                       goto invalid_content;
+
+               /* The key file will be NULL if we decided to skip it.
+                * e.g. A file with the same UID may already exist. */
+               if (parse_data->key_file != NULL)
+                       migrate_parse_property (
+                               parse_data,
+                               element_name,
+                               attribute_names,
+                               attribute_values,
+                               error);
+
+               return;
+       }
+
+       g_set_error (
+               error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+               "Unknown element <%s>", element_name);
+
+       return;
+
+invalid_content:
+
+       g_set_error (
+               error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+               "Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_source_xml_end_element (GMarkupParseContext *context,
+                                      const gchar *element_name,
+                                      gpointer user_data,
+                                      GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (g_strcmp0 (element_name, "group") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_GROUP) {
+                       parse_data->state = PARSE_STATE_IN_SOURCES_VALUE;
+
+                       /* Clean up <group> tag data. */
+
+                       g_free (parse_data->base_uri);
+                       parse_data->base_uri = NULL;
+               }
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "source") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_SOURCE) {
+                       parse_data->state = PARSE_STATE_IN_GROUP;
+
+                       /* Clean up <source> tag data. */
+
+                       /* The key file will be NULL if we decided to skip it.
+                        * e.g. A file with the same UID may already exist. */
+                       if (parse_data->key_file != NULL) {
+                               GError *local_error = NULL;
+
+                               if (!parse_data->skip)
+                                       migrate_parse_commit_changes (
+                                               parse_data->type,
+                                               parse_data->file,
+                                               parse_data->key_file,
+                                               parse_data->mangled_uri,
+                                               &local_error);
+
+                               if (local_error != NULL) {
+                                       g_printerr (
+                                               "  FAILED: %s\n",
+                                               local_error->message);
+                                       g_error_free (local_error);
+                               }
+
+                               g_key_file_free (parse_data->key_file);
+                               parse_data->key_file = NULL;
+                       }
+
+                       if (parse_data->file != NULL) {
+                               g_object_unref (parse_data->file);
+                               parse_data->file = NULL;
+                       }
+
+                       g_free (parse_data->mangled_uri);
+                       parse_data->mangled_uri = NULL;
+
+                       if (parse_data->soup_uri != NULL) {
+                               soup_uri_free (parse_data->soup_uri);
+                               parse_data->soup_uri = NULL;
+                       }
+
+                       parse_data->property_func = NULL;
+
+                       parse_data->skip = FALSE;
+               }
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "properties") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_PROPERTIES)
+                       parse_data->state = PARSE_STATE_IN_SOURCE;
+               return;
+       }
+}
+
+static GMarkupParser source_xml_parser = {
+       migrate_parse_source_xml_start_element,
+       migrate_parse_source_xml_end_element,
+       NULL,  /* text */
+       NULL,  /* passthrough */
+       NULL   /* error */
+};
+
+static void
+migrate_parse_gconf_xml_start_element (GMarkupParseContext *context,
+                                       const gchar *element_name,
+                                       const gchar **attribute_names,
+                                       const gchar **attribute_values,
+                                       gpointer user_data,
+                                       GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (g_strcmp0 (element_name, "gconf") == 0) {
+               if (parse_data->state != PARSE_STATE_INITIAL)
+                       goto invalid_content;
+
+               parse_data->state = PARSE_STATE_IN_GCONF;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "entry") == 0) {
+               const gchar *name;
+               gboolean success;
+
+               if (parse_data->state != PARSE_STATE_IN_GCONF)
+                       goto invalid_content;
+
+               success = g_markup_collect_attributes (
+                       element_name,
+                       attribute_names,
+                       attribute_values,
+                       error,
+                       G_MARKUP_COLLECT_STRING,
+                       "name", &name,
+                       G_MARKUP_COLLECT_STRING,
+                       "mtime", NULL,
+                       G_MARKUP_COLLECT_STRING |
+                       G_MARKUP_COLLECT_OPTIONAL,
+                       "type", NULL,
+                       G_MARKUP_COLLECT_STRING |
+                       G_MARKUP_COLLECT_OPTIONAL,
+                       "ltype", NULL,
+                       G_MARKUP_COLLECT_STRING |
+                       G_MARKUP_COLLECT_OPTIONAL,
+                       "schema", NULL,
+                       G_MARKUP_COLLECT_STRING |
+                       G_MARKUP_COLLECT_OPTIONAL,
+                       "value", NULL,
+                       G_MARKUP_COLLECT_INVALID);
+
+               if (success && g_strcmp0 (name, "accounts") == 0)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNTS_ENTRY;
+
+               if (success && g_strcmp0 (name, "signatues") == 0)
+                       parse_data->state = PARSE_STATE_IN_SIGNATURES_ENTRY;
+
+               if (success && g_strcmp0 (name, "sources") == 0)
+                       parse_data->state = PARSE_STATE_IN_SOURCES_ENTRY;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "li") == 0)
+               return;
+
+       if (g_strcmp0 (element_name, "stringvalue") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_ENTRY)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNTS_VALUE;
+
+               if (parse_data->state == PARSE_STATE_IN_SIGNATURES_ENTRY)
+                       parse_data->state = PARSE_STATE_IN_SIGNATURES_VALUE;
+
+               if (parse_data->state == PARSE_STATE_IN_SOURCES_ENTRY)
+                       parse_data->state = PARSE_STATE_IN_SOURCES_VALUE;
+
+               return;
+       }
+
+       g_set_error (
+               error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+               "Unknown element <%s>", element_name);
+
+       return;
+
+invalid_content:
+
+       g_set_error (
+               error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+               "Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_gconf_xml_end_element (GMarkupParseContext *context,
+                                     const gchar *element_name,
+                                     gpointer user_data,
+                                     GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       if (g_strcmp0 (element_name, "gconf") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_GCONF)
+                       parse_data->state = PARSE_STATE_INITIAL;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "entry") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_ENTRY)
+                       parse_data->state = PARSE_STATE_IN_GCONF;
+
+               if (parse_data->state == PARSE_STATE_IN_SIGNATURES_ENTRY)
+                       parse_data->state = PARSE_STATE_IN_GCONF;
+
+               if (parse_data->state == PARSE_STATE_IN_SOURCES_ENTRY)
+                       parse_data->state = PARSE_STATE_IN_GCONF;
+
+               return;
+       }
+
+       if (g_strcmp0 (element_name, "stringvalue") == 0) {
+               if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_VALUE)
+                       parse_data->state = PARSE_STATE_IN_ACCOUNTS_ENTRY;
+
+               if (parse_data->state == PARSE_STATE_IN_SIGNATURES_VALUE)
+                       parse_data->state = PARSE_STATE_IN_SIGNATURES_ENTRY;
+
+               if (parse_data->state == PARSE_STATE_IN_SOURCES_VALUE)
+                       parse_data->state = PARSE_STATE_IN_SOURCES_ENTRY;
+
+               return;
+       }
+}
+
+static void
+migrate_parse_gconf_xml_text (GMarkupParseContext *context,
+                              const gchar *text,
+                              gsize length,
+                              gpointer user_data,
+                              GError **error)
+{
+       ParseData *parse_data = user_data;
+
+       /* The account and signature data is encoded XML stuffed into
+        * GConf XML (yuck!).  Fortunately GMarkupParseContext decodes
+        * the XML for us, so we just have to feed it to a nested
+        * GMarkupParseContext. */
+
+       switch (parse_data->state) {
+               case PARSE_STATE_IN_ACCOUNTS_VALUE:
+                       context = g_markup_parse_context_new (
+                               &account_xml_parser, 0, parse_data, NULL);
+                       break;
+
+               case PARSE_STATE_IN_SIGNATURES_VALUE:
+                       context = g_markup_parse_context_new (
+                               &signature_xml_parser, 0, parse_data, NULL);
+                       break;
+
+               case PARSE_STATE_IN_SOURCES_VALUE:
+                       context = g_markup_parse_context_new (
+                               &source_xml_parser, 0, parse_data, NULL);
+                       break;
+
+               default:
+                       return;
+       }
+
+       if (g_markup_parse_context_parse (context, text, length, error))
+               g_markup_parse_context_end_parse (context, error);
+
+       g_markup_parse_context_free (context);
+}
+
+static GMarkupParser gconf_xml_parser = {
+       migrate_parse_gconf_xml_start_element,
+       migrate_parse_gconf_xml_end_element,
+       migrate_parse_gconf_xml_text,
+       NULL,  /* passthrough */
+       NULL   /* error */
+};
+
+static gboolean
+migrate_parse_gconf_xml (ParseType parse_type,
+                         const gchar *contents,
+                         gsize length,
+                         GError **error)
+{
+       GMarkupParseContext *context;
+       ParseData *parse_data;
+       gboolean success = FALSE;
+
+       parse_data = parse_data_new (parse_type);
+
+       context = g_markup_parse_context_new (
+               &gconf_xml_parser, 0, parse_data,
+               (GDestroyNotify) parse_data_free);
+
+       if (g_markup_parse_context_parse (context, contents, length, error))
+               if (g_markup_parse_context_end_parse (context, error))
+                       success = TRUE;
+
+       g_markup_parse_context_free (context);
+
+       return success;
+}
+
+static void
+migrate_remove_gconf_xml (const gchar *gconf_key,
+                          const gchar *gconf_xml)
+{
+       /* Remove the GConf string list so the user is not haunted by
+        * old data sources being resurrected from leftover GConf data.
+        * Also delete the %gconf.xml file itself.  If gconfd is running
+        * then it will just recreate the file from memory when it exits
+        * (which is why we invoke gconftool-2), otherwise the file will
+        * stay deleted. */
+
+       gchar *path_to_program;
+
+       path_to_program = g_find_program_in_path ("gconftool-2");
+
+       if (path_to_program != NULL) {
+               gchar *command_line;
+               GError *error = NULL;
+
+               command_line = g_strjoin (
+                       " ",
+                       path_to_program,
+                       "--set",
+                       "--type=list",
+                       "--list-type=string",
+                       gconf_key, "[]", NULL);
+
+               /* We don't really care if the command worked or not,
+                * just check that the program got spawned successfully. */
+               if (!g_spawn_command_line_async (command_line, &error)) {
+                       g_printerr (
+                               "Failed to spawn '%s': %s\n",
+                               path_to_program, error->message);
+                       g_error_free (error);
+               }
+
+               g_free (path_to_program);
+               g_free (command_line);
+       }
+
+       if (g_file_test (gconf_xml, G_FILE_TEST_IS_REGULAR)) {
+               if (g_remove (gconf_xml) == -1) {
+                       g_printerr (
+                               "Failed to remove '%s': %s\n",
+                               gconf_xml, g_strerror (errno));
+               }
+       }
+}
+
+void
+evolution_source_registry_migrate_sources (void)
+{
+       gchar *base_dir;
+       gchar *contents;
+       gchar *gconf_xml;
+       gsize length;
+       const gchar *gconf_key;
+       GError *error = NULL;
+
+       base_dir = g_build_filename (
+               g_get_home_dir (), ".gconf", "apps", "evolution", NULL);
+
+       /* ------------------------------------------------------------------*/
+
+       g_print ("Migrating mail accounts from GConf...\n");
+
+       gconf_xml = g_build_filename (
+               base_dir, "mail", "%gconf.xml", NULL);
+       g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+       if (error == NULL) {
+               migrate_parse_gconf_xml (
+                       PARSE_TYPE_MAIL,
+                       contents, length, &error);
+               g_free (contents);
+       }
+
+       if (error == NULL) {
+               gconf_key = "/apps/evolution/mail/accounts";
+               migrate_remove_gconf_xml (gconf_key, gconf_xml);
+       } else {
+               g_printerr ("  FAILED: %s\n", error->message);
+               g_clear_error (&error);
+       }
+
+       g_free (gconf_xml);
+
+       /* ------------------------------------------------------------------*/
+
+       g_print ("Migrating addressbook sources from GConf...\n");
+
+       gconf_xml = g_build_filename (
+               base_dir, "addressbook", "%gconf.xml", NULL);
+       g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+       if (error == NULL) {
+               migrate_parse_gconf_xml (
+                       PARSE_TYPE_ADDRESSBOOK,
+                       contents, length, &error);
+               g_free (contents);
+       }
+
+       if (error == NULL) {
+               gconf_key = "/apps/evolution/addressbook/sources";
+               migrate_remove_gconf_xml (gconf_key, gconf_xml);
+       } else {
+               g_printerr ("  FAILED: %s\n", error->message);
+               g_clear_error (&error);
+       }
+
+       g_free (gconf_xml);
+
+       /* ------------------------------------------------------------------*/
+
+       g_print ("Migrating calendar sources from GConf...\n");
+
+       gconf_xml = g_build_filename (
+               base_dir, "calendar", "%gconf.xml", NULL);
+       g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+       if (error == NULL) {
+               migrate_parse_gconf_xml (
+                       PARSE_TYPE_CALENDAR,
+                       contents, length, &error);
+               g_free (contents);
+       }
+
+       if (error == NULL) {
+               gconf_key = "/apps/evolution/calendar/sources";
+               migrate_remove_gconf_xml (gconf_key, gconf_xml);
+       } else {
+               g_printerr ("  FAILED: %s\n", error->message);
+               g_clear_error (&error);
+       }
+
+       g_free (gconf_xml);
+
+       /* ------------------------------------------------------------------*/
+
+       g_print ("Migrating task list sources from GConf...\n");
+
+       gconf_xml = g_build_filename (
+               base_dir, "tasks", "%gconf.xml", NULL);
+       g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+       if (error == NULL) {
+               migrate_parse_gconf_xml (
+                       PARSE_TYPE_TASKS,
+                       contents, length, &error);
+               g_free (contents);
+       }
+
+       if (error == NULL) {
+               gconf_key = "/apps/evolution/tasks/sources";
+               migrate_remove_gconf_xml (gconf_key, gconf_xml);
+       } else {
+               g_printerr ("  FAILED: %s\n", error->message);
+               g_clear_error (&error);
+       }
+
+       g_free (gconf_xml);
+
+       /* ------------------------------------------------------------------*/
+
+       g_print ("Migrating memo list sources from GConf...\n");
+
+       gconf_xml = g_build_filename (
+               base_dir, "memos", "%gconf.xml", NULL);
+       g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+       if (error == NULL) {
+               migrate_parse_gconf_xml (
+                       PARSE_TYPE_MEMOS,
+                       contents, length, &error);
+               g_free (contents);
+       }
+
+       if (error == NULL) {
+               gconf_key = "/apps/evolution/memos/sources";
+               migrate_remove_gconf_xml (gconf_key, gconf_xml);
+       } else {
+               g_printerr ("  FAILED: %s\n", error->message);
+               g_clear_error (&error);
+       }
+
+       g_free (gconf_xml);
+
+       /* ------------------------------------------------------------------*/
+
+       g_free (base_dir);
+}
diff --git a/services/evolution-source-registry/evolution-source-registry.c b/services/evolution-source-registry/evolution-source-registry.c
new file mode 100644 (file)
index 0000000..56b9a21
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * e-source-registry.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <config.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+
+#include <libebackend/e-source-registry-server.h>
+
+/* Forward Declarations */
+void evolution_source_registry_migrate_basedir (void);
+void evolution_source_registry_migrate_sources (void);
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       EDBusServer *server;
+       EDBusServerExitCode exit_code;
+       GError *error = NULL;
+
+       setlocale (LC_ALL, "");
+       bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+       bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+       g_type_init ();
+
+reload:
+       /* Migrate user data from ~/.evolution to XDG base directories. */
+       evolution_source_registry_migrate_basedir ();
+
+       /* Migrate ESource data from GConf XML blobs to key files.
+        * Do this AFTER XDG base directory migration since the key
+        * files are saved according to XDG base directory settings. */
+       evolution_source_registry_migrate_sources ();
+
+       server = e_source_registry_server_new ();
+
+       /* Failure here is fatal.  Don't even try to keep going. */
+       e_source_registry_server_load_all (
+               E_SOURCE_REGISTRY_SERVER (server), &error);
+
+       if (error != NULL) {
+               g_printerr ("%s\n", error->message);
+               g_object_unref (server);
+               exit (EXIT_FAILURE);
+       }
+
+       g_print ("Server is up and running...\n");
+
+       /* Keep the server from quitting on its own.
+        * We don't have a way of tracking number of
+        * active clients, so once the server is up,
+        * it's up until the session bus closes. */
+       e_dbus_server_hold (server);
+
+       exit_code = e_dbus_server_run (server, FALSE);
+
+       g_object_unref (server);
+
+       if (exit_code == E_DBUS_SERVER_EXIT_RELOAD) {
+               g_print ("Reloading...\n");
+               goto reload;
+       }
+
+       g_print ("Bye.\n");
+
+       return 0;
+}
diff --git a/services/evolution-source-registry/org.gnome.evolution.dataserver.Sources.service.in b/services/evolution-source-registry/org.gnome.evolution.dataserver.Sources.service.in
new file mode 100644 (file)
index 0000000..bc89d45
--- /dev/null
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=@SOURCES_DBUS_SERVICE_NAME@
+Exec=@libexecdir@/evolution-source-registry