[evolution-data-server] Add 'online-accounts' module.



commit 56a858c91c295b9dc138eefe0b87a03bd6d82707
Author: Matthew Barnes <mbarnes redhat com>
Date:   Sun Apr 1 15:02:27 2012 -0400

    Add 'online-accounts' module.
    
    This replaces the 'online-accounts' module in Evolution.

 Makefile.am                                      |    2 +-
 configure.ac                                     |   11 +-
 modules/Makefile.am                              |   11 +
 modules/online-accounts/Makefile.am              |   34 +
 modules/online-accounts/goaewsclient.c           |  542 +++++++++++++
 modules/online-accounts/goaewsclient.h           |   53 ++
 modules/online-accounts/module-online-accounts.c |  903 ++++++++++++++++++++++
 7 files changed, 1554 insertions(+), 2 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 1d619ff..de2e6c5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS=-I m4
 
 DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --with-krb5=/usr --without-weather --enable-introspection
 
-SUBDIRS = camel data private libedataserver libebackend addressbook calendar libedataserverui services tests docs art po
+SUBDIRS = camel data private libedataserver libebackend addressbook calendar libedataserverui modules services tests docs art po
 
 if HAVE_INTROSPECTION
 if HAVE_VALA
diff --git a/configure.ac b/configure.ac
index f0db717..1582361 100644
--- a/configure.ac
+++ b/configure.ac
@@ -38,13 +38,15 @@ m4_define([gtk_minimum_version], [3.2])
 m4_define([gconf_minimum_version], [2.0.0])		dnl XXX Just a Guess
 m4_define([gcr_minimum_version], [3.4])
 m4_define([gnome_keyring_minimum_version], [2.20.1])
-m4_define([goa_minimum_version], [3.2])
 m4_define([libxml_minimum_version], [2.0.0])		dnl XXX Just a Guess
 m4_define([libsoup_minimum_version], [2.38.1])
 m4_define([libgdata_minimum_version], [0.10])
 m4_define([oauth_minimum_version], [0.9.4])
 m4_define([sqlite_minimum_version], [3.5])
 m4_define([libical_minimum_version], [0.43])
+
+dnl Optional Packages
+m4_define([goa_minimum_version], [3.2])
 m4_define([gweather_minimum_version], [2.90.0])
 
 AC_SUBST([BASE_VERSION],[base_version])
@@ -404,6 +406,11 @@ if test "x$enable_goa" = xyes; then
 fi
 AM_CONDITIONAL(HAVE_GOA, [test x$enable_goa = xyes])
 
+dnl GoaPasswordBased was introduced in version 3.5.
+if `$PKG_CONFIG --atleast-version=3.5 goa-1.0`; then
+	AC_DEFINE(HAVE_GOA_PASSWORD_BASED,1,[Have GoaPasswordBased in goa-1.0])
+fi
+
 if test x$os_win32 = xno; then
 	PKG_CHECK_MODULES(GIO_UNIX, [gio-unix-2.0])
 fi
@@ -1655,6 +1662,8 @@ libedataserver/eds-version.h
 libedataserver/libedataserver.pc
 libedataserverui/Makefile
 libedataserverui/libedataserverui.pc
+modules/Makefile
+modules/online-accounts/Makefile
 private/Makefile
 services/Makefile
 services/evolution-addressbook-factory/Makefile
diff --git a/modules/Makefile.am b/modules/Makefile.am
new file mode 100644
index 0000000..6f7cf42
--- /dev/null
+++ b/modules/Makefile.am
@@ -0,0 +1,11 @@
+NULL =
+
+if HAVE_GOA
+ONLINE_ACCOUNTS_DIR = online-accounts
+endif
+
+SUBDIRS = \
+	$(ONLINE_ACCOUNTS_DIR) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/online-accounts/Makefile.am b/modules/online-accounts/Makefile.am
new file mode 100644
index 0000000..54bad0f
--- /dev/null
+++ b/modules/online-accounts/Makefile.am
@@ -0,0 +1,34 @@
+NULL =
+
+module_LTLIBRARIES = module-online-accounts.la
+
+module_online_accounts_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir) \
+	-DG_LOG_DOMAIN=\"module-online-accounts\" \
+	$(E_BACKEND_CFLAGS) \
+	$(CAMEL_CFLAGS) \
+	$(SOUP_CFLAGS) \
+	$(GOA_CFLAGS) \
+	$(NULL)
+
+module_online_accounts_la_SOURCES = \
+	module-online-accounts.c \
+	goaewsclient.c \
+	goaewsclient.h \
+	$(NULL)
+
+module_online_accounts_la_LIBADD = \
+	$(top_builddir)/libebackend/libebackend-1.2.la \
+	$(top_builddir)/libedataserver/libedataserver-1.2.la \
+	$(E_BACKEND_LIBS) \
+	$(CAMEL_LIBS) \
+	$(SOUP_LIBS) \
+	$(GOA_LIBS) \
+	$(NULL)
+
+module_online_accounts_la_LDFLAGS = \
+	-module -avoid-version $(NO_UNDEFINED) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/online-accounts/goaewsclient.c b/modules/online-accounts/goaewsclient.c
new file mode 100644
index 0000000..d387b4d
--- /dev/null
+++ b/modules/online-accounts/goaewsclient.c
@@ -0,0 +1,542 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+/* Based on code by the Evolution team.
+ *
+ * This was originally written as a part of evolution-ews:
+ * evolution-ews/src/server/e-ews-connection.c
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libsoup/soup.h>
+#include <libxml/xmlIO.h>
+
+#include <libedataserver/e-data-server-util.h>
+
+#include "goaewsclient.h"
+
+typedef struct {
+	GCancellable *cancellable;
+	SoupMessage *msgs[2];
+	SoupSession *session;
+	gulong cancellable_id;
+	xmlOutputBuffer *buf;
+
+	/* results */
+	gchar *as_url;
+	gchar *oab_url;
+} AutodiscoverData;
+
+typedef struct {
+	gchar *password;
+	gchar *username;
+} AutodiscoverAuthData;
+
+static void
+ews_autodiscover_data_free (AutodiscoverData *data)
+{
+	if (data->cancellable_id > 0) {
+		g_cancellable_disconnect (
+			data->cancellable, data->cancellable_id);
+		g_object_unref (data->cancellable);
+	}
+
+	/* soup_session_queue_message stole the references to data->msgs */
+	xmlOutputBufferClose (data->buf);
+	g_object_unref (data->session);
+
+	g_free (data->as_url);
+	g_free (data->oab_url);
+
+	g_slice_free (AutodiscoverData, data);
+}
+
+static void
+ews_autodiscover_auth_data_free (gpointer data,
+                                 GClosure *closure)
+{
+	AutodiscoverAuthData *auth = data;
+
+	g_free (auth->password);
+	g_free (auth->username);
+	g_slice_free (AutodiscoverAuthData, auth);
+}
+
+static gboolean
+ews_check_node (const xmlNode *node,
+                const gchar *name)
+{
+	g_return_val_if_fail (node != NULL, FALSE);
+
+	return (node->type == XML_ELEMENT_NODE) &&
+		(g_strcmp0 ((gchar *) node->name, name) == 0);
+}
+
+static void
+ews_authenticate (SoupSession *session,
+                  SoupMessage *msg,
+                  SoupAuth *auth,
+                  gboolean retrying,
+                  AutodiscoverAuthData *data)
+{
+	if (retrying)
+		return;
+
+	soup_auth_authenticate (auth, data->username, data->password);
+}
+
+static void
+ews_autodiscover_cancelled_cb (GCancellable *cancellable,
+                               AutodiscoverData *data)
+{
+	soup_session_abort (data->session);
+}
+
+static gboolean
+ews_autodiscover_parse_protocol (xmlNode *node,
+                                 AutodiscoverData *data)
+{
+	gboolean got_as_url = FALSE;
+	gboolean got_oab_url = FALSE;
+
+	for (node = node->children; node; node = node->next) {
+		xmlChar *content;
+
+		if (ews_check_node (node, "ASUrl")) {
+			content = xmlNodeGetContent (node);
+			data->as_url = g_strdup ((gchar *) content);
+			xmlFree (content);
+			got_as_url = TRUE;
+
+		} else if (ews_check_node (node, "OABUrl")) {
+			content = xmlNodeGetContent (node);
+			data->oab_url = g_strdup ((gchar *) content);
+			xmlFree (content);
+			got_oab_url = TRUE;
+		}
+
+		if (got_as_url && got_oab_url)
+			break;
+	}
+
+	return (got_as_url && got_oab_url);
+}
+
+static void
+ews_autodiscover_response_cb (SoupSession *session,
+                              SoupMessage *msg,
+                              gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AutodiscoverData *data;
+	gboolean success = FALSE;
+	guint status;
+	gint idx;
+	gsize size;
+	xmlDoc *doc;
+	xmlNode *node;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+	data = g_simple_async_result_get_op_res_gpointer (simple);
+
+	status = msg->status_code;
+	if (status == SOUP_STATUS_CANCELLED)
+		return;
+
+	size = sizeof (data->msgs) / sizeof (data->msgs[0]);
+
+	for (idx = 0; idx < size; idx++) {
+		if (data->msgs[idx] == msg)
+			break;
+	}
+	if (idx == size)
+		return;
+
+	data->msgs[idx] = NULL;
+
+	if (status != SOUP_STATUS_OK) {
+		g_set_error (
+			&error, GOA_ERROR,
+			GOA_ERROR_FAILED, /* TODO: more specific */
+			_("Code: %u - Unexpected response from server"),
+			status);
+		goto out;
+	}
+
+	soup_buffer_free (
+		soup_message_body_flatten (
+		SOUP_MESSAGE (msg)->response_body));
+
+	g_debug ("The response headers");
+	g_debug ("===================");
+	g_debug ("%s", SOUP_MESSAGE (msg)->response_body->data);
+
+	doc = xmlReadMemory (
+		msg->response_body->data,
+		msg->response_body->length,
+		"autodiscover.xml", NULL, 0);
+	if (doc == NULL) {
+		g_set_error (
+			&error, GOA_ERROR,
+			GOA_ERROR_FAILED, /* TODO: more specific */
+			_("Failed to parse autodiscover response XML"));
+		goto out;
+	}
+
+	node = xmlDocGetRootElement (doc);
+	if (g_strcmp0 ((gchar *) node->name, "Autodiscover") != 0) {
+		g_set_error (
+			&error, GOA_ERROR,
+			GOA_ERROR_FAILED, /* TODO: more specific */
+			_("Failed to find Autodiscover element"));
+		goto out;
+	}
+
+	for (node = node->children; node; node = node->next) {
+		if (ews_check_node (node, "Response"))
+			break;
+	}
+	if (node == NULL) {
+		g_set_error (
+			&error, GOA_ERROR,
+			GOA_ERROR_FAILED, /* TODO: more specific */
+			_("Failed to find Response element"));
+		goto out;
+	}
+
+	for (node = node->children; node; node = node->next) {
+		if (ews_check_node (node, "Account"))
+			break;
+	}
+	if (node == NULL) {
+		g_set_error (
+			&error, GOA_ERROR,
+			GOA_ERROR_FAILED, /* TODO: more specific */
+			_("Failed to find Account element"));
+		goto out;
+	}
+
+	for (node = node->children; node; node = node->next) {
+		if (ews_check_node (node, "Protocol")) {
+			success = ews_autodiscover_parse_protocol (node, data);
+			break;
+		}
+	}
+	if (!success) {
+		g_set_error (
+			&error, GOA_ERROR,
+			GOA_ERROR_FAILED, /* TODO: more specific*/
+			_("Failed to find ASUrl and OABUrl in autodiscover response"));
+			goto out;
+	}
+
+	for (idx = 0; idx < size; idx++) {
+		if (data->msgs[idx] != NULL) {
+			/* Since we are cancelling from the same thread
+			 * that we queued the message, the callback (ie.
+			 * this function) will be invoked before
+			 * soup_session_cancel_message returns. */
+			soup_session_cancel_message (
+				data->session, data->msgs[idx],
+				SOUP_STATUS_CANCELLED);
+			data->msgs[idx] = NULL;
+		}
+	}
+
+out:
+	if (error != NULL) {
+		for (idx = 0; idx < size; idx++) {
+			if (data->msgs[idx] != NULL) {
+				/* There's another request outstanding.
+				 * Hope that it has better luck. */
+				g_clear_error (&error);
+				return;
+			}
+		}
+		g_simple_async_result_take_error (simple, error);
+	}
+
+	g_simple_async_result_complete_in_idle (simple);
+	g_object_unref (simple);
+}
+
+static xmlDoc *
+ews_create_autodiscover_xml (const gchar *email)
+{
+	xmlDoc *doc;
+	xmlNode *node;
+	xmlNs *ns;
+
+	doc = xmlNewDoc ((xmlChar *) "1.0");
+
+	node = xmlNewDocNode (doc, NULL, (xmlChar *) "Autodiscover", NULL);
+	xmlDocSetRootElement (doc, node);
+	ns = xmlNewNs (
+		node,
+		(xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006";,
+		NULL);
+
+	node = xmlNewChild (node, ns, (xmlChar *) "Request", NULL);
+	xmlNewChild (node, ns, (xmlChar *) "EMailAddress", (xmlChar *) email);
+	xmlNewChild (
+		node, ns,
+		(xmlChar *) "AcceptableResponseSchema",
+		(xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";);
+
+	return doc;
+}
+
+static void
+ews_post_restarted_cb (SoupMessage *msg, gpointer data)
+{
+	xmlOutputBuffer *buf = data;
+
+	/* In violation of RFC2616, libsoup will change a
+	 * POST request to a GET on receiving a 302 redirect. */
+	g_debug ("Working around libsoup bug with redirect");
+	g_object_set (msg, SOUP_MESSAGE_METHOD, "POST", NULL);
+
+	soup_message_set_request (
+		msg, "text/xml; charset=utf-8",
+		SOUP_MEMORY_COPY,
+		(gchar *) buf->buffer->content,
+		buf->buffer->use);
+}
+
+static SoupMessage *
+ews_create_msg_for_url (const gchar *url,
+                        xmlOutputBuffer *buf)
+{
+	SoupMessage *msg;
+
+	msg = soup_message_new (buf != NULL ? "POST" : "GET", url);
+	soup_message_headers_append (
+		msg->request_headers, "User-Agent", "libews/0.1");
+
+	if (buf != NULL) {
+		soup_message_set_request (
+			msg, "text/xml; charset=utf-8",
+			SOUP_MEMORY_COPY,
+			(gchar *) buf->buffer->content,
+			buf->buffer->use);
+		g_signal_connect (
+			msg, "restarted",
+			G_CALLBACK (ews_post_restarted_cb), buf);
+	}
+
+	soup_buffer_free (
+		soup_message_body_flatten (
+		SOUP_MESSAGE (msg)->request_body));
+
+	g_debug ("The request headers");
+	g_debug ("===================");
+	g_debug ("%s", SOUP_MESSAGE (msg)->request_body->data);
+
+	return msg;
+}
+
+void
+goa_ews_autodiscover (GoaObject *goa_object,
+                      GCancellable *cancellable,
+                      GAsyncReadyCallback callback,
+                      gpointer user_data)
+{
+	/* XXX This function is only called if HAVE_GOA_PASSWORD_BASED
+	 *     is defined, so don't worry about a fallback behavior. */
+#ifdef HAVE_GOA_PASSWORD_BASED
+	GoaAccount *goa_account;
+	GoaExchange *goa_exchange;
+	GoaPasswordBased *goa_password;
+	GSimpleAsyncResult *simple;
+	AutodiscoverData *data;
+	AutodiscoverAuthData *auth;
+	gchar *url1;
+	gchar *url2;
+	xmlDoc *doc;
+	xmlOutputBuffer *buf;
+	gchar *email;
+	gchar *host;
+	gchar *password = NULL;
+	GError *error = NULL;
+
+	g_return_if_fail (GOA_IS_OBJECT (goa_object));
+
+	goa_account = goa_object_get_account (goa_object);
+	goa_exchange = goa_object_get_exchange (goa_object);
+	goa_password = goa_object_get_password_based (goa_object);
+
+	email = goa_account_dup_presentation_identity (goa_account);
+	host = goa_exchange_dup_host (goa_exchange);
+
+	doc = ews_create_autodiscover_xml (email);
+	buf = xmlAllocOutputBuffer (NULL);
+	xmlNodeDumpOutput (buf, doc, xmlDocGetRootElement (doc), 0, 1, NULL);
+	xmlOutputBufferFlush (buf);
+
+	url1 = g_strdup_printf (
+		"https://%s/autodiscover/autodiscover.xml";, host);
+	url2 = g_strdup_printf (
+		"https://autodiscover.%s/autodiscover/autodiscover.xml";, host);
+
+	g_free (host);
+	g_free (email);
+
+	/* http://msdn.microsoft.com/en-us/library/ee332364.aspx says we are
+	* supposed to try $domain and then autodiscover.$domain. But some
+	* people have broken firewalls on the former which drop packets
+	* instead of rejecting connections, and make the request take ages
+	* to time out. So run both queries in parallel and let the fastest
+	* (successful) one win. */
+	data = g_slice_new0 (AutodiscoverData);
+	data->buf = buf;
+	data->msgs[0] = ews_create_msg_for_url (url1, buf);
+	data->msgs[1] = ews_create_msg_for_url (url2, buf);
+	data->session = soup_session_async_new_with_options (
+		SOUP_SESSION_USE_NTLM, TRUE,
+		SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL);
+	if (G_IS_CANCELLABLE (cancellable)) {
+		data->cancellable = g_object_ref (cancellable);
+		data->cancellable_id = g_cancellable_connect (
+			data->cancellable,
+			G_CALLBACK (ews_autodiscover_cancelled_cb),
+			data, NULL);
+	}
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (goa_object), callback,
+		user_data, goa_ews_autodiscover);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, data, (GDestroyNotify) ews_autodiscover_data_free);
+
+	goa_password_based_call_get_password_sync (
+		goa_password, "", &password, cancellable, &error);
+
+	/* Sanity check */
+	g_return_if_fail (
+		((password != NULL) && (error == NULL)) ||
+		((password == NULL) && (error != NULL)));
+
+	if (error == NULL) {
+		gchar *username;
+
+		username = goa_account_dup_identity (goa_account);
+
+		auth = g_slice_new0 (AutodiscoverAuthData);
+		auth->username = username;  /* takes ownership */
+		auth->password = password;  /* takes ownership */
+
+		g_signal_connect_data (
+			data->session, "authenticate",
+			G_CALLBACK (ews_authenticate), auth,
+			ews_autodiscover_auth_data_free, 0);
+
+		soup_session_queue_message (
+			data->session, data->msgs[0],
+			ews_autodiscover_response_cb, simple);
+		soup_session_queue_message (
+			data->session, data->msgs[1],
+			ews_autodiscover_response_cb, simple);
+	} else {
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete_in_idle (simple);
+		g_object_unref (simple);
+	}
+
+	g_free (url2);
+	g_free (url1);
+	xmlFreeDoc (doc);
+
+	g_object_unref (goa_account);
+	g_object_unref (goa_exchange);
+	g_object_unref (goa_password);
+#endif /* HAVE_GOA_PASSWORD_BASED */
+}
+
+gboolean
+goa_ews_autodiscover_finish (GoaObject *goa_object,
+                             GAsyncResult *result,
+                             gchar **out_as_url,
+                             gchar **out_oab_url,
+                             GError **error)
+{
+	GSimpleAsyncResult *simple;
+	AutodiscoverData *data;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (goa_object),
+		goa_ews_autodiscover), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	data = g_simple_async_result_get_op_res_gpointer (simple);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return FALSE;
+
+	if (out_as_url != NULL) {
+		*out_as_url = data->as_url;
+		data->as_url = NULL;
+	}
+
+	if (out_oab_url != NULL) {
+		*out_oab_url = data->oab_url;
+		data->oab_url = NULL;
+	}
+
+	return TRUE;
+}
+
+gboolean
+goa_ews_autodiscover_sync (GoaObject *goa_object,
+                           gchar **out_as_url,
+                           gchar **out_oab_url,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	gboolean success;
+
+	g_return_val_if_fail (GOA_IS_OBJECT (goa_object), FALSE);
+
+	closure = e_async_closure_new ();
+
+	goa_ews_autodiscover (
+		goa_object, cancellable,
+		e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	success = goa_ews_autodiscover_finish (
+		goa_object, result, out_as_url, out_oab_url, error);
+
+	e_async_closure_free (closure);
+
+	return success;
+}
+
diff --git a/modules/online-accounts/goaewsclient.h b/modules/online-accounts/goaewsclient.h
new file mode 100644
index 0000000..7798c59
--- /dev/null
+++ b/modules/online-accounts/goaewsclient.h
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+/* XXX This is a rather hacked up version of GoaEwsClient from
+ *     GNOME Online Accounts which returns the discovered URLs. */
+
+#ifndef __GOA_EWS_CLIENT_H__
+#define __GOA_EWS_CLIENT_H__
+
+/* XXX Yeah, yeah... */
+#define GOA_API_IS_SUBJECT_TO_CHANGE
+
+#include <goa/goa.h>
+
+G_BEGIN_DECLS
+
+void		goa_ews_autodiscover		(GoaObject *goa_object,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback  callback,
+						 gpointer user_data);
+gboolean	goa_ews_autodiscover_finish	(GoaObject *goa_object,
+						 GAsyncResult *result,
+						 gchar **out_as_url,
+						 gchar **out_oab_url,
+						 GError **error);
+gboolean	goa_ews_autodiscover_sync	(GoaObject *goa_object,
+						 gchar **out_as_url,
+						 gchar **out_oab_url,
+						 GCancellable *cancellable,
+						 GError **error);
+
+G_END_DECLS
+
+#endif /* __GOA_EWS_CLIENT_H__ */
diff --git a/modules/online-accounts/module-online-accounts.c b/modules/online-accounts/module-online-accounts.c
new file mode 100644
index 0000000..9bcede7
--- /dev/null
+++ b/modules/online-accounts/module-online-accounts.c
@@ -0,0 +1,903 @@
+/*
+ * module-online-accounts.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/>
+ *
+ */
+
+/* XXX Yeah, yeah... */
+#define GOA_API_IS_SUBJECT_TO_CHANGE
+
+#include <config.h>
+#include <goa/goa.h>
+#include <gnome-keyring.h>
+
+#include <libedataserver/e-uid.h>
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-source-authentication.h>
+#include <libedataserver/e-source-camel.h>
+#include <libedataserver/e-source-collection.h>
+#include <libedataserver/e-source-goa.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-extension.h>
+#include <libebackend/e-server-side-source.h>
+#include <libebackend/e-source-registry-server.h>
+
+#include "goaewsclient.h"
+
+/* Standard GObject macros */
+#define E_TYPE_ONLINE_ACCOUNTS \
+	(e_online_accounts_get_type ())
+#define E_ONLINE_ACCOUNTS(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ONLINE_ACCOUNTS, EOnlineAccounts))
+
+#define CAMEL_OAUTH_MECHANISM_NAME  "XOAUTH"
+
+typedef struct _EOnlineAccounts EOnlineAccounts;
+typedef struct _EOnlineAccountsClass EOnlineAccountsClass;
+
+struct _EOnlineAccounts {
+	EExtension parent;
+
+	GoaClient *goa_client;
+	GCancellable *create_client;
+
+	/* GoaAccount ID -> ESource UID */
+	GHashTable *goa_to_eds;
+};
+
+struct _EOnlineAccountsClass {
+	EExtensionClass parent_class;
+};
+
+/* The keyring definintions are copied from e-authentication-session.c */
+
+#define KEYRING_ITEM_ATTRIBUTE_NAME	"e-source-uid"
+#define KEYRING_ITEM_DISPLAY_FORMAT	"Evolution Data Source %s"
+
+static GnomeKeyringPasswordSchema schema = {
+	GNOME_KEYRING_ITEM_GENERIC_SECRET,
+	{
+		{ KEYRING_ITEM_ATTRIBUTE_NAME,
+		  GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+		{ NULL, 0 }
+	}
+};
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_online_accounts_get_type (void);
+
+G_DEFINE_DYNAMIC_TYPE (
+	EOnlineAccounts,
+	e_online_accounts,
+	E_TYPE_EXTENSION)
+
+static const gchar *
+online_accounts_get_backend_name (const gchar *goa_provider_type)
+{
+	const gchar *eds_backend_name = NULL;
+
+	/* This is a mapping between GoaAccount provider types and
+	 * ESourceCollection backend names.  It requires knowledge
+	 * of other registry modules, possibly even from 3rd party
+	 * packages.  No way around it. */
+
+	if (g_strcmp0 (goa_provider_type, "exchange") == 0)
+		eds_backend_name = "ews";
+
+	if (g_strcmp0 (goa_provider_type, "google") == 0)
+		eds_backend_name = "google";
+
+	else if (g_strcmp0 (goa_provider_type, "yahoo") == 0)
+		eds_backend_name = "yahoo";
+
+	return eds_backend_name;
+}
+
+static ESourceRegistryServer *
+online_accounts_get_server (EOnlineAccounts *extension)
+{
+	EExtensible *extensible;
+
+	extensible = e_extension_get_extensible (E_EXTENSION (extension));
+
+	return E_SOURCE_REGISTRY_SERVER (extensible);
+}
+
+static gboolean
+online_accounts_provider_type_to_backend_name (GBinding *binding,
+                                               const GValue *source_value,
+                                               GValue *target_value,
+                                               gpointer unused)
+{
+	const gchar *provider_type;
+	const gchar *backend_name;
+
+	provider_type = g_value_get_string (source_value);
+	backend_name = online_accounts_get_backend_name (provider_type);
+	g_return_val_if_fail (backend_name != NULL, FALSE);
+	g_value_set_string (target_value, backend_name);
+
+	return TRUE;
+}
+
+static gboolean
+online_accounts_object_is_non_null (GBinding *binding,
+                                    const GValue *source_value,
+                                    GValue *target_value,
+                                    gpointer unused)
+{
+	gpointer v_object;
+
+	v_object = g_value_get_object (source_value);
+	g_value_set_boolean (target_value, v_object != NULL);
+
+	return TRUE;
+}
+
+static ESource *
+online_accounts_new_source (EOnlineAccounts *extension)
+{
+	ESourceRegistryServer *server;
+	ESource *source;
+	GFile *file;
+	GError *error = NULL;
+
+	/* This being a brand new data source, creating the instance
+	 * should never fail but we'll check for errors just the same. */
+	server = online_accounts_get_server (extension);
+	file = e_server_side_source_new_user_file (NULL);
+	source = e_server_side_source_new (server, file, &error);
+	g_object_unref (file);
+
+	if (error != NULL) {
+		g_warn_if_fail (source == NULL);
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+
+	return source;
+}
+
+static void
+online_accounts_config_exchange (EOnlineAccounts *extension,
+                                 ESource *source,
+                                 GoaObject *goa_object)
+{
+#ifdef HAVE_GOA_PASSWORD_BASED
+	ESourceExtension *source_extension;
+	const gchar *extension_name;
+	gchar *as_url = NULL;
+	gchar *oab_url = NULL;
+	gpointer class;
+	GError *error = NULL;
+
+	if (goa_object_peek_exchange (goa_object) == NULL)
+		return;
+
+	/* This should force the ESourceCamelEws type to be registered.
+	 * It will also tell us if Evolution-EWS is even installed. */
+	class = g_type_class_ref (g_type_from_name ("EEwsBackend"));
+	if (class != NULL) {
+		g_type_class_unref (class);
+	} else {
+		g_critical (
+			"%s: Could not locate EEwsBackendClass. "
+			"Is Evolution-EWS installed?", G_STRFUNC);
+		return;
+	}
+
+	/* XXX GNOME Online Accounts already runs autodiscover to test
+	 *     the user-entered values but doesn't share the discovered
+	 *     URLs.  It only provides us a host name and expects us to
+	 *     re-run autodiscover for ourselves.
+	 *
+	 *     So I've copied a slab of code from GOA which was in turn
+	 *     copied from Evolution-EWS which does the autodiscovery.
+	 *
+	 *     I've already complained to Debarshi Ray about the lack
+	 *     of useful info in GOA's Exchange interface so hopefully
+	 *     it will someday publish discovered URLs and then we can
+	 *     remove this hack. */
+
+	goa_ews_autodiscover_sync (
+		goa_object, &as_url, &oab_url, NULL, &error);
+
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+		return;
+	}
+
+	g_return_if_fail (as_url != NULL);
+	g_return_if_fail (oab_url != NULL);
+
+	/* XXX We don't have direct access to CamelEwsSettings from here
+	 *     since it's defined in Evolution-EWS.  But we can find out
+	 *     its extension name and set properties by name. */
+
+	extension_name = e_source_camel_get_extension_name ("ews");
+	source_extension = e_source_get_extension (source, extension_name);
+
+	/* This will be NULL if Evolution-EWS is not installed. */
+	if (source_extension != NULL) {
+		g_object_set (
+			source_extension,
+			"hosturl", as_url,
+			"oaburl", oab_url,
+			NULL);
+	} else {
+		g_critical (
+			"%s: Failed to create [%s] extension",
+			G_STRFUNC, extension_name);
+	}
+
+	g_free (as_url);
+	g_free (oab_url);
+#endif /* HAVE_GOA_PASSWORD_BASED */
+}
+
+static void
+online_accounts_config_oauth (EOnlineAccounts *extension,
+                              ESource *source,
+                              GoaObject *goa_object)
+{
+	ESourceExtension *source_extension;
+	const gchar *extension_name;
+
+	if (goa_object_peek_oauth_based (goa_object) == NULL)
+		return;
+
+	extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
+	source_extension = e_source_get_extension (source, extension_name);
+
+	e_source_authentication_set_method (
+		E_SOURCE_AUTHENTICATION (source_extension),
+		CAMEL_OAUTH_MECHANISM_NAME);
+}
+
+static void
+online_accounts_config_password (EOnlineAccounts *extension,
+                                 ESource *source,
+                                 GoaObject *goa_object)
+{
+#ifdef HAVE_GOA_PASSWORD_BASED
+	GoaAccount *goa_account;
+	GoaPasswordBased *goa_password_based;
+	GnomeKeyringResult keyring_result;
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	const gchar *uid;
+	gchar *arg_id;
+	gchar *display_name;
+	gchar *password = NULL;
+	GError *error = NULL;
+
+	/* If the GNOME Online Account is password-based, we use its
+	 * password to seed our own keyring entry for the collection
+	 * source which avoids having to special-case authentication
+	 * like we do for OAuth.  Plus, if the stored password is no
+	 * good we'll prompt for a new one instead of just giving up. */
+
+	goa_password_based = goa_object_get_password_based (goa_object);
+
+	if (goa_password_based == NULL)
+		return;
+
+	closure = e_async_closure_new ();
+
+	/* XXX The GOA documentation doesn't explain the string
+	 *     argument in goa_password_based_get_password() so
+	 *     we'll pass in the identity and hope for the best. */
+	goa_account = goa_object_get_account (goa_object);
+	arg_id = goa_account_dup_identity (goa_account);
+	g_object_unref (goa_account);
+
+	goa_password_based_call_get_password (
+		goa_password_based, arg_id, NULL,
+		e_async_closure_callback, closure);
+
+	g_free (arg_id);
+
+	result = e_async_closure_wait (closure);
+
+	goa_password_based_call_get_password_finish (
+		goa_password_based, &password, result, &error);
+
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+		goto exit;
+	}
+
+	uid = e_source_get_uid (source);
+	display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
+
+	/* XXX Just call gnome-keyring synchronously.  I know it's
+	 *     evil, but I want to know the password has been stored
+	 *     before returning from this function.  We'll be moving
+	 *     to libsecret soon anyway, which is more GIO-based, so
+	 *     we could then reuse the EAsyncClosure here. */
+	keyring_result = gnome_keyring_store_password_sync (
+		&schema, GNOME_KEYRING_DEFAULT, display_name,
+		password, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+
+	g_free (display_name);
+
+	/* If we fail to store the password, we'll just end up prompting
+	 * for a password like normal.  Annoying, maybe, but not the end
+	 * of the world.  Still leave a breadcrumb for debugging though. */
+	if (keyring_result != GNOME_KEYRING_RESULT_OK) {
+		const gchar *message;
+		message = gnome_keyring_result_to_message (keyring_result);
+		g_warning ("%s: %s", G_STRFUNC, message);
+	}
+
+exit:
+	e_async_closure_free (closure);
+	g_object_unref (goa_password_based);
+#endif /* HAVE_GOA_PASSWORD_BASED */
+}
+
+static void
+online_accounts_config_collection (EOnlineAccounts *extension,
+                                   ESource *source,
+                                   GoaObject *goa_object)
+{
+	GoaAccount *goa_account;
+	ESourceExtension *source_extension;
+	const gchar *extension_name;
+
+	goa_account = goa_object_get_account (goa_object);
+
+	g_object_bind_property (
+		goa_account, "presentation-identity",
+		source, "display-name",
+		G_BINDING_SYNC_CREATE);
+
+	extension_name = E_SOURCE_EXTENSION_GOA;
+	source_extension = e_source_get_extension (source, extension_name);
+
+	g_object_bind_property (
+		goa_account, "id",
+		source_extension, "account-id",
+		G_BINDING_SYNC_CREATE);
+
+	extension_name = E_SOURCE_EXTENSION_COLLECTION;
+	source_extension = e_source_get_extension (source, extension_name);
+
+	g_object_bind_property_full (
+		goa_account, "provider-type",
+		source_extension, "backend-name",
+		G_BINDING_SYNC_CREATE,
+		online_accounts_provider_type_to_backend_name,
+		NULL,
+		NULL, (GDestroyNotify) NULL);
+
+	g_object_bind_property (
+		goa_account, "identity",
+		source_extension, "identity",
+		G_BINDING_SYNC_CREATE);
+
+	g_object_bind_property_full (
+		goa_object, "calendar",
+		source_extension, "calendar-enabled",
+		G_BINDING_SYNC_CREATE,
+		online_accounts_object_is_non_null,
+		NULL,
+		NULL, (GDestroyNotify) NULL);
+
+	g_object_bind_property_full (
+		goa_object, "contacts",
+		source_extension, "contacts-enabled",
+		G_BINDING_SYNC_CREATE,
+		online_accounts_object_is_non_null,
+		NULL,
+		NULL, (GDestroyNotify) NULL);
+
+	g_object_bind_property_full (
+		goa_object, "mail",
+		source_extension, "mail-enabled",
+		G_BINDING_SYNC_CREATE,
+		online_accounts_object_is_non_null,
+		NULL,
+		NULL, (GDestroyNotify) NULL);
+
+	g_object_unref (goa_account);
+
+	/* Handle optional GOA interfaces. */
+	online_accounts_config_exchange (extension, source, goa_object);
+	online_accounts_config_password (extension, source, goa_object);
+
+	/* The data source should not be removable by clients. */
+	e_server_side_source_set_removable (
+		E_SERVER_SIDE_SOURCE (source), FALSE);
+}
+
+static void
+online_accounts_config_mail_account (EOnlineAccounts *extension,
+                                     ESource *source,
+                                     GoaObject *goa_object)
+{
+	online_accounts_config_oauth (extension, source, goa_object);
+
+	/* XXX Need to defer the network security settings to the
+	 *     provider-specific module since "imap-use-tls" tells
+	 *     us neither the port number, nor whether to use IMAP
+	 *     over SSL versus STARTTLS.  The module will know. */
+}
+
+static void
+online_accounts_config_mail_identity (EOnlineAccounts *extension,
+                                      ESource *source,
+                                      GoaObject *goa_object)
+{
+	GoaMail *goa_mail;
+	ESourceExtension *source_extension;
+	const gchar *extension_name;
+
+	goa_mail = goa_object_get_mail (goa_object);
+	g_return_if_fail (goa_mail != NULL);
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+	source_extension = e_source_get_extension (source, extension_name);
+
+	g_object_bind_property (
+		goa_mail, "email-address",
+		source_extension, "address",
+		G_BINDING_SYNC_CREATE);
+
+	g_object_unref (goa_mail);
+}
+
+static void
+online_accounts_config_mail_transport (EOnlineAccounts *extension,
+                                       ESource *source,
+                                       GoaObject *goa_object)
+{
+	online_accounts_config_oauth (extension, source, goa_object);
+
+	/* XXX Need to defer the network security settings to the
+	 *     provider-specific module since "smtp-use-tls" tells
+	 *     us neither the port number, nor whether to use SMTP
+	 *     over SSL versus STARTTLS.  The module will know. */
+}
+
+static void
+online_accounts_create_collection (EOnlineAccounts *extension,
+                                   EBackendFactory *backend_factory,
+                                   GoaObject *goa_object)
+{
+	GoaAccount *goa_account;
+	ESourceRegistryServer *server;
+	ESource *collection_source;
+	ESource *mail_account_source;
+	ESource *mail_identity_source;
+	ESource *mail_transport_source;
+	const gchar *account_id;
+	const gchar *parent_uid;
+
+	server = online_accounts_get_server (extension);
+
+	collection_source = online_accounts_new_source (extension);
+	g_return_if_fail (E_IS_SOURCE (collection_source));
+
+	mail_account_source = online_accounts_new_source (extension);
+	g_return_if_fail (E_IS_SOURCE (mail_account_source));
+
+	mail_identity_source = online_accounts_new_source (extension);
+	g_return_if_fail (E_IS_SOURCE (mail_identity_source));
+
+	mail_transport_source = online_accounts_new_source (extension);
+	g_return_if_fail (E_IS_SOURCE (mail_transport_source));
+
+	/* Configure parent/child relationships. */
+	parent_uid = e_source_get_uid (collection_source);
+	e_source_set_parent (mail_account_source, parent_uid);
+	e_source_set_parent (mail_identity_source, parent_uid);
+	e_source_set_parent (mail_transport_source, parent_uid);
+
+	/* Give the factory first crack at mail configuration. */
+	e_collection_backend_factory_prepare_mail (
+		E_COLLECTION_BACKEND_FACTORY (backend_factory),
+		mail_account_source,
+		mail_identity_source,
+		mail_transport_source);
+
+	/* Now it's our turn. */
+	online_accounts_config_collection (
+		extension, collection_source, goa_object);
+	online_accounts_config_mail_account (
+		extension, mail_account_source, goa_object);
+	online_accounts_config_mail_identity (
+		extension, mail_identity_source, goa_object);
+	online_accounts_config_mail_transport (
+		extension, mail_transport_source, goa_object);
+
+	/* Export the new source collection. */
+	e_source_registry_server_add_source (server, collection_source);
+	e_source_registry_server_add_source (server, mail_account_source);
+	e_source_registry_server_add_source (server, mail_identity_source);
+	e_source_registry_server_add_source (server, mail_transport_source);
+
+	goa_account = goa_object_get_account (goa_object);
+	account_id = goa_account_get_id (goa_account);
+
+	g_hash_table_insert (
+		extension->goa_to_eds,
+		g_strdup (account_id),
+		g_strdup (parent_uid));
+
+	g_object_unref (goa_account);
+
+	g_object_unref (collection_source);
+	g_object_unref (mail_account_source);
+	g_object_unref (mail_identity_source);
+	g_object_unref (mail_transport_source);
+}
+
+static void
+online_accounts_remove_collection (EOnlineAccounts *extension,
+                                   ESource *source)
+{
+	GError *error = NULL;
+
+	/* This removes the entire subtree rooted at source.
+	 * Deletes the corresponding on-disk key files too. */
+	e_source_remove_sync (source, NULL, &error);
+
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+online_accounts_account_added_cb (GoaClient *goa_client,
+                                  GoaObject *goa_object,
+                                  EOnlineAccounts *extension)
+{
+	GoaAccount *goa_account;
+	ESourceRegistryServer *server;
+	EBackendFactory *backend_factory = NULL;
+	const gchar *provider_type;
+	const gchar *backend_name;
+	const gchar *account_id;
+	const gchar *source_uid;
+
+	server = online_accounts_get_server (extension);
+
+	goa_account = goa_object_get_account (goa_object);
+	provider_type = goa_account_get_provider_type (goa_account);
+	backend_name = online_accounts_get_backend_name (provider_type);
+
+	account_id = goa_account_get_id (goa_account);
+	source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
+
+	if (source_uid == NULL && backend_name != NULL)
+		backend_factory = e_data_factory_ref_backend_factory (
+			E_DATA_FACTORY (server), backend_name);
+
+	if (backend_factory != NULL) {
+		online_accounts_create_collection (
+			extension, backend_factory, goa_object);
+		g_object_unref (backend_factory);
+	}
+
+	g_object_unref (goa_account);
+}
+
+static void
+online_accounts_account_removed_cb (GoaClient *goa_client,
+                                    GoaObject *goa_object,
+                                    EOnlineAccounts *extension)
+{
+	ESource *source = NULL;
+	ESourceRegistryServer *server;
+	GoaAccount *goa_account;
+	const gchar *account_id;
+	const gchar *source_uid;
+
+	server = online_accounts_get_server (extension);
+
+	goa_account = goa_object_get_account (goa_object);
+
+	account_id = goa_account_get_id (goa_account);
+	source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
+
+	if (source_uid != NULL)
+		source = e_source_registry_server_ref_source (
+			server, source_uid);
+
+	if (source != NULL) {
+		online_accounts_remove_collection (extension, source);
+		g_object_unref (source);
+	}
+
+	g_object_unref (goa_account);
+}
+
+static gint
+online_accounts_compare_id (GoaObject *goa_object,
+                            const gchar *target_id)
+{
+	GoaAccount *goa_account;
+	const gchar *account_id;
+	gint result;
+
+	goa_account = goa_object_get_account (goa_object);
+	account_id = goa_account_get_id (goa_account);
+	result = g_strcmp0 (account_id, target_id);
+	g_object_unref (goa_account);
+
+	return result;
+}
+
+static void
+online_accounts_populate_accounts_table (EOnlineAccounts *extension,
+                                         GList *goa_objects)
+{
+	ESourceRegistryServer *server;
+	GQueue trash = G_QUEUE_INIT;
+	GList *list, *link;
+	const gchar *extension_name;
+
+	server = online_accounts_get_server (extension);
+
+	extension_name = E_SOURCE_EXTENSION_GOA;
+	list = e_source_registry_server_list_sources (server, extension_name);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESource *source;
+		ESourceGoa *goa_ext;
+		const gchar *account_id;
+		const gchar *source_uid;
+		GList *match;
+
+		source = E_SOURCE (link->data);
+		source_uid = e_source_get_uid (source);
+
+		extension_name = E_SOURCE_EXTENSION_GOA;
+		goa_ext = e_source_get_extension (source, extension_name);
+		account_id = e_source_goa_get_account_id (goa_ext);
+
+		if (account_id == NULL)
+			continue;
+
+		/* Verify the GOA account still exists. */
+		match = g_list_find_custom (
+			goa_objects, account_id,
+			(GCompareFunc) online_accounts_compare_id);
+
+		/* If a matching GoaObject was found, add its ID
+		 * to our accounts hash table.  Otherwise remove
+		 * the ESource after we finish looping. */
+		if (match != NULL) {
+			GoaObject *goa_object;
+
+			g_hash_table_insert (
+				extension->goa_to_eds,
+				g_strdup (account_id),
+				g_strdup (source_uid));
+
+			goa_object = GOA_OBJECT (match->data);
+			online_accounts_config_collection (
+				extension, source, goa_object);
+		} else {
+			g_queue_push_tail (&trash, source);
+		}
+	}
+
+	/* Empty the trash. */
+	while (!g_queue_is_empty (&trash)) {
+		ESource *source = g_queue_pop_head (&trash);
+		online_accounts_remove_collection (extension, source);
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+static void
+online_accounts_create_client_cb (GObject *source_object,
+                                  GAsyncResult *result,
+                                  gpointer user_data)
+{
+	EOnlineAccounts *extension;
+	GoaClient *goa_client;
+	GList *list, *link;
+	GError *error = NULL;
+
+	/* If we get back a G_IO_ERROR_CANCELLED then it means the
+	 * EOnlineAccounts is already finalized, so be careful not
+	 * to touch it until after we have a valid GoaClient. */
+
+	goa_client = goa_client_new_finish (result, &error);
+
+	if (error != NULL) {
+		g_warn_if_fail (goa_client == NULL);
+		g_warning (
+			"Unable to connect to the GNOME Online "
+			"Accounts service: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	g_return_if_fail (GOA_IS_CLIENT (goa_client));
+
+	/* Should be safe to dereference the EOnlineAccounts now. */
+
+	extension = E_ONLINE_ACCOUNTS (user_data);
+	extension->goa_client = goa_client;  /* takes ownership */
+
+	/* Don't need the GCancellable anymore. */
+	g_object_unref (extension->create_client);
+	extension->create_client = NULL;
+
+	list = goa_client_get_accounts (extension->goa_client);
+
+	/* This populates a hash table of GOA ID -> ESource UID strings by
+	 * searching through available data sources for ones with a "GNOME
+	 * Online Accounts" extension.  If such an extension is found, but
+	 * no corresponding GoaAccount (presumably meaning the GOA account
+	 * was somehow deleted between E-D-S sessions) then the ESource in
+	 * which the extension was found gets deleted. */
+	online_accounts_populate_accounts_table (extension, list);
+
+	for (link = list; link != NULL; link = g_list_next (link))
+		online_accounts_account_added_cb (
+			extension->goa_client,
+			GOA_OBJECT (link->data),
+			extension);
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+	/* Listen for Online Account changes. */
+	g_signal_connect (
+		extension->goa_client, "account-added",
+		G_CALLBACK (online_accounts_account_added_cb), extension);
+	g_signal_connect (
+		extension->goa_client, "account-removed",
+		G_CALLBACK (online_accounts_account_removed_cb), extension);
+}
+
+static void
+online_accounts_bus_acquired_cb (EDBusServer *server,
+                                 GDBusConnection *connection,
+                                 EOnlineAccounts *extension)
+{
+	/* Connect to the GNOME Online Accounts service. */
+
+	/* Note we don't reference the extension.  If the
+	 * extension gets destroyed before this completes
+	 * we cancel the operation from dispose(). */
+	goa_client_new (
+		extension->create_client,
+		online_accounts_create_client_cb,
+		extension);
+}
+
+static void
+online_accounts_dispose (GObject *object)
+{
+	EOnlineAccounts *extension;
+
+	extension = E_ONLINE_ACCOUNTS (object);
+
+	if (extension->goa_client != NULL) {
+		g_signal_handlers_disconnect_matched (
+			extension->goa_client,
+			G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (extension->goa_client);
+		extension->goa_client = NULL;
+	}
+
+	/* This cancels goa_client_new() in case it still
+	 * hasn't completed.  We're no longer interested. */
+	if (extension->create_client != NULL) {
+		g_cancellable_cancel (extension->create_client);
+		g_object_unref (extension->create_client);
+		extension->create_client = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_online_accounts_parent_class)->dispose (object);
+}
+
+static void
+online_accounts_finalize (GObject *object)
+{
+	EOnlineAccounts *extension;
+
+	extension = E_ONLINE_ACCOUNTS (object);
+
+	g_hash_table_destroy (extension->goa_to_eds);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_online_accounts_parent_class)->finalize (object);
+}
+
+static void
+online_accounts_constructed (GObject *object)
+{
+	EExtension *extension;
+	EExtensible *extensible;
+
+	extension = E_EXTENSION (object);
+	extensible = e_extension_get_extensible (extension);
+
+	/* Wait for the registry service to acquire its well-known
+	 * bus name so we don't do anything destructive beforehand. */
+
+	g_signal_connect (
+		extensible, "bus-acquired",
+		G_CALLBACK (online_accounts_bus_acquired_cb), extension);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_online_accounts_parent_class)->constructed (object);
+}
+
+static void
+e_online_accounts_class_init (EOnlineAccountsClass *class)
+{
+	GObjectClass *object_class;
+	EExtensionClass *extension_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = online_accounts_dispose;
+	object_class->finalize = online_accounts_finalize;
+	object_class->constructed = online_accounts_constructed;
+
+	extension_class = E_EXTENSION_CLASS (class);
+	extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
+}
+
+static void
+e_online_accounts_class_finalize (EOnlineAccountsClass *class)
+{
+}
+
+static void
+e_online_accounts_init (EOnlineAccounts *extension)
+{
+	/* Used to cancel unfinished goa_client_new(). */
+	extension->create_client = g_cancellable_new ();
+
+	extension->goa_to_eds = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_free);
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+	e_online_accounts_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
+



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