[evolution-kolab] Prototype automatic resource discovery.



commit 90e54b8ea598167268be84dbcfe4a97510b61fed
Author: Matthew Barnes <mbarnes redhat com>
Date:   Mon Aug 13 13:24:26 2012 -0400

    Prototype automatic resource discovery.
    
    On startup, the EKolabBackend queries the server for a folder list and
    creates ESources to represent the event/task/note/contact type folders.

 src/collection/e-kolab-backend.c  |  487 ++++++++++++++++++++++++++++++++++++-
 src/collection/e-kolab-backend.h  |   30 +++
 src/libekolab/kolab-mail-access.c |   24 ++-
 3 files changed, 531 insertions(+), 10 deletions(-)
---
diff --git a/src/collection/e-kolab-backend.c b/src/collection/e-kolab-backend.c
index 34373c3..bd52aa2 100644
--- a/src/collection/e-kolab-backend.c
+++ b/src/collection/e-kolab-backend.c
@@ -19,12 +19,23 @@
 #include <libekolab/e-source-kolab-folder.h>
 #include <libekolab/camel-kolab-imapx-settings.h>
 
+#include <libekolabutil/kolab-util-camel.h>
+
 #define E_KOLAB_BACKEND_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_KOLAB_BACKEND, EKolabBackendPrivate))
 
+/* This forces the GType to be registered in a way that
+ * avoids a "statement with no effect" compiler warning. */
+#define REGISTER_TYPE(type) \
+	(g_type_class_unref (g_type_class_ref (type)))
+
+#define KOLAB_PATH_SEPARATOR   '/'
+#define KOLAB_PATH_SEPARATOR_S "/"
+
 struct _EKolabBackendPrivate {
-	gint placeholder;   /* remove when there's something else to add */
+	KolabMailAccess *koma;
+	GMutex koma_lock;
 };
 
 G_DEFINE_DYNAMIC_TYPE (
@@ -32,17 +43,77 @@ G_DEFINE_DYNAMIC_TYPE (
 	e_kolab_backend,
 	E_TYPE_COLLECTION_BACKEND)
 
+static CamelKolabIMAPXSettings *
+kolab_backend_get_settings (EKolabBackend *backend)
+{
+	ESource *source;
+	ESourceCamel *extension;
+	CamelSettings *settings;
+	const gchar *extension_name;
+	const gchar *protocol;
+
+	protocol = KOLAB_CAMEL_PROVIDER_PROTOCOL;
+	source = e_backend_get_source (E_BACKEND (backend));
+	extension_name = e_source_camel_get_extension_name (protocol);
+	extension = e_source_get_extension (source, extension_name);
+	settings = e_source_camel_get_settings (extension);
+
+	return CAMEL_KOLAB_IMAPX_SETTINGS (settings);
+}
+
+static void
+kolab_backend_sync_folders_done_cb (GObject *source_object,
+                                    GAsyncResult *result,
+                                    gpointer user_data)
+{
+	EKolabBackend *backend;
+	GError *error = NULL;
+
+	backend = E_KOLAB_BACKEND (source_object);
+
+	e_kolab_backend_sync_folders_finish (backend, result, &error);
+
+	if (error != NULL) {
+		g_critical ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+kolab_backend_dispose (GObject *object)
+{
+	EKolabBackendPrivate *priv;
+
+	priv = E_KOLAB_BACKEND_GET_PRIVATE (object);
+
+	if (priv->koma != NULL) {
+		g_object_unref (priv->koma);
+		priv->koma = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_kolab_backend_parent_class)->dispose (object);
+}
+
+static void
+kolab_backend_finalize (GObject *object)
+{
+	EKolabBackendPrivate *priv;
+
+	priv = E_KOLAB_BACKEND_GET_PRIVATE (object);
+
+	g_mutex_clear (&priv->koma_lock);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_kolab_backend_parent_class)->finalize (object);
+}
+
 static void
 kolab_backend_populate (ECollectionBackend *backend)
 {
-	/* FIXME At this point the backend should query the Kolab server
-	 *       for all available non-mail folders such as address books
-	 *       and calendars, and create ESources for them.
-	 *
-	 *       Upstream authors are still fleshing out the details of
-	 *       how this should work.  Query Matthew Barnes when you're
-	 *       ready to implement this.
-	 */
+	e_kolab_backend_sync_folders (
+		E_KOLAB_BACKEND (backend), NULL,
+		kolab_backend_sync_folders_done_cb, NULL);
 }
 
 static gchar *
@@ -79,16 +150,24 @@ kolab_backend_child_removed (ECollectionBackend *backend,
 static void
 e_kolab_backend_class_init (EKolabBackendClass *class)
 {
+	GObjectClass *object_class;
 	ECollectionBackendClass *backend_class;
 
 	g_type_class_add_private (class, sizeof (EKolabBackendPrivate));
 
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = kolab_backend_dispose;
+	object_class->finalize = kolab_backend_finalize;
+
 	backend_class = E_COLLECTION_BACKEND_CLASS (class);
 	backend_class->populate = kolab_backend_populate;
 	backend_class->dup_resource_id = kolab_backend_dup_resource_id;
 	backend_class->child_added = kolab_backend_child_added;
 	backend_class->child_removed = kolab_backend_child_removed;
 
+	/* Register relevant ESourceExtension types. */
+	REGISTER_TYPE (E_TYPE_SOURCE_KOLAB_FOLDER);
+
 	/* This generates an ESourceCamel subtype for CamelKolabIMAPXSettings. */
 	e_source_camel_generate_subtype ("kolab", CAMEL_TYPE_KOLAB_IMAPX_SETTINGS);
 }
@@ -101,7 +180,18 @@ e_kolab_backend_class_finalize (EKolabBackendClass *class)
 static void
 e_kolab_backend_init (EKolabBackend *backend)
 {
+	GError *error = NULL;
+
 	backend->priv = E_KOLAB_BACKEND_GET_PRIVATE (backend);
+
+	g_mutex_init (&backend->priv->koma_lock);
+
+	/* Initialize Camel and NSS.  If we fail here, there's not
+	 * much else to do but abort the whole service immediately. */
+	if (!kolab_util_camel_init (&error)) {
+		g_error ("%s: %s", G_STRFUNC, error->message);
+		g_assert_not_reached ();
+	}
 }
 
 void
@@ -113,3 +203,382 @@ e_kolab_backend_type_register (GTypeModule *type_module)
 	e_kolab_backend_register_type (type_module);
 }
 
+ESource *
+e_kolab_backend_new_child (EKolabBackend *backend,
+                           KolabFolderDescriptor *desc)
+{
+	ESource *source;
+	ESourceBackend *backend_extension;
+	ESourceResource *resource_extension;
+	const gchar *display_name;
+	const gchar *extension_name;
+
+	g_return_val_if_fail (E_IS_KOLAB_BACKEND (backend), NULL);
+	g_return_val_if_fail (desc != NULL, NULL);
+
+	/* Return NULL for folder types we don't support, and map
+	 * the folder type to an equivalent ESourceExtension name
+	 * while we're at it. */
+	switch (desc->type_id) {
+		case KOLAB_FOLDER_TYPE_EVENT:
+		case KOLAB_FOLDER_TYPE_EVENT_DEFAULT:
+			extension_name = E_SOURCE_EXTENSION_CALENDAR;
+			break;
+		case KOLAB_FOLDER_TYPE_TASK:
+		case KOLAB_FOLDER_TYPE_TASK_DEFAULT:
+			extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+			break;
+		case KOLAB_FOLDER_TYPE_NOTE:
+		case KOLAB_FOLDER_TYPE_NOTE_DEFAULT:
+			extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
+			break;
+		case KOLAB_FOLDER_TYPE_CONTACT:
+		case KOLAB_FOLDER_TYPE_CONTACT_DEFAULT:
+			extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+			break;
+		default:
+			return NULL;
+	}
+
+	source = e_collection_backend_new_child (
+		E_COLLECTION_BACKEND (backend), desc->name);
+
+	/* Use the extension name set in the switch statement above. */
+	backend_extension = e_source_get_extension (source, extension_name);
+	e_source_backend_set_backend_name (backend_extension, "kolab");
+
+	/* XXX The resource identity is the folder path.  This is not a
+	 *     stable identifier -- a folder rename will invalidate the
+	 *     cache on the next start -- but it's all we're given. */
+	extension_name = E_SOURCE_EXTENSION_KOLAB_FOLDER;
+	resource_extension = e_source_get_extension (source, extension_name);
+	e_source_resource_set_identity (resource_extension, desc->name);
+
+	/* The hierarchy is flattened for non-mail folders,
+	 * so the last path segment is the display name. */
+	if (desc->name != NULL) {
+		gchar **segments;
+		guint n_segments;
+
+		segments = g_strsplit (desc->name, KOLAB_PATH_SEPARATOR_S, -1);
+		n_segments = g_strv_length (segments);
+
+		if (n_segments > 0) {
+			const gchar *display_name;
+			display_name = segments[n_segments - 1];
+			e_source_set_display_name (source, display_name);
+		}
+
+		g_strfreev (segments);
+	}
+
+	return source;
+}
+
+KolabMailAccess *
+e_kolab_backend_ref_mail_access_sync (EKolabBackend *backend,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+	KolabMailAccess *koma = NULL;
+	CamelKolabIMAPXSettings *settings;
+	KolabSettingsHandler *settings_handler;
+	ESource *source;
+	gboolean success;
+
+	g_return_val_if_fail (E_IS_KOLAB_BACKEND (backend), NULL);
+
+	g_mutex_lock (&backend->priv->koma_lock);
+	if (backend->priv->koma != NULL)
+		koma = g_object_ref (backend->priv->koma);
+	g_mutex_unlock (&backend->priv->koma_lock);
+
+	/* Return a stashed KolabMailAccess if we have one. */
+	if (koma != NULL)
+		return koma;
+
+	/* FIXME Creating a KolabMailAccess instance is a somewhat
+	 *       involved process which is repeated in the calendar
+	 *       and address book backends.  Should be centralized
+	 *       in libekolab and provided as a one-step function.
+	 *       The configure steps at least should be done during
+	 *       initialization using constructor properties. */
+
+	settings = kolab_backend_get_settings (backend);
+	source = e_backend_get_source (E_BACKEND (backend));
+
+	settings_handler = kolab_settings_handler_new (settings);
+
+	/* XXX Christian recommends KOLAB_FOLDER_CONTEXT_EMAIL here
+	 *     although the operations we need are supposed to work
+	 *     for any folder context. */
+	success = kolab_settings_handler_configure (
+		settings_handler, KOLAB_FOLDER_CONTEXT_EMAIL, error);
+	if (!success) {
+		g_object_unref (settings_handler);
+		return NULL;
+	}
+
+	if (!kolab_settings_handler_bringup (settings_handler, error)) {
+		g_object_unref (settings_handler);
+		return NULL;
+	}
+
+	/* This must be done after configure() and bringup(). */
+	success = kolab_settings_handler_set_char_field (
+		settings_handler,
+		KOLAB_SETTINGS_HANDLER_CHAR_FIELD_ESOURCE_UID,
+		e_source_dup_uid (source), error);
+	if (!success) {
+		g_object_unref (settings_handler);
+		return NULL;
+	}
+
+	koma = g_object_new (KOLAB_TYPE_MAIL_ACCESS, NULL);
+
+	if (!kolab_mail_access_configure (koma, settings_handler, error)) {
+		g_object_unref (settings_handler);
+		g_object_unref (koma);
+		return NULL;
+	}
+
+	if (!kolab_mail_access_bringup (koma, cancellable, error)) {
+		g_object_unref (settings_handler);
+		g_object_unref (koma);
+		return NULL;
+	}
+
+	success = e_backend_authenticate_sync (
+		E_BACKEND (backend),
+		E_SOURCE_AUTHENTICATOR (koma),
+		cancellable, error);
+	if (!success) {
+		g_object_unref (settings_handler);
+		g_object_unref (koma);
+		return NULL;
+	}
+
+	/* Another thread may have created a KolabMailAccess since
+	 * our previous check above.  If so, replace it with ours. */
+	g_mutex_lock (&backend->priv->koma_lock);
+	if (backend->priv->koma != NULL)
+		g_object_unref (backend->priv->koma);
+	backend->priv->koma = g_object_ref (koma);
+	g_mutex_unlock (&backend->priv->koma_lock);
+
+	g_object_unref (settings_handler);
+
+	return koma;
+}
+
+/* Helper for e_kolab_backend_ref_mail_access() */
+static void
+kolab_backend_ref_mail_access_thread (GSimpleAsyncResult *simple,
+                                      GObject *object,
+                                      GCancellable *cancellable)
+{
+	KolabMailAccess *koma;
+	GError *error = NULL;
+
+	koma = e_kolab_backend_ref_mail_access_sync (
+		E_KOLAB_BACKEND (object), cancellable, &error);
+
+	if (koma != NULL)
+		g_simple_async_result_set_op_res_gpointer (
+			simple, koma, (GDestroyNotify) g_object_unref);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+}
+
+void
+e_kolab_backend_ref_mail_access (EKolabBackend *backend,
+                                 GCancellable *cancellable,
+                                 GAsyncReadyCallback callback,
+                                 gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail (E_IS_KOLAB_BACKEND (backend));
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (backend), callback, user_data,
+		e_kolab_backend_ref_mail_access);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_simple_async_result_run_in_thread (
+		simple, kolab_backend_ref_mail_access_thread,
+		G_PRIORITY_DEFAULT, cancellable);
+
+	g_object_unref (simple);
+}
+
+KolabMailAccess *
+e_kolab_backend_ref_mail_access_finish (EKolabBackend *backend,
+                                        GAsyncResult *result,
+                                        GError **error)
+{
+	GSimpleAsyncResult *simple;
+	KolabMailAccess *koma;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (backend),
+		e_kolab_backend_ref_mail_access), NULL);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return NULL;
+
+	koma = g_simple_async_result_get_op_res_gpointer (simple);
+	g_return_val_if_fail (KOLAB_IS_MAIL_ACCESS (koma), NULL);
+
+	return g_object_ref (koma);
+}
+
+gboolean
+e_kolab_backend_sync_folders_sync (EKolabBackend *backend,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+	ECollectionBackend *col_backend;
+	ESourceRegistryServer *server;
+	KolabMailAccess *koma;
+	GList *list, *link;
+	GQueue trash = G_QUEUE_INIT;
+	GError *local_error = NULL;
+
+	g_return_val_if_fail (E_IS_KOLAB_BACKEND (backend), FALSE);
+
+	col_backend = E_COLLECTION_BACKEND (backend);
+
+	/* If we're offline, all we can do is add the last known set
+	 * of folders from our cache of previously used ESources. */
+	if (!e_backend_get_online (E_BACKEND (backend))) {
+		server = e_collection_backend_ref_server (col_backend);
+		list = e_collection_backend_claim_all_resources (col_backend);
+
+		for (link = list; link != NULL; link = g_list_next (link))
+			e_source_registry_server_add_source (
+				server, E_SOURCE (link->data));
+
+		g_list_free_full (list, (GDestroyNotify) g_object_unref);
+		g_object_unref (server);
+
+		return TRUE;
+	}
+
+	koma = e_kolab_backend_ref_mail_access_sync (
+		backend, cancellable, error);
+
+	if (koma == NULL)
+		return FALSE;
+
+	list = kolab_mail_access_query_folder_info_online (
+		koma, cancellable, &local_error);
+
+	g_object_unref (koma);
+
+	if (local_error != NULL) {
+		g_propagate_error (error, local_error);
+		return FALSE;
+	}
+
+	/* Create or reuse ESources to represent Kolab folders. */
+
+	server = e_collection_backend_ref_server (col_backend);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		KolabFolderDescriptor *desc = link->data;
+		ESource *source;
+
+		source = e_kolab_backend_new_child (backend, desc);
+
+		if (source != NULL) {
+			e_source_registry_server_add_source (server, source);
+			g_object_unref (source);
+		}
+	}
+
+	g_object_unref (server);
+
+	g_list_free_full (
+		list, (GDestroyNotify) kolab_util_folder_descriptor_free);
+
+	/* Discard any previously cached folders no longer
+	 * present in the folder list from the Kolab server.
+	 *
+	 * Note: This is just an attempt to cleanup cached
+	 *       data, so we don't really care about errors.
+	 */
+
+	list = e_collection_backend_claim_all_resources (col_backend);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESource *source = E_SOURCE (link->data);
+		e_source_remove_sync (source, NULL, NULL);
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+	return TRUE;
+}
+
+/* Helper for e_kolab_backend_sync_folders() */
+static void
+kolab_backend_sync_folders_thread (GSimpleAsyncResult *simple,
+                                   GObject *object,
+                                   GCancellable *cancellable)
+{
+	GError *error = NULL;
+
+	e_kolab_backend_sync_folders_sync (
+		E_KOLAB_BACKEND (object), cancellable, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+}
+
+void
+e_kolab_backend_sync_folders (EKolabBackend *backend,
+                              GCancellable *cancellable,
+                              GAsyncReadyCallback callback,
+                              gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail (E_IS_KOLAB_BACKEND (backend));
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (backend), callback, user_data,
+		e_kolab_backend_sync_folders);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_simple_async_result_run_in_thread (
+		simple, kolab_backend_sync_folders_thread,
+		G_PRIORITY_DEFAULT, cancellable);
+
+	g_object_unref (simple);
+}
+
+gboolean
+e_kolab_backend_sync_folders_finish (EKolabBackend *backend,
+                                     GAsyncResult *result,
+                                     GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (backend),
+		e_kolab_backend_sync_folders), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError was set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
diff --git a/src/collection/e-kolab-backend.h b/src/collection/e-kolab-backend.h
index ab581e2..72b6e98 100644
--- a/src/collection/e-kolab-backend.h
+++ b/src/collection/e-kolab-backend.h
@@ -19,6 +19,8 @@
 
 #include <libebackend/libebackend.h>
 
+#include "libekolab/kolab-mail-access.h"
+
 /* Standard GObject macros */
 #define E_TYPE_KOLAB_BACKEND \
 	(e_kolab_backend_get_type ())
@@ -55,6 +57,34 @@ struct _EKolabBackendClass {
 
 GType		e_kolab_backend_get_type	(void) G_GNUC_CONST;
 void		e_kolab_backend_type_register	(GTypeModule *type_module);
+ESource *	e_kolab_backend_new_child	(EKolabBackend *backend,
+						 KolabFolderDescriptor *desc);
+KolabMailAccess *
+		e_kolab_backend_ref_mail_access_sync
+						(EKolabBackend *backend,
+						 GCancellable *cancellable,
+						 GError **error);
+void		e_kolab_backend_ref_mail_access	(EKolabBackend *backend,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+KolabMailAccess *
+		e_kolab_backend_ref_mail_access_finish
+						(EKolabBackend *backend,
+						 GAsyncResult *result,
+						 GError **error);
+gboolean	e_kolab_backend_sync_folders_sync
+						(EKolabBackend *backend,
+						 GCancellable *cancellable,
+						 GError **error);
+void		e_kolab_backend_sync_folders	(EKolabBackend *backend,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_kolab_backend_sync_folders_finish
+						(EKolabBackend *backend,
+						 GAsyncResult *result,
+						 GError **error);
 
 G_END_DECLS
 
diff --git a/src/libekolab/kolab-mail-access.c b/src/libekolab/kolab-mail-access.c
index 3b3d1ca..8e886b7 100644
--- a/src/libekolab/kolab-mail-access.c
+++ b/src/libekolab/kolab-mail-access.c
@@ -111,6 +111,10 @@ mail_access_try_password_sync (ESourceAuthenticator *authenticator,
                                GCancellable *cancellable,
                                GError **error)
 {
+	KolabMailAccessPrivate *priv;
+	ESourceAuthenticationResult result;
+	gboolean success;
+
 	/* FIXME This needs to test the provided password and return:
 	 *
 	 *       E_SOURCE_AUTHENTICATION_ACCEPTED if the password is valid.
@@ -136,7 +140,25 @@ mail_access_try_password_sync (ESourceAuthenticator *authenticator,
 	 *       This is broken behavior.
 	 */
 
-	return E_SOURCE_AUTHENTICATION_ACCEPTED;
+	priv = KOLAB_MAIL_ACCESS_PRIVATE (authenticator);
+
+	if (priv->ksettings != NULL)
+		kolab_settings_handler_set_char_field (
+			priv->ksettings,
+			KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_PASSWORD,
+			g_strdup (password->str), NULL);
+
+	success = kolab_mail_access_set_opmode (
+		KOLAB_MAIL_ACCESS (authenticator),
+		KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
+		cancellable, error);
+
+	if (success)
+		result = E_SOURCE_AUTHENTICATION_ACCEPTED;
+	else
+		result = E_SOURCE_AUTHENTICATION_ERROR;
+
+	return result;
 }
 
 static void



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