[evolution-data-server/account-mgmt: 8/42] Add a new "evolution-source-registry" D-Bus service.
- From: Matthew Barnes <mbarnes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/account-mgmt: 8/42] Add a new "evolution-source-registry" D-Bus service.
- Date: Tue, 24 Apr 2012 23:10:16 +0000 (UTC)
commit b244a64b84d08501631ad9e26f51659fe5758fce
Author: Matthew Barnes <mbarnes redhat com>
Date: Thu Sep 29 16:08:20 2011 -0400
Add a new "evolution-source-registry" D-Bus service.
This new service manages data source key files and serves them to
clients, through the ESource and ESourceRegistry client-side APIs.
configure.ac | 31 +-
docs/reference/libebackend/libebackend-docs.xml | 5 +
.../reference/libebackend/libebackend-sections.txt | 144 +
docs/reference/libebackend/libebackend.types | 10 +
libebackend/Makefile.am | 19 +-
libebackend/e-backend-enums.h | 39 +-
libebackend/e-collection-backend-factory.c | 161 +
libebackend/e-collection-backend-factory.h | 88 +
libebackend/e-collection-backend.c | 525 ++++
libebackend/e-collection-backend.h | 92 +
libebackend/e-dbus-server.c | 6 +-
libebackend/e-dbus-source-authenticator.c | 1762 +++++++++++
libebackend/e-dbus-source-authenticator.h | 180 ++
libebackend/e-server-side-source.c | 1089 +++++++
libebackend/e-server-side-source.h | 87 +
libebackend/e-source-registry-server.c | 1675 ++++++++++
libebackend/e-source-registry-server.h | 132 +
services/Makefile.am | 1 +
services/evolution-addressbook-factory/Makefile.am | 5 +-
...evolution-addressbook-factory-migrate-basedir.c | 319 --
.../evolution-addressbook-factory.c | 6 -
services/evolution-calendar-factory/Makefile.am | 5 +-
.../evolution-calendar-factory.c | 6 -
services/evolution-source-registry/Makefile.am | 41 +
.../evolution-source-registry-migrate-basedir.c} | 79 +-
.../evolution-source-registry-migrate-sources.c | 3240 ++++++++++++++++++++
.../evolution-source-registry.c | 85 +
...g.gnome.evolution.dataserver.Sources.service.in | 3 +
28 files changed, 9484 insertions(+), 351 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 725cb6d..eb97f40 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
@@ -389,14 +396,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
@@ -1319,7 +1327,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
@@ -1489,6 +1497,12 @@ 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)
@@ -1502,7 +1516,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 ******************************
@@ -1618,6 +1632,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
diff --git a/docs/reference/libebackend/libebackend-docs.xml b/docs/reference/libebackend/libebackend-docs.xml
index 5ec2486..8e9d716 100644
--- a/docs/reference/libebackend/libebackend-docs.xml
+++ b/docs/reference/libebackend/libebackend-docs.xml
@@ -12,14 +12,19 @@
<title>Evolution-Data-Server Manual: Backend Utilities (libebackend)</title>
<xi:include href="xml/e-backend.xml"/>
<xi:include href="xml/e-backend-factory.xml"/>
+ <xi:include href="xml/e-collection-backend.xml"/>
+ <xi:include href="xml/e-collection-backend-factory.xml"/>
<xi:include href="xml/e-data-factory.xml"/>
<xi:include href="xml/e-dbus-server.xml"/>
+ <xi:include href="xml/e-dbus-source-authenticator.xml"/>
<xi:include href="xml/e-extensible.xml"/>
<xi:include href="xml/e-extension.xml"/>
<xi:include href="xml/e-module.xml"/>
<xi:include href="xml/e-file-cache.xml"/>
<xi:include href="xml/e-dbhash.xml"/>
<xi:include href="xml/e-db3-utils.xml"/>
+ <xi:include href="xml/e-server-side-source.xml"/>
+ <xi:include href="xml/e-source-registry-server.xml"/>
<xi:include href="xml/e-sqlite3-vfs.xml"/>
<xi:include href="xml/e-offline-listener.xml"/>
</chapter>
diff --git a/docs/reference/libebackend/libebackend-sections.txt b/docs/reference/libebackend/libebackend-sections.txt
index f1872c3..b14080c 100644
--- a/docs/reference/libebackend/libebackend-sections.txt
+++ b/docs/reference/libebackend/libebackend-sections.txt
@@ -38,6 +38,45 @@ e_backend_factory_get_type
</SECTION>
<SECTION>
+<FILE>e-collection-backend</FILE>
+<TITLE>ECollectionBackend</TITLE>
+ECollectionBackend
+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
@@ -81,6 +120,51 @@ e_dbus_server_exit_code_get_type
</SECTION>
<SECTION>
+<FILE>e-dbus-source-authenticator</FILE>
+<TITLE>EDBusSourceAuthenticator</TITLE>
+E_DBUS_SOURCE_AUTHENTICATOR_ERROR
+EDBusSourceAuthenticator
+EDBusSourceAuthenticatorResult
+e_dbus_source_authenticator_new
+e_dbus_source_authenticator_get_server
+e_dbus_source_authenticator_get_source_uid
+e_dbus_source_authenticator_get_prompt_title
+e_dbus_source_authenticator_set_prompt_title
+e_dbus_source_authenticator_get_prompt_message
+e_dbus_source_authenticator_set_prompt_message
+e_dbus_source_authenticator_get_prompt_description
+e_dbus_source_authenticator_set_prompt_description
+e_dbus_source_authenticator_export
+e_dbus_source_authenticator_initiate
+e_dbus_source_authenticator_dismiss
+e_dbus_source_authenticator_transmit
+e_dbus_source_authenticator_complete
+e_dbus_source_authenticator_store_password_sync
+e_dbus_source_authenticator_store_password
+e_dbus_source_authenticator_store_password_finish
+e_dbus_source_authenticator_lookup_password_sync
+e_dbus_source_authenticator_lookup_password
+e_dbus_source_authenticator_lookup_password_finish
+e_dbus_source_authenticator_delete_password_sync
+e_dbus_source_authenticator_delete_password
+e_dbus_source_authenticator_delete_password_finish
+<SUBSECTION Standard>
+E_DBUS_SOURCE_AUTHENTICATOR
+E_IS_DBUS_SOURCE_AUTHENTICATOR
+E_TYPE_DBUS_SOURCE_AUTHENTICATOR
+E_DBUS_SOURCE_AUTHENTICATOR_CLASS
+E_IS_DBUS_SOURCE_AUTHENTICATOR_CLASS
+E_DBUS_SOURCE_AUTHENTICATOR_GET_CLASS
+E_TYPE_DBUS_SOURCE_AUTHENTICATOR_RESULT
+EDBusSourceAuthenticatorClass
+<SUBSECTION Private>
+EDBusSourceAuthenticatorPrivate
+e_dbus_source_authenticator_error_quark
+e_dbus_source_authenticator_get_type
+e_dbus_source_authenticator_result_get_type
+</SECTION>
+
+<SECTION>
<FILE>e-file-cache</FILE>
<TITLE>EFileCache</TITLE>
EFileCache
@@ -209,6 +293,66 @@ 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
+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_add_authenticator
+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>
diff --git a/docs/reference/libebackend/libebackend.types b/docs/reference/libebackend/libebackend.types
index e23f54b..b8bf45f 100644
--- a/docs/reference/libebackend/libebackend.types
+++ b/docs/reference/libebackend/libebackend.types
@@ -1,19 +1,29 @@
#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-dbus-source-authenticator.h>
#include <libebackend/e-extensible.h>
#include <libebackend/e-extension.h>
#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_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_dbus_source_authenticator_get_type
e_extensible_get_type
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
diff --git a/libebackend/Makefile.am b/libebackend/Makefile.am
index 0b6c2a6..f5cdfbb 100644
--- a/libebackend/Makefile.am
+++ b/libebackend/Makefile.am
@@ -14,10 +14,15 @@ 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)
@@ -26,21 +31,28 @@ libebackend_1_2_la_SOURCES = \
$(BUILT_SOURCES) \
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-dbus-source-authenticator.c \
e-extensible.c \
e-extension.c \
e-offline-listener.c \
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)
@@ -55,14 +67,19 @@ libebackendinclude_HEADERS = \
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-dbus-source-authenticator.h \
e-extensible.h \
e-extension.h \
e-offline-listener.h \
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-backend-enums.h b/libebackend/e-backend-enums.h
index 4d06518..5150b52 100644
--- a/libebackend/e-backend-enums.h
+++ b/libebackend/e-backend-enums.h
@@ -41,5 +41,42 @@ typedef enum {
E_DBUS_SERVER_EXIT_RELOAD
} EDBusServerExitCode;
-#endif /* E_BACKEND_ENUMS_H */
+/**
+ * EDBusSourceAuthenticatorResult:
+ * @E_DBUS_SOURCE_AUTHENTICATOR_SUCCESSFUL:
+ * Client reported successful authentication.
+ * @E_DBUS_SOURCE_AUTHENTICATOR_DISMISSED:
+ * User dismissed the authentication prompt.
+ * @E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED:
+ * Client cancelled or failed to respond.
+ *
+ * Completion codes used by #EDBusSourceAuthenticator.
+ *
+ * Since: 3.6
+ **/
+typedef enum {
+ E_DBUS_SOURCE_AUTHENTICATOR_SUCCESSFUL,
+ E_DBUS_SOURCE_AUTHENTICATOR_DISMISSED,
+ E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED
+} EDBusSourceAuthenticatorResult;
+/**
+ * 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
index 0000000..f5aab6d
--- /dev/null
+++ b/libebackend/e-collection-backend-factory.c
@@ -0,0 +1,161 @@
+/*
+ * 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/>
+ *
+ */
+
+#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)
+{
+}
+
+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
index 0000000..c2a436f
--- /dev/null
+++ b/libebackend/e-collection-backend-factory.h
@@ -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
index 0000000..cafae0e
--- /dev/null
+++ b/libebackend/e-collection-backend.c
@@ -0,0 +1,525 @@
+/*
+ * 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/>
+ *
+ */
+
+#include "e-collection-backend.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 <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;
+ 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 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_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);
+
+ /* 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->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);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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
index 0000000..2fc9357
--- /dev/null
+++ b/libebackend/e-collection-backend.h
@@ -0,0 +1,92 @@
+/*
+ * 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;
+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 */
+
diff --git a/libebackend/e-dbus-server.c b/libebackend/e-dbus-server.c
index 917ea44..d21c61c 100644
--- a/libebackend/e-dbus-server.c
+++ b/libebackend/e-dbus-server.c
@@ -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-dbus-source-authenticator.c b/libebackend/e-dbus-source-authenticator.c
new file mode 100644
index 0000000..abff7c7
--- /dev/null
+++ b/libebackend/e-dbus-source-authenticator.c
@@ -0,0 +1,1762 @@
+/*
+ * e-dbus-source-authenticator.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 "e-dbus-source-authenticator.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-backend-enumtypes.h>
+#include <libebackend/e-server-side-source.h>
+#include <libebackend/e-source-registry-server.h>
+
+#define E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_DBUS_SOURCE_AUTHENTICATOR, EDBusSourceAuthenticatorPrivate))
+
+/* 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 goes dark. */
+#define INACTIVITY_TIMEOUT (3 * 60) /* in seconds */
+
+/* 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 _EDBusSourceAuthenticatorPrivate {
+ EDBusAuthenticator *interface;
+ GcrSecretExchange *secret_exchange;
+ ESourceRegistryServer *server;
+ GCancellable *cancellable;
+ GcrPrompt *system_prompt;
+ gulong quit_server_handler_id;
+ gboolean client_ready;
+ gboolean server_ready;
+ gchar *source_uid;
+ gchar *password;
+ guint timeout_id;
+
+ /* These are for configuring system prompts. */
+ gchar *prompt_title;
+ gchar *prompt_message;
+ gchar *prompt_description;
+};
+
+struct _AsyncContext {
+ gchar *password;
+ gboolean permanently;
+};
+
+enum {
+ PROP_0,
+ PROP_PROMPT_DESCRIPTION,
+ PROP_PROMPT_MESSAGE,
+ PROP_PROMPT_TITLE,
+ PROP_SERVER,
+ PROP_SOURCE_UID
+};
+
+enum {
+ COMPLETE,
+ LAST_SIGNAL
+};
+
+static GnomeKeyringPasswordSchema schema = {
+ GNOME_KEYRING_ITEM_GENERIC_SECRET,
+ {
+ { KEYRING_ITEM_ATTRIBUTE_NAME,
+ GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+ { NULL, 0 }
+ }
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* Forward Declarations */
+static void dbus_source_authenticator_msg
+ (EDBusSourceAuthenticator *authenticator,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+G_DEFINE_TYPE (
+ EDBusSourceAuthenticator,
+ e_dbus_source_authenticator,
+ G_TYPE_OBJECT)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ g_free (async_context->password);
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+dbus_source_authenticator_msg (EDBusSourceAuthenticator *authenticator,
+ const gchar *format,
+ ...)
+{
+ GString *buffer;
+ GDBusInterfaceSkeleton *interface;
+ const gchar *object_path;
+ va_list args;
+
+ buffer = g_string_sized_new (256);
+
+ /* Show a truncated object path. */
+ interface = G_DBUS_INTERFACE_SKELETON (authenticator->priv->interface);
+ object_path = g_dbus_interface_skeleton_get_object_path (interface);
+ object_path += strlen (E_SOURCE_REGISTRY_SERVER_OBJECT_PATH);
+
+ g_string_append_printf (buffer, "AUTH (%s): ", object_path);
+
+ 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
+dbus_source_authenticator_maybe_initiate (EDBusSourceAuthenticator *authenticator)
+{
+ EDBusSourceAuthenticatorClass *class;
+
+ if (!authenticator->priv->client_ready)
+ return;
+
+ if (!authenticator->priv->server_ready)
+ return;
+
+ class = E_DBUS_SOURCE_AUTHENTICATOR_GET_CLASS (authenticator);
+ g_return_if_fail (class->initiate != NULL);
+
+ dbus_source_authenticator_msg (authenticator, "Initiated");
+
+ class->initiate (authenticator);
+}
+
+static void
+dbus_source_authenticator_system_prompt_close_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EDBusSourceAuthenticator *authenticator;
+ GcrSystemPrompt *system_prompt;
+ GError *error = NULL;
+
+ system_prompt = GCR_SYSTEM_PROMPT (source_object);
+ authenticator = E_DBUS_SOURCE_AUTHENTICATOR (user_data);
+
+ gcr_system_prompt_close_finish (system_prompt, result, &error);
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free (error);
+
+ /* The system prompt is unusable now regardless of the close
+ * result, but leave a breadcrumb as evidence that something
+ * went wrong. */
+ } else if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ /* This wipes the password from memory. */
+ gcr_secure_memory_strfree (authenticator->priv->password);
+ authenticator->priv->password = NULL;
+
+ /* The system prompt should finalize after we return. */
+ g_object_unref (authenticator->priv->system_prompt);
+ authenticator->priv->system_prompt = NULL;
+
+ g_object_unref (authenticator);
+}
+
+static void
+dbus_source_authenticator_prompt_password_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EDBusSourceAuthenticator *authenticator;
+ GcrPrompt *system_prompt;
+ const gchar *password;
+ GError *error = NULL;
+
+ system_prompt = GCR_PROMPT (source_object);
+ authenticator = E_DBUS_SOURCE_AUTHENTICATOR (user_data);
+
+ password = gcr_prompt_password_finish (system_prompt, result, &error);
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_warn_if_fail (password == NULL);
+ g_error_free (error);
+ goto close;
+
+ /* XXX This may not be the best way to handle errors here,
+ * but we don't want to just leave the client hanging,
+ * so act like the user dismissed the system prompt. */
+ } else if (error != NULL) {
+ g_warn_if_fail (password == NULL);
+ e_dbus_source_authenticator_dismiss (authenticator);
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ goto close;
+
+ /* Check for an actual system prompt dismissal. */
+ } else if (password == NULL) {
+ e_dbus_source_authenticator_dismiss (authenticator);
+ goto close;
+ }
+
+ /* Stash the password internally so we can store
+ * it in the keyring after the client verifies it. */
+ gcr_secure_memory_strfree (authenticator->priv->password);
+ authenticator->priv->password = gcr_secure_memory_strdup (password);
+
+ /* Relay the password to the client and wait for a response. */
+ e_dbus_source_authenticator_transmit (authenticator, password);
+
+ g_object_unref (authenticator);
+
+ return;
+
+close:
+ /* Ref and unref the authenticator just as a
+ * reminder that we are holding a reference. */
+
+ gcr_system_prompt_close_async (
+ GCR_SYSTEM_PROMPT (system_prompt),
+ authenticator->priv->cancellable,
+ dbus_source_authenticator_system_prompt_close_cb,
+ g_object_ref (authenticator));
+
+ g_object_unref (authenticator);
+}
+
+static void
+dbus_source_authenticator_system_prompt_open_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EDBusSourceAuthenticator *authenticator;
+ GcrPrompt *system_prompt;
+ const gchar *label;
+ GError *error = NULL;
+
+ authenticator = E_DBUS_SOURCE_AUTHENTICATOR (user_data);
+
+ system_prompt = gcr_system_prompt_open_finish (result, &error);
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_warn_if_fail (system_prompt == NULL);
+ g_object_unref (authenticator);
+ return;
+
+ /* XXX This may not be the best way to handle errors here,
+ * but we don't want to just leave the client hanging,
+ * so act like the user dismissed the system prompt. */
+ } else if (error != NULL) {
+ g_warn_if_fail (system_prompt == NULL);
+ e_dbus_source_authenticator_dismiss (authenticator);
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_object_unref (authenticator);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (GCR_IS_PROMPT (system_prompt));
+
+ authenticator->priv->system_prompt = system_prompt;
+
+ /* Configure and dispatch a password system prompt. */
+
+ g_object_bind_property (
+ authenticator, "prompt-title",
+ system_prompt, "title",
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ authenticator, "prompt-message",
+ system_prompt, "message",
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ authenticator, "prompt-description",
+ system_prompt, "description",
+ G_BINDING_SYNC_CREATE);
+
+ label = _("Add this password to your keyring");
+ gcr_prompt_set_choice_label (system_prompt, label);
+ gcr_prompt_set_choice_chosen (system_prompt, TRUE);
+
+ /* Ref and unref the authenticator just as a
+ * reminder that we are holding a reference. */
+
+ gcr_prompt_password_async (
+ authenticator->priv->system_prompt,
+ authenticator->priv->cancellable,
+ dbus_source_authenticator_prompt_password_cb,
+ g_object_ref (authenticator));
+
+ g_object_unref (authenticator);
+}
+
+static void
+dbus_source_authenticator_password_lookup_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EDBusSourceAuthenticator *authenticator;
+ ESourceRegistryServer *server;
+ ESource *source;
+ gboolean allow_auth_prompt;
+ gchar *password = NULL;
+ const gchar *uid;
+ GError *error = NULL;
+
+ authenticator = E_DBUS_SOURCE_AUTHENTICATOR (source_object);
+
+ e_dbus_source_authenticator_lookup_password_finish (
+ authenticator, result, &password, &error);
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ return;
+
+ /* If we failed to talk to the secret service, I guess we have
+ * to prompt the user now. Leave a breadcrumb as evidence that
+ * something went wrong. */
+ } else if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ /* 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 (password != NULL) {
+ e_dbus_source_authenticator_transmit (authenticator, password);
+ g_free (password);
+ return;
+ }
+
+ server = e_dbus_source_authenticator_get_server (authenticator);
+ uid = e_dbus_source_authenticator_get_source_uid (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) {
+ 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) {
+ e_dbus_source_authenticator_dismiss (authenticator);
+ return;
+ }
+
+ /* Request a system prompt. */
+ gcr_system_prompt_open_async (
+ SYSTEM_PROMPT_TIMEOUT,
+ authenticator->priv->cancellable,
+ dbus_source_authenticator_system_prompt_open_cb,
+ g_object_ref (authenticator));
+}
+
+static gboolean
+dbus_source_authenticator_timeout_cb (EDBusSourceAuthenticator *authenticator)
+{
+ EDBusSourceAuthenticatorResult result;
+
+ authenticator->priv->timeout_id = 0;
+
+ dbus_source_authenticator_msg (authenticator, "Timed out");
+
+ result = E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED;
+ e_dbus_source_authenticator_complete (authenticator, result);
+
+ return FALSE;
+}
+
+static void
+dbus_source_authenticator_start_timeout (EDBusSourceAuthenticator *authenticator)
+{
+ /* An authentication session starts when a D-Bus object path is
+ * exported with an Authenticator interface (that's us) and the
+ * client calls its 'Ready' method. The session then ping pongs
+ * between 'Authenticate' signals TO the client and 'Accepted'
+ * or 'Rejected' method calls FROM the client.
+ *
+ * For each 'ping' we start a countdown timer on the order of
+ * several minutes. If the client does not 'pong' (respond to)
+ * us before the countdown timer expires, we assume the client
+ * has terminated and we close the authentication session. */
+
+ /* Timer should not already be running. */
+ g_warn_if_fail (authenticator->priv->timeout_id == 0);
+
+ authenticator->priv->timeout_id = g_timeout_add_seconds (
+ INACTIVITY_TIMEOUT, (GSourceFunc)
+ dbus_source_authenticator_timeout_cb,
+ authenticator);
+}
+
+static void
+dbus_source_authenticator_authenticate_cb (EDBusAuthenticator *interface,
+ const gchar *encrypted_secret,
+ EDBusSourceAuthenticator *authenticator)
+{
+ dbus_source_authenticator_start_timeout (authenticator);
+}
+
+static gboolean
+dbus_source_authenticator_handle_ready (EDBusAuthenticator *interface,
+ GDBusMethodInvocation *invocation,
+ const gchar *encryption_key,
+ EDBusSourceAuthenticator *authenticator)
+{
+ GcrSecretExchange *secret_exchange;
+
+ /* Client responded, cancel the timeout. */
+ if (authenticator->priv->timeout_id > 0) {
+ g_source_remove (authenticator->priv->timeout_id);
+ authenticator->priv->timeout_id = 0;
+ }
+
+ secret_exchange = authenticator->priv->secret_exchange;
+ gcr_secret_exchange_receive (secret_exchange, encryption_key);
+
+ /* This method is idempotent. */
+ if (!authenticator->priv->client_ready) {
+ authenticator->priv->client_ready = TRUE;
+ dbus_source_authenticator_maybe_initiate (authenticator);
+ }
+
+ e_dbus_authenticator_complete_ready (interface, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+dbus_source_authenticator_handle_cancel (EDBusAuthenticator *interface,
+ GDBusMethodInvocation *invocation,
+ EDBusSourceAuthenticator *authenticator)
+{
+ /* Client responded, cancel the timeout. */
+ if (authenticator->priv->timeout_id > 0) {
+ g_source_remove (authenticator->priv->timeout_id);
+ authenticator->priv->timeout_id = 0;
+ }
+
+ dbus_source_authenticator_msg (authenticator, "Client cancelled");
+
+ e_dbus_source_authenticator_complete (
+ authenticator, E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED);
+
+ e_dbus_authenticator_complete_cancel (interface, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+dbus_source_authenticator_handle_accepted (EDBusAuthenticator *interface,
+ GDBusMethodInvocation *invocation,
+ EDBusSourceAuthenticator *authenticator)
+{
+ /* Client responded, cancel the timeout. */
+ if (authenticator->priv->timeout_id > 0) {
+ g_source_remove (authenticator->priv->timeout_id);
+ authenticator->priv->timeout_id = 0;
+ }
+
+ /* The system prompt may not have been opened yet if
+ * we first tried a cached password from the keyring. */
+ if (authenticator->priv->system_prompt != NULL) {
+ GcrPrompt *system_prompt;
+ const gchar *password;
+ gboolean permanently;
+
+ /* We should already have a password stashed away. */
+ password = authenticator->priv->password;
+ g_return_val_if_fail (password != NULL, FALSE);
+
+ system_prompt = authenticator->priv->system_prompt;
+ permanently = gcr_prompt_get_choice_chosen (system_prompt);
+
+ /* Store the password in the default or session keyring.
+ * We don't really care if this works or not, so a callback
+ * function is unnecessary. But still do it asynchronously
+ * in case the secret service is unresponsive. */
+ e_dbus_source_authenticator_store_password (
+ authenticator, password, permanently,
+ G_PRIORITY_LOW, NULL, NULL, NULL);
+
+ gcr_system_prompt_close_async (
+ GCR_SYSTEM_PROMPT (system_prompt),
+ authenticator->priv->cancellable,
+ dbus_source_authenticator_system_prompt_close_cb,
+ g_object_ref (authenticator));
+ }
+
+ e_dbus_source_authenticator_complete (
+ authenticator, E_DBUS_SOURCE_AUTHENTICATOR_SUCCESSFUL);
+
+ e_dbus_authenticator_complete_accepted (interface, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+dbus_source_authenticator_handle_rejected (EDBusAuthenticator *interface,
+ GDBusMethodInvocation *invocation,
+ EDBusSourceAuthenticator *authenticator)
+{
+ /* Client responded, cancel the timeout. */
+ if (authenticator->priv->timeout_id > 0) {
+ g_source_remove (authenticator->priv->timeout_id);
+ authenticator->priv->timeout_id = 0;
+ }
+
+ /* The cached password is bad so delete it from the keyring.
+ * We don't really care if this works or not, so a callback
+ * function is unnecessary. But still do it asynchronously
+ * in case the secret service is unresponsive. */
+ e_dbus_source_authenticator_delete_password (
+ authenticator, G_PRIORITY_LOW, NULL, NULL, NULL);
+
+ /* The system prompt may not have been opened yet if
+ * we first tried a cached password from the keyring. */
+ if (authenticator->priv->system_prompt != NULL) {
+ GcrPrompt *system_prompt;
+ const gchar *warning_message;
+
+ warning_message = _("Password was incorrect");
+ system_prompt = authenticator->priv->system_prompt;
+ gcr_prompt_set_warning (system_prompt, warning_message);
+
+ gcr_prompt_password_async (
+ system_prompt,
+ authenticator->priv->cancellable,
+ dbus_source_authenticator_prompt_password_cb,
+ g_object_ref (authenticator));
+ } else {
+ gcr_system_prompt_open_async (
+ SYSTEM_PROMPT_TIMEOUT,
+ authenticator->priv->cancellable,
+ dbus_source_authenticator_system_prompt_open_cb,
+ g_object_ref (authenticator));
+ }
+
+ e_dbus_authenticator_complete_rejected (interface, invocation);
+
+ return TRUE;
+}
+
+static void
+dbus_source_authenticator_quit_server_cb (EDBusServer *server,
+ EDBusServerExitCode code,
+ EDBusAuthenticator *authenticator)
+{
+ dbus_source_authenticator_msg (
+ E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+ "Server quitting");
+
+ e_dbus_source_authenticator_complete (
+ E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+ E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED);
+}
+
+static void
+dbus_source_authenticator_set_server (EDBusSourceAuthenticator *authenticator,
+ ESourceRegistryServer *server)
+{
+ g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+ g_return_if_fail (authenticator->priv->server == NULL);
+
+ authenticator->priv->server = g_object_ref (server);
+}
+
+static void
+dbus_source_authenticator_set_source_uid (EDBusSourceAuthenticator *authenticator,
+ const gchar *source_uid)
+{
+ g_return_if_fail (source_uid != NULL);
+ g_return_if_fail (authenticator->priv->source_uid == NULL);
+
+ authenticator->priv->source_uid = g_strdup (source_uid);
+}
+
+static void
+dbus_source_authenticator_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PROMPT_DESCRIPTION:
+ e_dbus_source_authenticator_set_prompt_description (
+ E_DBUS_SOURCE_AUTHENTICATOR (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PROMPT_MESSAGE:
+ e_dbus_source_authenticator_set_prompt_message (
+ E_DBUS_SOURCE_AUTHENTICATOR (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PROMPT_TITLE:
+ e_dbus_source_authenticator_set_prompt_title (
+ E_DBUS_SOURCE_AUTHENTICATOR (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_SERVER:
+ dbus_source_authenticator_set_server (
+ E_DBUS_SOURCE_AUTHENTICATOR (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SOURCE_UID:
+ dbus_source_authenticator_set_source_uid (
+ E_DBUS_SOURCE_AUTHENTICATOR (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+dbus_source_authenticator_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PROMPT_DESCRIPTION:
+ g_value_set_string (
+ value,
+ e_dbus_source_authenticator_get_prompt_description (
+ E_DBUS_SOURCE_AUTHENTICATOR (object)));
+ return;
+
+ case PROP_PROMPT_MESSAGE:
+ g_value_set_string (
+ value,
+ e_dbus_source_authenticator_get_prompt_message (
+ E_DBUS_SOURCE_AUTHENTICATOR (object)));
+ return;
+
+ case PROP_PROMPT_TITLE:
+ g_value_set_string (
+ value,
+ e_dbus_source_authenticator_get_prompt_title (
+ E_DBUS_SOURCE_AUTHENTICATOR (object)));
+ return;
+
+ case PROP_SERVER:
+ g_value_set_object (
+ value,
+ e_dbus_source_authenticator_get_server (
+ E_DBUS_SOURCE_AUTHENTICATOR (object)));
+ return;
+
+ case PROP_SOURCE_UID:
+ g_value_set_string (
+ value,
+ e_dbus_source_authenticator_get_source_uid (
+ E_DBUS_SOURCE_AUTHENTICATOR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+dbus_source_authenticator_dispose (GObject *object)
+{
+ EDBusSourceAuthenticatorPrivate *priv;
+
+ priv = E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (object);
+
+ 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;
+ }
+
+ if (priv->server != NULL) {
+ g_signal_handler_disconnect (
+ priv->server, priv->quit_server_handler_id);
+ g_object_unref (priv->server);
+ priv->server = NULL;
+ }
+
+ if (priv->cancellable != NULL) {
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ }
+
+ if (priv->system_prompt != NULL) {
+ g_object_unref (priv->system_prompt);
+ priv->system_prompt = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_dbus_source_authenticator_parent_class)->
+ dispose (object);
+}
+
+static void
+dbus_source_authenticator_finalize (GObject *object)
+{
+ EDBusSourceAuthenticatorPrivate *priv;
+
+ priv = E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (object);
+
+ if (priv->timeout_id > 0)
+ g_source_remove (priv->timeout_id);
+
+ /* This wipes the password from memory. */
+ gcr_secure_memory_strfree (priv->password);
+
+ 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_dbus_source_authenticator_parent_class)->
+ finalize (object);
+}
+
+static void
+dbus_source_authenticator_constructed (GObject *object)
+{
+ EDBusSourceAuthenticator *authenticator;
+ ESourceRegistryServer *server;
+ gulong handler_id;
+
+ authenticator = E_DBUS_SOURCE_AUTHENTICATOR (object);
+ server = e_dbus_source_authenticator_get_server (authenticator);
+
+ handler_id = g_signal_connect (
+ server, "quit-server",
+ G_CALLBACK (dbus_source_authenticator_quit_server_cb),
+ authenticator);
+
+ authenticator->priv->quit_server_handler_id = handler_id;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_dbus_source_authenticator_parent_class)->
+ constructed (object);
+}
+
+static void
+dbus_source_authenticator_initiate (EDBusSourceAuthenticator *authenticator)
+{
+ /* This method will not be called until both server and
+ * client report ready, so we don't have to worry about
+ * any of that synchronization stuff here. */
+
+ g_warn_if_fail (authenticator->priv->cancellable == NULL);
+ authenticator->priv->cancellable = g_cancellable_new ();
+
+ e_dbus_source_authenticator_lookup_password (
+ authenticator, G_PRIORITY_DEFAULT,
+ authenticator->priv->cancellable,
+ dbus_source_authenticator_password_lookup_cb, NULL);
+}
+
+static void
+dbus_source_authenticator_complete (EDBusSourceAuthenticator *authenticator,
+ EDBusSourceAuthenticatorResult result)
+{
+ if (result == E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED)
+ g_cancellable_cancel (authenticator->priv->cancellable);
+}
+
+static void
+e_dbus_source_authenticator_class_init (EDBusSourceAuthenticatorClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EDBusSourceAuthenticatorPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = dbus_source_authenticator_set_property;
+ object_class->get_property = dbus_source_authenticator_get_property;
+ object_class->dispose = dbus_source_authenticator_dispose;
+ object_class->finalize = dbus_source_authenticator_finalize;
+ object_class->constructed = dbus_source_authenticator_constructed;
+
+ class->initiate = dbus_source_authenticator_initiate;
+ class->complete = dbus_source_authenticator_complete;
+
+ 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 authenticator 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));
+
+ signals[COMPLETE] = g_signal_new (
+ "complete",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EDBusSourceAuthenticatorClass, complete),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ E_TYPE_DBUS_SOURCE_AUTHENTICATOR_RESULT);
+}
+
+static void
+e_dbus_source_authenticator_init (EDBusSourceAuthenticator *authenticator)
+{
+ authenticator->priv =
+ E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+ authenticator->priv->interface = e_dbus_authenticator_skeleton_new ();
+ authenticator->priv->secret_exchange = gcr_secret_exchange_new (NULL);
+
+ /* Start the timeout immediately. */
+
+ dbus_source_authenticator_start_timeout (authenticator);
+
+ /* The authenticate signal also starts the timeout. */
+
+ g_signal_connect (
+ authenticator->priv->interface, "authenticate",
+ G_CALLBACK (dbus_source_authenticator_authenticate_cb),
+ authenticator);
+
+ /* These methods cancel the timeout and do additional stuff. */
+
+ g_signal_connect (
+ authenticator->priv->interface, "handle-ready",
+ G_CALLBACK (dbus_source_authenticator_handle_ready),
+ authenticator);
+
+ g_signal_connect (
+ authenticator->priv->interface, "handle-cancel",
+ G_CALLBACK (dbus_source_authenticator_handle_cancel),
+ authenticator);
+
+ g_signal_connect (
+ authenticator->priv->interface, "handle-accepted",
+ G_CALLBACK (dbus_source_authenticator_handle_accepted),
+ authenticator);
+
+ g_signal_connect (
+ authenticator->priv->interface, "handle-rejected",
+ G_CALLBACK (dbus_source_authenticator_handle_rejected),
+ authenticator);
+}
+
+GQuark
+e_dbus_source_authenticator_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0)) {
+ const gchar *string;
+ string = "e-dbus-source-authenticator-error-quark";
+ quark = g_quark_from_static_string (string);
+ }
+
+ return quark;
+}
+
+/**
+ * e_dbus_source_authenticator_new:
+ * @server: an #ESourceRegistryServer
+ * @source_uid: a data source identifier
+ *
+ * Creates a new #EDBusSourceAuthenticator instance for @server.
+ *
+ * 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 #EDBusSourceAuthenticator
+ *
+ * Since: 3.6
+ **/
+EDBusSourceAuthenticator *
+e_dbus_source_authenticator_new (ESourceRegistryServer *server,
+ const gchar *source_uid)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
+ g_return_val_if_fail (source_uid != NULL, NULL);
+
+ return g_object_new (
+ E_TYPE_DBUS_SOURCE_AUTHENTICATOR,
+ "server", server, "source-uid", source_uid, NULL);
+}
+
+ESourceRegistryServer *
+e_dbus_source_authenticator_get_server (EDBusSourceAuthenticator *authenticator)
+{
+ g_return_val_if_fail (
+ E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+ return authenticator->priv->server;
+}
+
+const gchar *
+e_dbus_source_authenticator_get_source_uid (EDBusSourceAuthenticator *authenticator)
+{
+ g_return_val_if_fail (
+ E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+ return authenticator->priv->source_uid;
+}
+
+/**
+ * e_dbus_source_authenticator_get_prompt_title:
+ * @authenticator: an #EDBusSourceAuthenticator
+ *
+ * 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_dbus_source_authenticator_get_prompt_title (EDBusSourceAuthenticator *authenticator)
+{
+ g_return_val_if_fail (
+ E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+ return authenticator->priv->prompt_title;
+}
+
+/**
+ * e_dbus_source_authenticator_set_prompt_title:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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_dbus_source_authenticator_set_prompt_title (EDBusSourceAuthenticator *authenticator,
+ const gchar *prompt_title)
+{
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ g_free (authenticator->priv->prompt_title);
+ authenticator->priv->prompt_title = g_strdup (prompt_title);
+
+ g_object_notify (G_OBJECT (authenticator), "prompt-title");
+}
+
+/**
+ * e_dbus_source_authenticator_get_prompt_message:
+ * @authenticator: an #EDBusSourceAuthenticator
+ *
+ * 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_dbus_source_authenticator_get_prompt_message (EDBusSourceAuthenticator *authenticator)
+{
+ g_return_val_if_fail (
+ E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+ return authenticator->priv->prompt_message;
+}
+
+/**
+ * e_dbus_source_authenticator_set_prompt_message:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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_dbus_source_authenticator_set_prompt_message (EDBusSourceAuthenticator *authenticator,
+ const gchar *prompt_message)
+{
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ g_free (authenticator->priv->prompt_message);
+ authenticator->priv->prompt_message = g_strdup (prompt_message);
+
+ g_object_notify (G_OBJECT (authenticator), "prompt-message");
+}
+
+/**
+ * e_dbus_source_authenticator_get_prompt_description:
+ * @authenticator: an #EDBusSourceAuthenticator
+ *
+ * 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_dbus_source_authenticator_get_prompt_description (EDBusSourceAuthenticator *authenticator)
+{
+ g_return_val_if_fail (
+ E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+ return authenticator->priv->prompt_description;
+}
+
+/**
+ * e_dbus_source_authenticator_set_prompt_description:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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_dbus_source_authenticator_set_prompt_description (EDBusSourceAuthenticator *authenticator,
+ const gchar *prompt_description)
+{
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ g_free (authenticator->priv->prompt_description);
+ authenticator->priv->prompt_description = g_strdup (prompt_description);
+
+ g_object_notify (G_OBJECT (authenticator), "prompt-description");
+}
+
+gboolean
+e_dbus_source_authenticator_export (EDBusSourceAuthenticator *authenticator,
+ GDBusConnection *connection,
+ const gchar *object_path,
+ GError **error)
+{
+ EDBusAuthenticator *interface;
+
+ g_return_val_if_fail (
+ E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), FALSE);
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+ g_return_val_if_fail (object_path != NULL, FALSE);
+
+ interface = authenticator->priv->interface;
+
+ return g_dbus_interface_skeleton_export (
+ G_DBUS_INTERFACE_SKELETON (interface),
+ connection, object_path, error);
+}
+
+void
+e_dbus_source_authenticator_initiate (EDBusSourceAuthenticator *authenticator)
+{
+ /* Note, we delay invoking the class method until the client
+ * has also indicated it's ready. This way the subclass can
+ * start taking action directly in its initiate() method. */
+
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ /* This function is idempotent. */
+ if (!authenticator->priv->server_ready) {
+ authenticator->priv->server_ready = TRUE;
+ dbus_source_authenticator_maybe_initiate (authenticator);
+ }
+}
+
+void
+e_dbus_source_authenticator_dismiss (EDBusSourceAuthenticator *authenticator)
+{
+ ESourceRegistryServer *server;
+ ESource *source;
+ const gchar *uid;
+
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ dbus_source_authenticator_msg (authenticator, "Dismissed");
+
+ e_dbus_authenticator_emit_dismissed (authenticator->priv->interface);
+
+ server = e_dbus_source_authenticator_get_server (authenticator);
+ uid = e_dbus_source_authenticator_get_source_uid (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 until this is reset. */
+ e_server_side_source_set_allow_auth_prompt (
+ E_SERVER_SIDE_SOURCE (source), FALSE);
+ g_object_unref (source);
+ }
+
+ /* XXX This may finalize the authenticator. Wonder if we
+ * should defer this call to an idle source to avoid
+ * potential lifecycle issues with signal handlers. */
+ e_dbus_source_authenticator_complete (
+ authenticator, E_DBUS_SOURCE_AUTHENTICATOR_DISMISSED);
+}
+
+void
+e_dbus_source_authenticator_transmit (EDBusSourceAuthenticator *authenticator,
+ const gchar *unencrypted_secret)
+{
+ gchar *encrypted_secret;
+
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ encrypted_secret = gcr_secret_exchange_send (
+ authenticator->priv->secret_exchange, unencrypted_secret, -1);
+
+ dbus_source_authenticator_msg (authenticator, "Authenticate");
+
+ e_dbus_authenticator_emit_authenticate (
+ authenticator->priv->interface, encrypted_secret);
+
+ g_free (encrypted_secret);
+}
+
+void
+e_dbus_source_authenticator_complete (EDBusSourceAuthenticator *authenticator,
+ EDBusSourceAuthenticatorResult result)
+{
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ switch (result) {
+ case E_DBUS_SOURCE_AUTHENTICATOR_SUCCESSFUL:
+ dbus_source_authenticator_msg (
+ authenticator, "Complete (SUCCESSFUL)");
+ break;
+ case E_DBUS_SOURCE_AUTHENTICATOR_DISMISSED:
+ dbus_source_authenticator_msg (
+ authenticator, "Complete (DISMISSED)");
+ break;
+ case E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED:
+ dbus_source_authenticator_msg (
+ authenticator, "Complete (CANCELLED)");
+ break;
+ default:
+ g_warn_if_reached ();
+ }
+
+ g_signal_emit (authenticator, signals[COMPLETE], 0, result);
+}
+
+/* Helper for e_dbus_source_authenticator_store_password() */
+static void
+dbus_source_authenticator_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_dbus_source_authenticator_store_password_sync (
+ E_DBUS_SOURCE_AUTHENTICATOR (object),
+ async_context->password,
+ async_context->permanently,
+ cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_dbus_source_authenticator_store_sync:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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 @authenticator 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_dbus_source_authenticator_store_password_sync (EDBusSourceAuthenticator *authenticator,
+ 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_DBUS_SOURCE_AUTHENTICATOR (authenticator), 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_dbus_source_authenticator_get_source_uid (authenticator);
+ 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_DBUS_SOURCE_AUTHENTICATOR_ERROR,
+ result, gnome_keyring_result_to_message (result));
+ }
+
+ g_free (display_name);
+
+ return (result == GNOME_KEYRING_RESULT_OK);
+}
+
+/**
+ * e_dbus_source_authenticator_store_password:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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 @authenticator
+ * 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_dbus_source_authenticator_store_password_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_authenticator_store_password (EDBusSourceAuthenticator *authenticator,
+ 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_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+ 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 (authenticator), callback, user_data,
+ e_dbus_source_authenticator_store_password);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, async_context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, dbus_source_authenticator_store_password_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+/**
+ * e_dbus_source_authenticator_store_password_finish:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finished the operation started with
+ * e_dbus_source_authenticator_store_password().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_authenticator_store_password_finish (EDBusSourceAuthenticator *authenticator,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (authenticator),
+ e_dbus_source_authenticator_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_dbus_source_authenticator_store_password() */
+static void
+dbus_source_authenticator_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_dbus_source_authenticator_lookup_password_sync (
+ E_DBUS_SOURCE_AUTHENTICATOR (object), cancellable,
+ &async_context->password, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_dbus_source_authenticator_lookup_password_sync:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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 @authenticator 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_dbus_source_authenticator_lookup_password_sync (EDBusSourceAuthenticator *authenticator,
+ GCancellable *cancellable,
+ gchar **password,
+ GError **error)
+{
+ GnomeKeyringResult result;
+ const gchar *uid;
+ gchar *temp = NULL;
+
+ g_return_val_if_fail (
+ E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), 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_dbus_source_authenticator_get_source_uid (authenticator);
+
+ 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_DBUS_SOURCE_AUTHENTICATOR_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_dbus_source_authenticator_lookup_password:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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 @authenticator
+ * is representing. Both the default and session keyrings are queried.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call e_dbus_source_authenticator_lookup_password_finish() to get the
+ * result of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_authenticator_lookup_password (EDBusSourceAuthenticator *authenticator,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *async_context;
+
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ async_context = g_slice_new0 (AsyncContext);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (authenticator), callback, user_data,
+ e_dbus_source_authenticator_lookup_password);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, async_context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, dbus_source_authenticator_lookup_password_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+/**
+ * e_dbus_source_authenticator_lookup_password_finish:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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_dbus_source_authenticator_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_dbus_source_authenticator_lookup_password_finish (EDBusSourceAuthenticator *authenticator,
+ 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 (authenticator),
+ e_dbus_source_authenticator_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_dbus_source_authenticator_delete_password() */
+static void
+dbus_source_authenticator_delete_password_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+
+ e_dbus_source_authenticator_delete_password_sync (
+ E_DBUS_SOURCE_AUTHENTICATOR (object), cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_dbus_source_authenticator_delete_password_sync:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Deletes the password for the data source that @authenticator 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_dbus_source_authenticator_delete_password_sync (EDBusSourceAuthenticator *authenticator,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GnomeKeyringResult result;
+ const gchar *uid;
+
+ g_return_val_if_fail (
+ E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), 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_dbus_source_authenticator_get_source_uid (authenticator);
+
+ 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_DBUS_SOURCE_AUTHENTICATOR_ERROR,
+ result, gnome_keyring_result_to_message (result));
+ }
+
+ return (result == GNOME_KEYRING_RESULT_OK);
+}
+
+/**
+ * e_dbus_source_authenticator_delete_password:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @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 @authenticator
+ * is representing from either the default keyring or session keyring.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call e_dbus_source_authenticator_delete_password_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_authenticator_delete_password (EDBusSourceAuthenticator *authenticator,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (authenticator), callback, user_data,
+ e_dbus_source_authenticator_delete_password);
+
+ g_simple_async_result_run_in_thread (
+ simple, dbus_source_authenticator_delete_password_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+/**
+ * e_dbus_source_authenticator_delete_password_finish:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * e_dbus_source_authenticator_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_dbus_source_authenticator_delete_password_finish (EDBusSourceAuthenticator *authenticator,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (authenticator),
+ e_dbus_source_authenticator_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-dbus-source-authenticator.h b/libebackend/e-dbus-source-authenticator.h
new file mode 100644
index 0000000..4a82a65
--- /dev/null
+++ b/libebackend/e-dbus-source-authenticator.h
@@ -0,0 +1,180 @@
+/*
+ * e-dbus-source-authenticator.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_DBUS_SOURCE_AUTHENTICATOR_H
+#define E_DBUS_SOURCE_AUTHENTICATOR_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>
+
+/* Standard GObject macros */
+#define E_TYPE_DBUS_SOURCE_AUTHENTICATOR \
+ (e_dbus_source_authenticator_get_type ())
+#define E_DBUS_SOURCE_AUTHENTICATOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_DBUS_SOURCE_AUTHENTICATOR, EDBusSourceAuthenticator))
+#define E_DBUS_SOURCE_AUTHENTICATOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_DBUS_SOURCE_AUTHENTICATOR, EDBusSourceAuthenticatorClass))
+#define E_IS_DBUS_SOURCE_AUTHENTICATOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_DBUS_SOURCE_AUTHENTICATOR))
+#define E_IS_DBUS_SOURCE_AUTHENTICATOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_DBUS_SOURCE_AUTHENTICATOR))
+#define E_DBUS_SOURCE_AUTHENTICATOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_DBUS_SOURCE_AUTHENTICATOR, EDBusSourceAuthenticatorClass))
+
+/**
+ * E_DBUS_SOURCE_AUTHENTICATOR_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_DBUS_SOURCE_AUTHENTICATOR_ERROR \
+ (e_dbus_source_authenticator_error_quark ())
+
+G_BEGIN_DECLS
+
+struct _ESourceRegistryServer;
+
+typedef struct _EDBusSourceAuthenticator EDBusSourceAuthenticator;
+typedef struct _EDBusSourceAuthenticatorClass EDBusSourceAuthenticatorClass;
+typedef struct _EDBusSourceAuthenticatorPrivate EDBusSourceAuthenticatorPrivate;
+
+struct _EDBusSourceAuthenticator {
+ GObject parent;
+ EDBusSourceAuthenticatorPrivate *priv;
+};
+
+struct _EDBusSourceAuthenticatorClass {
+ GObjectClass parent_class;
+
+ /* Methods */
+ void (*initiate) (EDBusSourceAuthenticator *authenticator);
+
+ /* Signals */
+ void (*complete) (EDBusSourceAuthenticator *authenticator,
+ EDBusSourceAuthenticatorResult result);
+};
+
+GQuark e_dbus_source_authenticator_error_quark
+ (void) G_GNUC_CONST;
+GType e_dbus_source_authenticator_get_type
+ (void) G_GNUC_CONST;
+EDBusSourceAuthenticator *
+ e_dbus_source_authenticator_new
+ (struct _ESourceRegistryServer *server,
+ const gchar *source_uid);
+struct _ESourceRegistryServer *
+ e_dbus_source_authenticator_get_server
+ (EDBusSourceAuthenticator *authenticator);
+const gchar * e_dbus_source_authenticator_get_source_uid
+ (EDBusSourceAuthenticator *authenticator);
+const gchar * e_dbus_source_authenticator_get_prompt_title
+ (EDBusSourceAuthenticator *authenticator);
+void e_dbus_source_authenticator_set_prompt_title
+ (EDBusSourceAuthenticator *authenticator,
+ const gchar *prompt_title);
+const gchar * e_dbus_source_authenticator_get_prompt_message
+ (EDBusSourceAuthenticator *authenticator);
+void e_dbus_source_authenticator_set_prompt_message
+ (EDBusSourceAuthenticator *authenticator,
+ const gchar *prompt_message);
+const gchar * e_dbus_source_authenticator_get_prompt_description
+ (EDBusSourceAuthenticator *authenticator);
+void e_dbus_source_authenticator_set_prompt_description
+ (EDBusSourceAuthenticator *authenticator,
+ const gchar *prompt_description);
+gboolean e_dbus_source_authenticator_export
+ (EDBusSourceAuthenticator *authenticator,
+ GDBusConnection *connection,
+ const gchar *object_path,
+ GError **error);
+void e_dbus_source_authenticator_initiate
+ (EDBusSourceAuthenticator *authenticator);
+void e_dbus_source_authenticator_dismiss
+ (EDBusSourceAuthenticator *authenticator);
+void e_dbus_source_authenticator_transmit
+ (EDBusSourceAuthenticator *authenticator,
+ const gchar *unencrypted_secret);
+void e_dbus_source_authenticator_complete
+ (EDBusSourceAuthenticator *authenticator,
+ EDBusSourceAuthenticatorResult result);
+gboolean e_dbus_source_authenticator_store_password_sync
+ (EDBusSourceAuthenticator *authenticator,
+ const gchar *password,
+ gboolean permanently,
+ GCancellable *cancellable,
+ GError **error);
+void e_dbus_source_authenticator_store_password
+ (EDBusSourceAuthenticator *authenticator,
+ const gchar *password,
+ gboolean permanently,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_dbus_source_authenticator_store_password_finish
+ (EDBusSourceAuthenticator *authenticator,
+ GAsyncResult *result,
+ GError **error);
+gboolean e_dbus_source_authenticator_lookup_password_sync
+ (EDBusSourceAuthenticator *authenticator,
+ GCancellable *cancellable,
+ gchar **password,
+ GError **error);
+void e_dbus_source_authenticator_lookup_password
+ (EDBusSourceAuthenticator *authenticator,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_dbus_source_authenticator_lookup_password_finish
+ (EDBusSourceAuthenticator *authenticator,
+ GAsyncResult *result,
+ gchar **password,
+ GError **error);
+gboolean e_dbus_source_authenticator_delete_password_sync
+ (EDBusSourceAuthenticator *authenticator,
+ GCancellable *cancellable,
+ GError **error);
+void e_dbus_source_authenticator_delete_password
+ (EDBusSourceAuthenticator *authenticator,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_dbus_source_authenticator_delete_password_finish
+ (EDBusSourceAuthenticator *authenticator,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_DBUS_SOURCE_AUTHENTICATOR_H */
+
diff --git a/libebackend/e-server-side-source.c b/libebackend/e-server-side-source.c
new file mode 100644
index 0000000..180c07a
--- /dev/null
+++ b/libebackend/e-server-side-source.c
@@ -0,0 +1,1089 @@
+/*
+ * 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/>
+ *
+ */
+
+#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;
+
+ /* 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_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_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_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_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->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_constructed (GObject *object)
+{
+ EServerSideSource *source;
+ GDBusObject *dbus_object;
+ EDBusSource *dbus_source;
+ GFile *file;
+ gchar *uid;
+
+ source = E_SERVER_SIDE_SOURCE (object);
+ file = e_server_side_source_get_file (source);
+
+ dbus_source = e_dbus_source_skeleton_new ();
+
+ 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);
+
+ if (file != NULL)
+ uid = g_file_get_basename (file);
+ else
+ uid = e_uid_new ();
+ e_dbus_source_set_uid (dbus_source, uid);
+ g_free (uid);
+
+ g_signal_connect (
+ dbus_source, "handle-allow-auth-prompt",
+ G_CALLBACK (server_side_source_allow_auth_prompt_cb), source);
+
+ g_object_unref (dbus_source);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_server_side_source_parent_class)->
+ constructed (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);
+
+ /* 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);
+
+ 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 *basename;
+ gchar *filename;
+ const gchar *dirname;
+
+ dirname = e_server_side_source_get_user_dir ();
+
+ basename = g_file_get_basename (priv->file);
+ filename = g_build_filename (dirname, basename, NULL);
+ file = g_file_new_for_path (filename);
+ g_free (filename);
+ g_free (basename);
+
+ 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;
+
+ source = E_SERVER_SIDE_SOURCE (initable);
+
+ 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;
+ object_class->constructed = server_side_source_constructed;
+
+ 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 "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;
+}
+
+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;
+}
+
+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;
+}
+
+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);
+
+ /* Data sources with no GFile always load successfully. */
+ if (file == NULL)
+ return TRUE;
+
+ g_file_load_contents (
+ file, cancellable, &data, &length, NULL, &local_error);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((data != NULL) && (local_error == NULL)) ||
+ ((data == NULL) && (local_error != NULL)));
+
+ /* 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);
+
+ /* Create the bare minimum to pass parse_data(). */
+ data = g_strdup_printf ("[%s]", PRIMARY_GROUP_NAME);
+ length = strlen (data);
+
+ } else if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ return FALSE;
+
+ } else {
+ source->priv->file_contents = g_strdup (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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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");
+}
+
+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");
+}
+
+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
index 0000000..bf18df5
--- /dev/null
+++ b/libebackend/e-server-side-source.h
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+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;
+ESource * e_server_side_source_new (ESourceRegistryServer *server,
+ GFile *file,
+ 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
index 0000000..1463315
--- /dev/null
+++ b/libebackend/e-source-registry-server.c
@@ -0,0 +1,1675 @@
+/*
+ * 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/>
+ *
+ */
+
+#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-server-side-source.h>
+#include <libebackend/e-dbus-source-authenticator.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;
+ EDBusSourceAuthenticator *active_auth;
+
+ guint authentication_count;
+};
+
+enum {
+ LOAD_ERROR,
+ FILES_LOADED,
+ SOURCE_ADDED,
+ SOURCE_REMOVED,
+ LAST_SIGNAL
+};
+
+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_maybe_initiate_next_authenticator (ESourceRegistryServer *server)
+{
+ GQueue *queue;
+
+ if (server->priv->active_auth != NULL)
+ 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))
+ continue;
+
+ server->priv->active_auth_table_queue = queue;
+ server->priv->active_auth = g_queue_peek_head (queue);
+ }
+
+ /* 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_dbus_source_authenticator_initiate (
+ server->priv->active_auth);
+ else
+ server->priv->active_auth_table_queue = NULL;
+}
+
+static void
+source_registry_server_auth_complete_cb (EDBusSourceAuthenticator *authenticator,
+ EDBusSourceAuthenticatorResult result,
+ ESourceRegistryServer *server)
+{
+ GQueue *queue;
+ const gchar *uid;
+
+ uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+ g_return_if_fail (uid != NULL);
+
+ queue = source_registry_server_auth_table_lookup (server, uid);
+
+ /* Remove the completed authenticator from its queue. */
+ if (g_queue_remove (queue, authenticator))
+ g_object_unref (authenticator);
+
+ /* If the completed authenticator was the active one,
+ * clear the active pointer for the next authenticator. */
+ if (authenticator == server->priv->active_auth)
+ server->priv->active_auth = NULL;
+
+ source_registry_server_maybe_initiate_next_authenticator (server);
+}
+
+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;
+ EDBusSourceAuthenticator *authenticator;
+ const gchar *base_object_path;
+ gchar *auth_object_path;
+ GError *error = NULL;
+
+ connection = g_dbus_method_invocation_get_connection (invocation);
+
+ /* Create the authenticator object. */
+ authenticator = e_dbus_source_authenticator_new (server, source_uid);
+
+ /* Configure the authenticator object. */
+ g_object_set (
+ authenticator,
+ "prompt-title", prompt_title,
+ "prompt-message", prompt_message,
+ "prompt-description", prompt_description,
+ NULL);
+
+ /* Export the authenticator to a unique object path. This
+ * effectively initiates a new authentication session with
+ * the method caller. */
+
+ base_object_path = E_SOURCE_REGISTRY_SERVER_OBJECT_PATH;
+
+ auth_object_path = g_strdup_printf (
+ "%s/auth_%u", base_object_path,
+ server->priv->authentication_count++);
+
+ e_dbus_source_authenticator_export (
+ authenticator, connection, auth_object_path, &error);
+
+ if (error != NULL) {
+ g_dbus_method_invocation_take_error (invocation, error);
+ g_object_unref (authenticator);
+ g_free (auth_object_path);
+ return TRUE;
+ }
+
+ /* This references the authenticator and adds it to a queue. */
+ e_source_registry_server_add_authenticator (server, authenticator);
+
+ e_dbus_source_manager_complete_authenticate (
+ interface, invocation, auth_object_path);
+
+ g_object_unref (authenticator);
+ 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;
+ const gchar *user_dir;
+ gboolean success;
+ gchar *safe_uid;
+ gchar *path;
+ 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;
+ }
+
+ /* Generate a new UID and build a file path from it. */
+ safe_uid = g_strdup (uid);
+ e_filename_make_safe (safe_uid);
+ user_dir = e_server_side_source_get_user_dir ();
+ path = g_build_filename (user_dir, safe_uid, NULL);
+ file = g_file_new_for_path (path);
+ g_free (path);
+ g_free (safe_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)
+{
+ gchar *basename;
+ gboolean hidden;
+
+ basename = g_file_get_basename (file);
+ hidden = g_str_has_prefix (basename, ".");
+ g_free (basename);
+
+ /* XXX Ignore hidden files so we don't try
+ * to load .goutputstream-XXXXXX files. */
+ if (hidden)
+ return;
+
+ 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 *basename;
+
+ basename = g_file_get_basename (file);
+ source = e_source_registry_server_ref_source (server, basename);
+ g_free (basename);
+
+ 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);
+
+ /* 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);
+
+ /* 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;
+ EDBusSource *dbus_source;
+ GDBusObject *g_dbus_object;
+ const gchar *uid;
+ const gchar *object_name;
+ const gchar *object_path;
+ const gchar *extension_name;
+ gchar *data;
+
+ dbus_object = e_source_ref_dbus_object (source);
+ dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));
+
+ /* Before we export, 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. */
+ data = e_source_to_string (source, NULL);
+ e_dbus_source_set_data (dbus_source, data);
+ g_free (data);
+
+ 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_source);
+ g_object_unref (dbus_object);
+
+ /* Instantiate an ECollectionBackend if appropriate. */
+
+ 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));
+ }
+ }
+}
+
+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 #EDBusSourceObject 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 #EDBusSourceObject 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;
+
+ 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)
+{
+ const gchar *extension_name;
+ 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);
+ 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);
+
+ /* 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_add_authenticator:
+ * @server: an #ESourceRegistryServer
+ * @authenticator: 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_add_authenticator (ESourceRegistryServer *server,
+ EDBusSourceAuthenticator *authenticator)
+{
+ const gchar *uid;
+ GQueue *queue;
+
+ g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
+ g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+ uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+ g_return_if_fail (uid != NULL);
+
+ /* Add the authenticator to the appropriate queue. */
+ queue = source_registry_server_auth_table_lookup (server, uid);
+ g_queue_push_tail (queue, g_object_ref (authenticator));
+
+ /* Blindly push the UID onto the processing queue. */
+ g_queue_push_tail (server->priv->auth_queue, g_strdup (uid));
+
+ g_signal_connect (
+ authenticator, "complete",
+ G_CALLBACK (source_registry_server_auth_complete_cb),
+ server);
+
+ source_registry_server_maybe_initiate_next_authenticator (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;
+
+ /* XXX Ignore hidden files so we don't try
+ * to load .goutputstream-XXXXXX files. */
+ if (g_str_has_prefix (name, "."))
+ 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;
+ gchar *basename;
+ gboolean writable;
+ gboolean removable;
+
+ 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);
+
+ /* Check if we already have this file loaded. */
+ basename = g_file_get_basename (file);
+ source = e_source_registry_server_ref_source (server, basename);
+ g_free (basename);
+
+ 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
index 0000000..da9ecec
--- /dev/null
+++ b/libebackend/e-source-registry-server.h
@@ -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-backend-enums.h>
+#include <libebackend/e-data-factory.h>
+#include <libebackend/e-dbus-source-authenticator.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_add_authenticator
+ (ESourceRegistryServer *server,
+ EDBusSourceAuthenticator *authenticator);
+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 */
diff --git a/services/Makefile.am b/services/Makefile.am
index 1750bfa..551a440 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -3,6 +3,7 @@ NULL =
SUBDIRS = \
evolution-addressbook-factory \
evolution-calendar-factory \
+ evolution-source-registry \
$(NULL)
-include $(top_srcdir)/git.mk
diff --git a/services/evolution-addressbook-factory/Makefile.am b/services/evolution-addressbook-factory/Makefile.am
index 0d99ff7..beafb50 100644
--- a/services/evolution-addressbook-factory/Makefile.am
+++ b/services/evolution-addressbook-factory/Makefile.am
@@ -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.c b/services/evolution-addressbook-factory/evolution-addressbook-factory.c
index aa1080d..9450c81 100644
--- a/services/evolution-addressbook-factory/evolution-addressbook-factory.c
+++ b/services/evolution-addressbook-factory/evolution-addressbook-factory.c
@@ -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);
diff --git a/services/evolution-calendar-factory/Makefile.am b/services/evolution-calendar-factory/Makefile.am
index ba4d3b8..6c205d5 100644
--- a/services/evolution-calendar-factory/Makefile.am
+++ b/services/evolution-calendar-factory/Makefile.am
@@ -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 = \
diff --git a/services/evolution-calendar-factory/evolution-calendar-factory.c b/services/evolution-calendar-factory/evolution-calendar-factory.c
index a044e62..aa76072 100644
--- a/services/evolution-calendar-factory/evolution-calendar-factory.c
+++ b/services/evolution-calendar-factory/evolution-calendar-factory.c
@@ -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
index 0000000..4e65ea2
--- /dev/null
+++ b/services/evolution-source-registry/Makefile.am
@@ -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
diff --git a/services/evolution-calendar-factory/evolution-calendar-factory-migrate-basedir.c b/services/evolution-source-registry/evolution-source-registry-migrate-basedir.c
similarity index 82%
rename from services/evolution-calendar-factory/evolution-calendar-factory-migrate-basedir.c
rename to services/evolution-source-registry/evolution-source-registry-migrate-basedir.c
index d44119c..0c320a0 100644
--- a/services/evolution-calendar-factory/evolution-calendar-factory-migrate-basedir.c
+++ b/services/evolution-source-registry/evolution-source-registry-migrate-basedir.c
@@ -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
index 0000000..4f547d9
--- /dev/null
+++ b/services/evolution-source-registry/evolution-source-registry-migrate-sources.c
@@ -0,0 +1,3240 @@
+/*
+ * 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-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>
+
+/* 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;
+
+ /* Set by <account>, <source> and <signature> tags. */
+ gchar *filename;
+ GKeyFile *key_file;
+
+ /* Set by <account>/<source> tags. */
+ gboolean auto_bcc;
+ gboolean auto_cc;
+
+ /* Set by <identity> tags. */
+ gchar *identity_filename;
+ GKeyFile *identity_key_file;
+
+ /* Set by <transport> tags. */
+ gchar *transport_filename;
+ GKeyFile *transport_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. */
+
+ g_free (parse_data->filename);
+ g_free (parse_data->identity_filename);
+ g_free (parse_data->transport_filename);
+
+ if (parse_data->key_file != NULL)
+ g_key_file_free (parse_data->key_file);
+
+ if (parse_data->transport_key_file != NULL)
+ g_key_file_free (parse_data->transport_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 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,
+ const gchar *filename,
+ 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 = g_path_get_basename (filename);
+
+ 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_set_contents (filename, contents, length, error);
+ g_free (contents);
+
+ if (!success)
+ goto exit;
+
+ /* Rename the source's cache directory from its mangled URI
+ * to its UID. The key file's basename is also 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 is also 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_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;
+ const gchar *config_dir;
+ gchar *directory;
+ 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;
+
+ config_dir = e_get_user_config_dir ();
+ directory = g_build_filename (config_dir, "sources", NULL);
+ g_mkdir_with_parents (directory, 0700);
+
+ parse_data->filename = g_build_filename (directory, uid, NULL);
+
+ /* 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_test (parse_data->filename, G_FILE_TEST_EXISTS))
+ return;
+
+ parse_data->key_file = g_key_file_new ();
+
+ identity_uid = e_uid_new ();
+ parse_data->identity_filename =
+ g_build_filename (directory, identity_uid, NULL);
+ parse_data->identity_key_file = g_key_file_new ();
+
+ transport_uid = e_uid_new ();
+ parse_data->transport_filename =
+ g_build_filename (directory, transport_uid, NULL);
+ parse_data->transport_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_boolean (
+ parse_data->key_file,
+ E_SOURCE_GROUP_NAME,
+ "Enabled", enabled);
+
+ /* Mail account source doubles as an 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 (directory);
+ 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, evolution-groupwise,
+ * and evolution-mapi. Add more as needed. */
+ static struct {
+ const gchar *url_parameter;
+ const gchar *property_name;
+ } camel_url_conversion[] = {
+ { "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" },
+ { "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 (GKeyFile *key_file,
+ const gchar *filename,
+ const gchar *group_name,
+ const gchar *url_string,
+ GError **error)
+{
+ CamelURL *url;
+ const gchar *value;
+ gchar *uid;
+
+ struct {
+ GKeyFile *key_file;
+ const gchar *group_name;
+ } foreach_data;
+
+ url = camel_url_new (url_string, error);
+ if (url == NULL)
+ return;
+
+ /* Rename URL params as necessary to match
+ * their ESourceExtension property names. */
+ migrate_parse_url_rename_params (url);
+
+ /* Set authentication details. */
+
+ if (url->protocol != NULL)
+ g_key_file_set_string (
+ key_file, group_name,
+ "BackendName", url->protocol);
+
+ if (url->host != NULL)
+ g_key_file_set_string (
+ key_file,
+ E_SOURCE_EXTENSION_AUTHENTICATION,
+ "Host", url->host);
+
+ if (url->authmech != NULL)
+ g_key_file_set_string (
+ key_file,
+ E_SOURCE_EXTENSION_AUTHENTICATION,
+ "Method", url->authmech);
+
+ if (url->port > 0)
+ g_key_file_set_integer (
+ key_file,
+ E_SOURCE_EXTENSION_AUTHENTICATION,
+ "Port", url->port);
+
+ if (url->user != NULL)
+ g_key_file_set_string (
+ 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 (
+ 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 (
+ key_file,
+ E_SOURCE_EXTENSION_SECURITY,
+ "Method", value);
+ g_datalist_set_data (&url->params, "security-method", NULL);
+
+ /* 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 = key_file;
+ foreach_data.group_name = group_name;
+
+ g_datalist_foreach (
+ &url->params, (GDataForeachFunc)
+ migrate_parse_url_foreach, &foreach_data);
+
+ uid = g_path_get_basename (filename);
+ 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;
+
+ migrate_parse_commit_changes (
+ parse_data->type,
+ parse_data->filename,
+ 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;
+
+ migrate_parse_commit_changes (
+ parse_data->type,
+ parse_data->identity_filename,
+ 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;
+
+ migrate_parse_commit_changes (
+ parse_data->type,
+ parse_data->transport_filename,
+ 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;
+ }
+
+ g_free (parse_data->filename);
+ parse_data->filename = NULL;
+
+ g_free (parse_data->transport_filename);
+ parse_data->transport_filename = NULL;
+ }
+ 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->key_file,
+ parse_data->filename,
+ E_SOURCE_EXTENSION_MAIL_ACCOUNT,
+ text, error);
+ break;
+
+ case PARSE_STATE_IN_MAIL_TRANSPORT_URL:
+ migrate_parse_url (
+ parse_data->transport_key_file,
+ parse_data->transport_filename,
+ 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;
+
+ config_dir = e_get_user_config_dir ();
+
+ directory = g_build_filename (config_dir, "sources", NULL);
+ parse_data->filename = g_build_filename (directory, uid, NULL);
+ g_mkdir_with_parents (directory, 0700);
+ g_free (directory);
+
+ 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_test (parse_data->filename, G_FILE_TEST_EXISTS))
+ 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;
+
+ migrate_parse_commit_changes (
+ parse_data->type,
+ parse_data->filename,
+ 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;
+ }
+
+ g_free (parse_data->filename);
+ parse_data->filename = NULL;
+
+ 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 *config_dir;
+ const gchar *group_name;
+ const gchar *absolute_uri;
+ const gchar *relative_uri;
+ gchar *directory;
+ 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;
+
+ 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 ();
+ }
+
+ config_dir = e_get_user_config_dir ();
+ directory = g_build_filename (config_dir, "sources", NULL);
+ g_mkdir_with_parents (directory, 0700);
+
+ parse_data->filename = g_build_filename (directory, uid, NULL);
+
+ 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_test (parse_data->filename, G_FILE_TEST_EXISTS))
+ 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));
+
+ } 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;
+
+ migrate_parse_commit_changes (
+ parse_data->type,
+ parse_data->filename,
+ 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;
+ }
+
+ g_free (parse_data->filename);
+ parse_data->filename = 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;
+ }
+ 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
index 0000000..56b9a21
--- /dev/null
+++ b/services/evolution-source-registry/evolution-source-registry.c
@@ -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
index 0000000..1db5de8
--- /dev/null
+++ b/services/evolution-source-registry/org.gnome.evolution.dataserver.Sources.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name= SOURCES_DBUS_SERVICE_NAME@
+Exec= libexecdir@/e-source-registry
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]