[evolution-data-server] Add OAuth support to Google Contacts backend.



commit a8a17cfbcd82447e670c508c4dc89c201fbcef91
Author: Matthew Barnes <mbarnes redhat com>
Date:   Sun Jun 26 07:54:44 2011 -0400

    Add OAuth support to Google Contacts backend.
    
    For ESources tagged with a "goa-account-id" property set by Evolution,
    authenticate using OAuth 1.0 via libgoa-1.0 and libgdata 0.9.

 addressbook/backends/google/Makefile.am            |   49 ++-
 .../backends/google/e-book-backend-google.c        |  339 ++++++++-----
 .../backends/google/e-gdata-goa-authorizer.c       |  524 ++++++++++++++++++++
 .../backends/google/e-gdata-goa-authorizer.h       |   68 +++
 addressbook/libedata-book/Makefile.am              |    6 +-
 addressbook/libedata-book/e-data-book-factory.c    |   86 ++++
 configure.ac                                       |   36 ++-
 7 files changed, 963 insertions(+), 145 deletions(-)
---
diff --git a/addressbook/backends/google/Makefile.am b/addressbook/backends/google/Makefile.am
index 581e662..c4a4684 100644
--- a/addressbook/backends/google/Makefile.am
+++ b/addressbook/backends/google/Makefile.am
@@ -1,29 +1,40 @@
 ebook_backend_LTLIBRARIES = libebookbackendgoogle.la
 
+if HAVE_GOA
+GOA_SOURCES = \
+	e-gdata-goa-authorizer.c \
+	e-gdata-goa-authorizer.h
+endif
+
 libebookbackendgoogle_la_CPPFLAGS = \
-	$(AM_CPPFLAGS)					\
-	-DG_LOG_DOMAIN=\"libebookbackendgoogle\"	\
-	-I$(top_srcdir)					\
-	-I$(top_builddir)				\
-	-I$(top_srcdir)/addressbook			\
-	-I$(top_builddir)/addressbook			\
-	$(SOUP_CFLAGS)					\
-	$(EVOLUTION_ADDRESSBOOK_CFLAGS)			\
-	$(GDATA_CFLAGS)
+	$(AM_CPPFLAGS) \
+	-DG_LOG_DOMAIN=\"libebookbackendgoogle\" \
+	-I$(top_srcdir) \
+	-I$(top_builddir) \
+	-I$(top_srcdir)/addressbook \
+	-I$(top_builddir)/addressbook \
+	$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
+	$(SOUP_CFLAGS) \
+	$(GDATA_CFLAGS) \
+	$(GOA_CFLAGS) \
+	$(OAUTH_CFLAGS)
 
 libebookbackendgoogle_la_SOURCES = \
-	e-book-backend-google-factory.c	\
-	e-book-backend-google.h		\
-	e-book-backend-google.c
+	e-book-backend-google-factory.c \
+	e-book-backend-google.c \
+	e-book-backend-google.h \
+	$(GOA_SOURCES)
 
 libebookbackendgoogle_la_LIBADD = \
-	$(top_builddir)/addressbook/libebook/libebook-1.2.la		\
-	$(top_builddir)/addressbook/libedata-book/libedata-book-1.2.la	\
-	$(top_builddir)/libedataserver/libedataserver-1.2.la		\
-	$(top_builddir)/libebackend/libebackend-1.2.la			\
-	$(SOUP_LIBS)							\
-	$(EVOLUTION_ADDRESSBOOK_LIBS)					\
-	$(GDATA_LIBS)
+	$(top_builddir)/addressbook/libebook/libebook-1.2.la \
+	$(top_builddir)/addressbook/libedata-book/libedata-book-1.2.la \
+	$(top_builddir)/libedataserver/libedataserver-1.2.la \
+	$(top_builddir)/libebackend/libebackend-1.2.la \
+	$(EVOLUTION_ADDRESSBOOK_LIBS) \
+	$(SOUP_LIBS) \
+	$(GDATA_LIBS) \
+	$(GOA_LIBS) \
+	$(OAUTH_LIBS)
 
 libebookbackendgoogle_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/addressbook/backends/google/e-book-backend-google.c b/addressbook/backends/google/e-book-backend-google.c
index 428bb52..568acc9 100644
--- a/addressbook/backends/google/e-book-backend-google.c
+++ b/addressbook/backends/google/e-book-backend-google.c
@@ -37,6 +37,12 @@
 
 #include "e-book-backend-google.h"
 
+#ifdef HAVE_GOA
+#include "e-gdata-goa-authorizer.h"
+#endif
+
+#define CLIENT_ID "evolution-client-0.1.0"
+
 #define URI_GET_CONTACTS "://www.google.com/m8/feeds/contacts/default/full"
 
 #define EDB_ERROR(_code) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, NULL)
@@ -402,6 +408,32 @@ cache_needs_update (EBookBackend *backend, guint *remaining_secs)
 	return FALSE;
 }
 
+static gboolean
+backend_is_authorized (EBookBackend *backend)
+{
+	EBookBackendGooglePrivate *priv;
+
+	priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+	if (priv->service == NULL)
+		return FALSE;
+
+#ifdef HAVE_GOA
+	/* If we're using OAuth tokens, then as far as the backend
+	 * is concerned it's always authorized.  The GDataAuthorizer
+	 * will take care of everything in the background without
+	 * bothering clients with "auth-required" signals. */
+	if (E_IS_GDATA_GOA_AUTHORIZER (priv->authorizer))
+		return TRUE;
+#endif
+
+#ifdef HAVE_LIBGDATA_0_9
+	return gdata_service_is_authorized (priv->service);
+#else
+	return gdata_service_is_authenticated (priv->service);
+#endif
+}
+
 static void
 on_contact_added (EBookBackend *backend, EContact *contact)
 {
@@ -545,7 +577,9 @@ get_new_contacts_cb (GDataService *service, GAsyncResult *result, EBookBackend *
 		GList *entries = gdata_feed_get_entries (feed);
 		__debug__ ("Feed has %d entries", g_list_length (entries));
 	}
-	g_object_unref (feed);
+
+	if (feed != NULL)
+		g_object_unref (feed);
 
 	if (!gdata_error) {
 		/* Finish updating the cache */
@@ -573,11 +607,7 @@ get_new_contacts (EBookBackend *backend)
 	GCancellable *cancellable;
 
 	__debug__ (G_STRFUNC);
-	#ifdef HAVE_LIBGDATA_0_9
-	g_return_if_fail (priv->service && gdata_service_is_authorized (priv->service));
-	#else
-	g_return_if_fail (priv->service && gdata_service_is_authenticated (priv->service));
-	#endif
+	g_return_if_fail (backend_is_authorized (backend));
 
 	/* Sort out update times */
 	last_updated = cache_get_last_update (backend);
@@ -692,7 +722,9 @@ get_groups_cb (GDataService *service, GAsyncResult *result, EBookBackend *backen
 		GList *entries = gdata_feed_get_entries (feed);
 		__debug__ ("Group feed has %d entries", g_list_length (entries));
 	}
-	g_object_unref (feed);
+
+	if (feed != NULL)
+		g_object_unref (feed);
 
 	if (!gdata_error) {
 		/* Update the update time */
@@ -712,11 +744,7 @@ get_groups (EBookBackend *backend)
 	GCancellable *cancellable;
 
 	__debug__ (G_STRFUNC);
-	#ifdef HAVE_LIBGDATA_0_9
-	g_return_if_fail (priv->service && gdata_service_is_authorized (priv->service));
-	#else
-	g_return_if_fail (priv->service && gdata_service_is_authenticated (priv->service));
-	#endif
+	g_return_if_fail (backend_is_authorized (backend));
 
 	/* Build our query */
 	query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
@@ -799,11 +827,7 @@ cache_refresh_if_needed (EBookBackend *backend)
 
 	__debug__ (G_STRFUNC);
 
-	#ifdef HAVE_LIBGDATA_0_9
-	if (!priv->is_online || !priv->service || !gdata_service_is_authorized (priv->service)) {
-	#else
-	if (!priv->is_online || !priv->service || !gdata_service_is_authenticated (priv->service)) {
-	#endif
+	if (!priv->is_online || !backend_is_authorized (backend)) {
 		__debug__ ("We are not connected to Google%s.", (!priv->is_online) ? " (offline mode)" : "");
 		return TRUE;
 	}
@@ -846,6 +870,93 @@ cache_destroy (EBookBackend *backend)
 	priv->cache_type = NO_CACHE;
 }
 
+static void
+proxy_settings_changed (EProxy *proxy,
+                        EBookBackend *backend)
+{
+	EBookBackendGooglePrivate *priv;
+	SoupURI *proxy_uri = NULL;
+	gchar *uri;
+
+	priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+	if (!priv || !priv->service)
+		return;
+
+	/* Build the URI which libgdata would use to query contacts */
+	uri = g_strconcat (
+		priv->use_ssl ? "https" : "http",
+		URI_GET_CONTACTS, NULL);
+
+	/* use proxy if necessary */
+	if (e_proxy_require_proxy_for_uri (proxy, uri))
+		proxy_uri = e_proxy_peek_uri_for (proxy, uri);
+	gdata_service_set_proxy_uri (priv->service, proxy_uri);
+
+	g_free (uri);
+}
+
+static void
+request_authorization (EBookBackend *backend)
+{
+	EBookBackendGooglePrivate *priv;
+
+	priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+	/* Make sure we have the GDataService configured
+	 * before requesting authorization. */
+
+#ifdef HAVE_GOA
+	/* If this is associated with a GNOME Online Account,
+	 * use OAuth authentication instead of ClientLogin. */
+	if (priv->authorizer == NULL) {
+		EGDataGoaAuthorizer *authorizer;
+		GoaObject *goa_object;
+
+		goa_object = g_object_get_data (
+			G_OBJECT (backend), "GNOME Online Account");
+		if (GOA_IS_OBJECT (goa_object)) {
+			authorizer = e_gdata_goa_authorizer_new (goa_object);
+			priv->authorizer = GDATA_AUTHORIZER (authorizer);
+		}
+	}
+#endif
+
+#ifdef HAVE_LIBGDATA_0_9
+	if (priv->authorizer == NULL) {
+		GDataClientLoginAuthorizer *authorizer;
+
+		authorizer = gdata_client_login_authorizer_new (
+			CLIENT_ID, GDATA_TYPE_CONTACTS_SERVICE);
+		priv->authorizer = GDATA_AUTHORIZER (authorizer);
+	}
+#endif
+
+	if (priv->service == NULL) {
+		GDataContactsService *contacts_service;
+
+#ifdef HAVE_LIBGDATA_0_9
+		contacts_service =
+			gdata_contacts_service_new (priv->authorizer);
+#else
+		contacts_service = gdata_contacts_service_new (CLIENT_ID);
+#endif
+		priv->service = GDATA_SERVICE (contacts_service);
+		proxy_settings_changed (priv->proxy, backend);
+	}
+
+#ifdef HAVE_GOA
+	/* If we're using OAuth tokens, then as far as the backend
+	 * is concerned it's always authorized.  The GDataAuthorizer
+	 * will take care of everything in the background without
+	 * bothering clients with "auth-required" signals. */
+	if (E_IS_GDATA_GOA_AUTHORIZER (priv->authorizer))
+		return;
+#endif
+
+	e_book_backend_notify_auth_required (backend, TRUE, NULL);
+}
+
 typedef struct {
 	EBookBackend *backend;
 	EDataBook *book;
@@ -904,11 +1015,7 @@ e_book_backend_google_create_contact (EBookBackend *backend, EDataBook *book, gu
 		return;
 	}
 
-	#ifdef HAVE_LIBGDATA_0_9
-	g_return_if_fail (priv->service && gdata_service_is_authorized (priv->service));
-	#else
-	g_return_if_fail (priv->service && gdata_service_is_authenticated (priv->service));
-	#endif
+	g_return_if_fail (backend_is_authorized (backend));
 
 	/* Build the GDataEntry from the vCard */
 	contact = e_contact_new_from_vcard (vcard_str);
@@ -990,11 +1097,7 @@ e_book_backend_google_remove_contacts (EBookBackend *backend, EDataBook *book, g
 		return;
 	}
 
-	#ifdef HAVE_LIBGDATA_0_9
-	g_return_if_fail (priv->service && gdata_service_is_authorized (priv->service));
-	#else
-	g_return_if_fail (priv->service && gdata_service_is_authenticated (priv->service));
-	#endif
+	g_return_if_fail (backend_is_authorized (backend));
 
 	/* We make the assumption that the ID list we're passed is always exactly one element long, since we haven't specified "bulk-removes"
 	 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
@@ -1098,11 +1201,7 @@ e_book_backend_google_modify_contact (EBookBackend *backend, EDataBook *book, gu
 		return;
 	}
 
-	#ifdef HAVE_LIBGDATA_0_9
-	g_return_if_fail (priv->service && gdata_service_is_authorized (priv->service));
-	#else
-	g_return_if_fail (priv->service && gdata_service_is_authenticated (priv->service));
-	#endif
+	g_return_if_fail (backend_is_authorized (backend));
 
 	/* Get the new contact and its UID */
 	contact = e_contact_new_from_vcard (vcard_str);
@@ -1292,13 +1391,9 @@ e_book_backend_google_start_book_view (EBookBackend *backend, EDataBookView *boo
 
 	/* Update the cache if necessary */
 	if (cache_needs_update (backend, NULL)) {
-		#ifdef HAVE_LIBGDATA_0_9
-		if (!priv->service || !gdata_service_is_authorized (priv->service)) {
-		#else
-		if (!priv->service || !gdata_service_is_authenticated (priv->service)) {
-		#endif
+		if (!backend_is_authorized (backend)) {
 			/* We need authorization first */
-			e_book_backend_notify_auth_required (backend, TRUE, NULL);
+			request_authorization (backend);
 		} else {
 			/* Update in an idle function, so that this call doesn't block */
 			priv->idle_id = g_idle_add ((GSourceFunc) on_refresh_idle, backend);
@@ -1338,38 +1433,44 @@ e_book_backend_google_stop_book_view (EBookBackend *backend, EDataBookView *book
 		set_live_mode (backend, FALSE);
 }
 
+typedef struct {
+	EBookBackend *backend;
+	guint32 opid;
+} AuthenticateUserData;
+
+#ifdef HAVE_LIBGDATA_0_9
 static void
-proxy_settings_changed (EProxy *proxy, EBookBackend *backend)
+authenticate_client_login_cb (GDataClientLoginAuthorizer *authorizer,
+                              GAsyncResult *result,
+                              AuthenticateUserData *data)
 {
-	EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
-	SoupURI *proxy_uri = NULL;
-	gchar *uri;
-
-	if (!priv || !priv->service)
-		return;
+	GError *gdata_error = NULL;
+	GError *book_error = NULL;
 
-	/* Build the URI which libgdata would use to query contacts */
-	uri = g_strconcat (priv->use_ssl ? "https" : "http", URI_GET_CONTACTS, NULL);
+	__debug__ (G_STRFUNC);
 
-	/* use proxy if necessary */
-	if (e_proxy_require_proxy_for_uri (proxy, uri))
-		proxy_uri = e_proxy_peek_uri_for (proxy, uri);
-	gdata_service_set_proxy_uri (GDATA_SERVICE (priv->service), proxy_uri);
+	/* Finish authenticating */
+	gdata_client_login_authorizer_authenticate_finish (
+		authorizer, result, &gdata_error);
 
-	g_free (uri);
-}
+	if (gdata_error != NULL) {
+		data_book_error_from_gdata_error (&book_error, gdata_error);
+		__debug__ ("Authentication failed: %s", gdata_error->message);
+		g_error_free (gdata_error);
+	}
 
-typedef struct {
-	EBookBackend *backend;
-	guint32 opid;
-} AuthenticateUserData;
+	finish_operation (data->backend, data->opid);
+	e_book_backend_notify_readonly (data->backend, gdata_error != NULL);
+	e_book_backend_notify_opened (data->backend, book_error);
 
-static void
-#ifdef HAVE_LIBGDATA_0_9
-authenticate_user_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *result, AuthenticateUserData *data)
+	g_object_unref (data->backend);
+	g_slice_free (AuthenticateUserData, data);
+}
 #else
-authenticate_user_cb (GDataService *service, GAsyncResult *result, AuthenticateUserData *data)
-#endif
+static void
+authenticate_client_login_cb (GDataService *service,
+                              GAsyncResult *result,
+                              AuthenticateUserData *data)
 {
 	GError *gdata_error = NULL;
 	GError *book_error = NULL;
@@ -1377,23 +1478,25 @@ authenticate_user_cb (GDataService *service, GAsyncResult *result, AuthenticateU
 	__debug__ (G_STRFUNC);
 
 	/* Finish authenticating */
-	#ifdef HAVE_LIBGDATA_0_9
-	if (!gdata_client_login_authorizer_authenticate_finish (authorizer, result, &gdata_error)) {
-	#else
-	if (!gdata_service_authenticate_finish (service, result, &gdata_error)) {
-	#endif
+	gdata_service_authenticate_finish (service, result, &gdata_error);
+
+	if (gdata_error != NULL) {
 		data_book_error_from_gdata_error (&book_error, gdata_error);
 		__debug__ ("Authentication failed: %s", gdata_error->message);
 		g_error_free (gdata_error);
 	}
 
 	finish_operation (data->backend, data->opid);
-	e_book_backend_notify_readonly (data->backend, gdata_error ? TRUE : FALSE);
+	e_book_backend_notify_readonly (data->backend, gdata_error != NULL);
 	e_book_backend_notify_opened (data->backend, book_error);
 
 	g_object_unref (data->backend);
 	g_slice_free (AuthenticateUserData, data);
 }
+#endif
+
+#ifdef HAVE_LIBGDATA_0_9
+#endif
 
 static void
 e_book_backend_google_authenticate_user (EBookBackend *backend, GCancellable *cancellable, ECredentials *credentials)
@@ -1411,11 +1514,7 @@ e_book_backend_google_authenticate_user (EBookBackend *backend, GCancellable *ca
 		return;
 	}
 
-	#ifdef HAVE_LIBGDATA_0_9
-	if (priv->service && gdata_service_is_authorized (priv->service)) {
-	#else
-	if (priv->service && gdata_service_is_authenticated (priv->service)) {
-	#endif
+	if (backend_is_authorized (backend)) {
 		g_warning ("Connection to Google already established.");
 		e_book_backend_notify_readonly (backend, FALSE);
 		e_book_backend_notify_opened (backend, NULL);
@@ -1427,26 +1526,6 @@ e_book_backend_google_authenticate_user (EBookBackend *backend, GCancellable *ca
 		return;
 	}
 
-	#ifdef HAVE_LIBGDATA_0_9
-	/* Set up the service, authorizer and proxy */
-	if (!priv->service) {
-		priv->authorizer = GDATA_AUTHORIZER (gdata_client_login_authorizer_new ("evolution-client-0.1.0", GDATA_TYPE_CONTACTS_SERVICE));
-		priv->service = GDATA_SERVICE (gdata_contacts_service_new (priv->authorizer));
-	}
-	#else
-	/* Set up the service and proxy */
-	if (!priv->service)
-		priv->service = GDATA_SERVICE (gdata_contacts_service_new ("evolution-client-0.1.0"));
-	#endif
-
-	if (!priv->proxy) {
-		priv->proxy = e_proxy_new ();
-		e_proxy_setup_proxy (priv->proxy);
-
-		proxy_settings_changed (priv->proxy, backend);
-		g_signal_connect (priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), backend);
-	}
-
 	opid = -1;
 	while (g_hash_table_lookup (priv->cancellables, GUINT_TO_POINTER (opid)))
 		opid--;
@@ -1456,15 +1535,28 @@ e_book_backend_google_authenticate_user (EBookBackend *backend, GCancellable *ca
 	data->backend = g_object_ref (backend);
 	data->opid = opid;
 
-	cancellable = start_operation (backend, opid, cancellable, _("Authenticating with the serverâ"));
-	#ifdef HAVE_LIBGDATA_0_9
-	gdata_client_login_authorizer_authenticate_async (GDATA_CLIENT_LOGIN_AUTHORIZER (priv->authorizer),
-	                                                  e_credentials_peek (credentials, E_CREDENTIALS_KEY_USERNAME),
-	                                                  e_credentials_peek (credentials, E_CREDENTIALS_KEY_PASSWORD),
-	                                                  cancellable, (GAsyncReadyCallback) authenticate_user_cb, data);
-	#else
-	gdata_service_authenticate_async (priv->service, e_credentials_peek (credentials, E_CREDENTIALS_KEY_USERNAME), e_credentials_peek (credentials, E_CREDENTIALS_KEY_PASSWORD), cancellable, (GAsyncReadyCallback) authenticate_user_cb, data);
-	#endif
+	cancellable = start_operation (
+		backend, opid, cancellable,
+		_("Authenticating with the serverâ"));
+
+#ifdef HAVE_LIBGDATA_0_9
+	gdata_client_login_authorizer_authenticate_async (
+		GDATA_CLIENT_LOGIN_AUTHORIZER (priv->authorizer),
+		e_credentials_peek (credentials, E_CREDENTIALS_KEY_USERNAME),
+		e_credentials_peek (credentials, E_CREDENTIALS_KEY_PASSWORD),
+		cancellable,
+		(GAsyncReadyCallback) authenticate_client_login_cb,
+		data);
+#else
+	gdata_service_authenticate_async (
+		priv->service,
+		e_credentials_peek (credentials, E_CREDENTIALS_KEY_USERNAME),
+		e_credentials_peek (credentials, E_CREDENTIALS_KEY_PASSWORD),
+		cancellable,
+		(GAsyncReadyCallback) authenticate_client_login_cb,
+		data);
+#endif
+
 	g_object_unref (cancellable);
 }
 
@@ -1529,13 +1621,18 @@ e_book_backend_google_open (EBookBackend *backend, EDataBook *book, guint opid,
 	e_book_backend_notify_readonly (backend, TRUE);
 
 	if (priv->is_online) {
-		/* We're going online, so we need to authenticate and create the service and proxy.
-		 * This is done in e_book_backend_google_authenticate_user() when it gets the authentication data. */
-		e_book_backend_notify_auth_required (backend, TRUE, NULL);
-	} else {
-		e_book_backend_notify_opened (backend, NULL /* Success */);
+		request_authorization (backend);
+
+#ifdef HAVE_LIBGDATA_0_9
+		/* Refresh the authorizer.  This may block. */
+		gdata_authorizer_refresh_authorization (
+			priv->authorizer, cancellable, NULL);
+#endif
 	}
 
+	if (!priv->is_online || backend_is_authorized (backend))
+		e_book_backend_notify_opened (backend, NULL /* Success */);
+
 	e_data_book_respond_open (book, opid, NULL /* Success */);
 }
 
@@ -1714,9 +1811,7 @@ e_book_backend_google_set_online (EBookBackend *backend, gboolean is_online)
 	e_book_backend_notify_online (backend, is_online);
 
 	if (is_online && e_book_backend_is_opened (backend)) {
-		/* Going online, so we need to re-authenticate and re-create the service and proxy.
-		 * This is done in e_book_backend_google_authenticate_user() when it gets the authentication data. */
-		e_book_backend_notify_auth_required (backend, TRUE, NULL);
+		request_authorization (backend);
 	} else {
 		/* Going offline, so cancel all running operations */
 		google_cancel_all_operations (backend);
@@ -1725,20 +1820,10 @@ e_book_backend_google_set_online (EBookBackend *backend, gboolean is_online)
 		 * e_book_backend_google_authenticate_user() will mark us as writeable again once the user's authenticated again. */
 		e_book_backend_notify_readonly (backend, TRUE);
 
-		/* We can free our service and proxy */
+		/* We can free our service. */
 		if (priv->service)
 			g_object_unref (priv->service);
 		priv->service = NULL;
-
-		#ifdef HAVE_LIBGDATA_0_9
-		if (priv->authorizer != NULL)
-			g_object_unref (priv->authorizer);
-		priv->authorizer = NULL;
-		#endif
-
-		if (priv->proxy)
-			g_object_unref (priv->proxy);
-		priv->proxy = NULL;
 	}
 }
 
@@ -1766,11 +1851,11 @@ e_book_backend_google_dispose (GObject *object)
 		g_object_unref (priv->service);
 	priv->service = NULL;
 
-	#ifdef HAVE_LIBGDATA_0_9
+#ifdef HAVE_LIBGDATA_0_9
 	if (priv->authorizer != NULL)
 		g_object_unref (priv->authorizer);
 	priv->authorizer = NULL;
-	#endif
+#endif
 
 	if (priv->proxy)
 		g_object_unref (priv->proxy);
@@ -1833,6 +1918,14 @@ e_book_backend_google_init (EBookBackendGoogle *backend)
 	backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (
 		backend, E_TYPE_BOOK_BACKEND_GOOGLE,
 		EBookBackendGooglePrivate);
+
+	/* Set up our EProxy. */
+	backend->priv->proxy = e_proxy_new ();
+	e_proxy_setup_proxy (backend->priv->proxy);
+
+	g_signal_connect (
+		backend->priv->proxy, "changed",
+		G_CALLBACK (proxy_settings_changed), backend);
 }
 
 EBookBackend *
diff --git a/addressbook/backends/google/e-gdata-goa-authorizer.c b/addressbook/backends/google/e-gdata-goa-authorizer.c
new file mode 100644
index 0000000..b43df79
--- /dev/null
+++ b/addressbook/backends/google/e-gdata-goa-authorizer.c
@@ -0,0 +1,524 @@
+/*
+ * e-gdata-goa-authorizer.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-gdata-goa-authorizer.h"
+
+#include <string.h>
+#include <oauth.h>
+
+#define E_GDATA_GOA_AUTHORIZER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_GDATA_GOA_AUTHORIZER, EGDataGoaAuthorizerPrivate))
+
+struct _EGDataGoaAuthorizerPrivate {
+
+	/* GDataAuthorizer methods must be thread-safe. */
+	GMutex *mutex;
+
+	/* GoaObject is already thread-safe. */
+	GoaObject *goa_object;
+
+	/* These members are protected by the GMutex. */
+	gchar *access_token;
+	gchar *access_token_secret;
+	GHashTable *authorization_domains;
+};
+
+enum {
+	PROP_0,
+	PROP_GOA_OBJECT
+};
+
+/* Forward Declarations */
+static void	e_gdata_goa_authorizer_interface_init
+					(GDataAuthorizerInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	EGDataGoaAuthorizer,
+	e_gdata_goa_authorizer,
+	G_TYPE_OBJECT,
+	G_IMPLEMENT_INTERFACE (
+		GDATA_TYPE_AUTHORIZER,
+		e_gdata_goa_authorizer_interface_init))
+
+static GHashTable *
+gdata_goa_authorizer_get_parameters (SoupMessage *message,
+                                     const gchar *consumer_key,
+                                     const gchar *consumer_secret,
+                                     const gchar *access_token,
+                                     const gchar *access_token_secret)
+{
+	GString *query;
+	GString *base_string;
+	GString *signing_key;
+	GHashTable *parameters;
+	GHashTable *hash_table;
+	GHashTableIter iter;
+	SoupURI *soup_uri;
+	GList *keys, *link;
+	gchar *string;
+	gchar *request_uri;
+	gpointer key, value;
+
+	parameters = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) NULL,
+		(GDestroyNotify) g_free);
+
+	/* soup_form_decode() uses an awkward allocation style for
+	 * its hash table entries, so it's easier to copy its content
+	 * into our own hash table than try to use the returned hash
+	 * table directly. */
+
+	soup_uri = soup_message_get_uri (message);
+	hash_table = soup_form_decode (soup_uri->query);
+	g_hash_table_iter_init (&iter, hash_table);
+	while (g_hash_table_iter_next (&iter, &key, &value)) {
+		key = (gpointer) g_intern_string (key);
+		g_hash_table_insert (parameters, key, g_strdup (value));
+	}
+	g_hash_table_destroy (hash_table);
+
+	/* Add OAuth parameters. */
+
+	key = (gpointer) "oauth_version";
+	g_hash_table_insert (parameters, key, g_strdup ("1.0"));
+
+	string = oauth_gen_nonce ();
+	key = (gpointer) "oauth_nonce";
+	g_hash_table_insert (parameters, key, g_strdup (string));
+	free (string);  /* do not use g_free() */
+
+	key = (gpointer) "oauth_timestamp";
+	string = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) time (NULL));
+	g_hash_table_insert (parameters, key, string); /* takes ownership */
+
+	key = (gpointer) "oauth_consumer_key";
+	g_hash_table_insert (parameters, key, g_strdup (consumer_key));
+
+	key = (gpointer) "oauth_token";
+	g_hash_table_insert (parameters, key, g_strdup (access_token));
+
+	key = (gpointer) "oauth_signature_method";
+	g_hash_table_insert (parameters, key, g_strdup ("HMAC-SHA1"));
+
+	/* Build the query part of the signature base string. */
+
+	query = g_string_sized_new (512);
+	keys = g_hash_table_get_keys (parameters);
+	keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
+	for (link = keys; link != NULL; link = g_list_next (link)) {
+		const gchar *key = link->data;
+		const gchar *val;
+
+		val = g_hash_table_lookup (parameters, key);
+
+		if (link != keys)
+			g_string_append_c (query, '&');
+
+		g_string_append_uri_escaped (query, key, NULL, FALSE);
+		g_string_append_c (query, '=');
+		g_string_append_uri_escaped (query, val, NULL, FALSE);
+	}
+	g_list_free (keys);
+
+	/* Build the signature base string. */
+
+	soup_uri = soup_uri_copy (soup_uri);
+	soup_uri_set_query (soup_uri, NULL);
+	soup_uri_set_fragment (soup_uri, NULL);
+	request_uri = soup_uri_to_string (soup_uri, FALSE);
+	soup_uri_free (soup_uri);
+
+	base_string = g_string_sized_new (512);
+	g_string_append_uri_escaped (base_string, message->method, NULL, FALSE);
+	g_string_append_c (base_string, '&');
+	g_string_append_uri_escaped (base_string, request_uri, NULL, FALSE);
+	g_string_append_c (base_string, '&');
+	g_string_append_uri_escaped (base_string, query->str, NULL, FALSE);
+
+	/* Build the HMAC-SHA1 signing key. */
+
+	signing_key = g_string_sized_new (512);
+	g_string_append_uri_escaped (
+		signing_key, consumer_secret, NULL, FALSE);
+	g_string_append_c (signing_key, '&');
+	g_string_append_uri_escaped (
+		signing_key, access_token_secret, NULL, FALSE);
+
+	/* Sign the request. */
+
+	key = (gpointer) "oauth_signature";
+	string = oauth_sign_hmac_sha1 (base_string->str, signing_key->str);
+	g_hash_table_insert (parameters, key, g_strdup (string));
+	free (string);  /* do not use g_free() */
+
+	g_free (request_uri);
+
+	g_string_free (query, TRUE);
+	g_string_free (base_string, TRUE);
+	g_string_free (signing_key, TRUE);
+
+	return parameters;
+}
+
+static void
+gdata_goa_authorizer_add_authorization (GDataAuthorizer *authorizer,
+                                        SoupMessage *message)
+{
+	EGDataGoaAuthorizerPrivate *priv;
+	GoaOAuthBased *goa_oauth_based;
+	GHashTable *parameters;
+	GString *authorization;
+	const gchar *consumer_key;
+	const gchar *consumer_secret;
+	gint ii;
+
+	const gchar *oauth_keys[] = {
+		"oauth_version",
+		"oauth_nonce",
+		"oauth_timestamp",
+		"oauth_consumer_key",
+		"oauth_token",
+		"oauth_signature_method",
+		"oauth_signature"
+	};
+
+	/* This MUST be called with the mutex already locked. */
+
+	priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
+
+	/* We can't add an Authorization header without an access token.
+	 * Let the request fail.  GData should refresh us if it gets back
+	 * a "401 Authorization required" response from Google, and then
+	 * automatically retry the request. */
+	if (priv->access_token == NULL)
+		return;
+
+	goa_oauth_based = goa_object_get_oauth_based (priv->goa_object);
+
+	consumer_key = goa_oauth_based_get_consumer_key (goa_oauth_based);
+	consumer_secret = goa_oauth_based_get_consumer_secret (goa_oauth_based);
+
+	parameters = gdata_goa_authorizer_get_parameters (
+		message,
+		consumer_key,
+		consumer_secret,
+		priv->access_token,
+		priv->access_token_secret);
+
+	authorization = g_string_new ("OAuth ");
+
+	for (ii = 0; ii < G_N_ELEMENTS (oauth_keys); ii++) {
+		const gchar *key;
+		const gchar *val;
+
+		key = oauth_keys[ii];
+		val = g_hash_table_lookup (parameters, key);
+
+		if (ii > 0)
+			g_string_append (authorization, ", ");
+
+		g_string_append (authorization, key);
+		g_string_append_c (authorization, '=');
+		g_string_append_c (authorization, '"');
+		g_string_append_uri_escaped (authorization, val, NULL, FALSE);
+		g_string_append_c (authorization, '"');
+	}
+
+	/* Use replace here, not append, to make sure
+	 * there's only one "Authorization" header. */
+	soup_message_headers_replace (
+		message->request_headers,
+		"Authorization", authorization->str);
+
+	g_string_free (authorization, TRUE);
+	g_hash_table_destroy (parameters);
+
+	g_object_unref (goa_oauth_based);
+}
+
+static gboolean
+gdata_goa_authorizer_is_authorized (GDataAuthorizer *authorizer,
+                                    GDataAuthorizationDomain *domain)
+{
+	EGDataGoaAuthorizerPrivate *priv;
+
+	/* This MUST be called with the mutex already locked. */
+
+	if (domain == NULL)
+		return TRUE;
+
+	priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
+	domain = g_hash_table_lookup (priv->authorization_domains, domain);
+
+	return (domain != NULL);
+}
+
+static void
+gdata_goa_authorizer_set_goa_object (EGDataGoaAuthorizer *authorizer,
+                                     GoaObject *goa_object)
+{
+	g_return_if_fail (GOA_IS_OBJECT (goa_object));
+	g_return_if_fail (authorizer->priv->goa_object == NULL);
+
+	authorizer->priv->goa_object = g_object_ref (goa_object);
+}
+
+static void
+gdata_goa_authorizer_set_property (GObject *object,
+                                   guint property_id,
+                                   const GValue *value,
+                                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_GOA_OBJECT:
+			gdata_goa_authorizer_set_goa_object (
+				E_GDATA_GOA_AUTHORIZER (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+gdata_goa_authorizer_get_property (GObject *object,
+                                   guint property_id,
+                                   GValue *value,
+                                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_GOA_OBJECT:
+			g_value_set_object (
+				value,
+				e_gdata_goa_authorizer_get_goa_object (
+				E_GDATA_GOA_AUTHORIZER (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+gdata_goa_authorizer_dispose (GObject *object)
+{
+	EGDataGoaAuthorizerPrivate *priv;
+
+	priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (object);
+
+	if (priv->goa_object != NULL) {
+		g_object_unref (priv->goa_object);
+		priv->goa_object = NULL;
+	}
+
+	g_hash_table_remove_all (priv->authorization_domains);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_gdata_goa_authorizer_parent_class)->dispose (object);
+}
+
+static void
+gdata_goa_authorizer_finalize (GObject *object)
+{
+	EGDataGoaAuthorizerPrivate *priv;
+
+	priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (object);
+
+	g_mutex_free (priv->mutex);
+	g_free (priv->access_token);
+	g_free (priv->access_token_secret);
+	g_hash_table_destroy (priv->authorization_domains);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_gdata_goa_authorizer_parent_class)->finalize (object);
+}
+
+static void
+gdata_goa_authorizer_constructed (GObject *object)
+{
+	EGDataGoaAuthorizerPrivate *priv;
+	GType service_type;
+	GList *domains;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_gdata_goa_authorizer_parent_class)->
+		constructed (object);
+
+	priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (object);
+
+	/* XXX We would need to generalize this to make the class
+	 *     reusable for other service types, probably by adding
+	 *     a "service-type" constructor property. */
+	service_type = GDATA_TYPE_CONTACTS_SERVICE;
+	domains = gdata_service_get_authorization_domains (service_type);
+
+	while (domains != NULL) {
+		g_hash_table_insert (
+			priv->authorization_domains,
+			g_object_ref (domains->data),
+			domains->data);
+		domains = g_list_delete_link (domains, domains);
+	}
+}
+
+static void
+gdata_goa_authorizer_process_request (GDataAuthorizer *authorizer,
+                                      GDataAuthorizationDomain *domain,
+                                      SoupMessage *message)
+{
+	EGDataGoaAuthorizerPrivate *priv;
+
+	priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
+
+	g_mutex_lock (priv->mutex);
+
+	if (gdata_goa_authorizer_is_authorized (authorizer, domain))
+		gdata_goa_authorizer_add_authorization (authorizer, message);
+
+	g_mutex_unlock (priv->mutex);
+}
+
+static gboolean
+gdata_goa_authorizer_is_authorized_for_domain (GDataAuthorizer *authorizer,
+                                               GDataAuthorizationDomain *domain)
+{
+	EGDataGoaAuthorizerPrivate *priv;
+	gboolean authorized;
+
+	priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
+
+	g_mutex_lock (priv->mutex);
+
+	authorized = gdata_goa_authorizer_is_authorized (authorizer, domain);
+
+	g_mutex_unlock (priv->mutex);
+
+	return authorized;
+}
+
+static gboolean
+gdata_goa_authorizer_refresh_authorization (GDataAuthorizer *authorizer,
+                                            GCancellable *cancellable,
+                                            GError **error)
+{
+	EGDataGoaAuthorizerPrivate *priv;
+	GoaOAuthBased *goa_oauth_based;
+	GoaAccount *goa_account;
+	gboolean success = TRUE;
+
+	priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
+
+	g_mutex_lock (priv->mutex);
+
+	g_free (priv->access_token);
+	priv->access_token = NULL;
+
+	g_free (priv->access_token_secret);
+	priv->access_token_secret = NULL;
+
+	goa_account = goa_object_get_account (priv->goa_object);
+	goa_oauth_based = goa_object_get_oauth_based (priv->goa_object);
+
+	success &= goa_account_call_ensure_credentials_sync (
+		goa_account, NULL, cancellable, error);
+
+	success &= goa_oauth_based_call_get_access_token_sync (
+		goa_oauth_based, &priv->access_token,
+		&priv->access_token_secret, NULL, cancellable, error);
+
+	g_object_unref (goa_account);
+	g_object_unref (goa_oauth_based);
+
+	g_mutex_unlock (priv->mutex);
+
+	return success;
+}
+
+static void
+e_gdata_goa_authorizer_class_init (EGDataGoaAuthorizerClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EGDataGoaAuthorizerPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = gdata_goa_authorizer_set_property;
+	object_class->get_property = gdata_goa_authorizer_get_property;
+	object_class->dispose = gdata_goa_authorizer_dispose;
+	object_class->finalize = gdata_goa_authorizer_finalize;
+	object_class->constructed = gdata_goa_authorizer_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_GOA_OBJECT,
+		g_param_spec_object (
+			"goa-object",
+			"GoaObject",
+			"The GOA account to authenticate",
+			GOA_TYPE_OBJECT,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_gdata_goa_authorizer_interface_init (GDataAuthorizerInterface *interface)
+{
+	interface->process_request =
+		gdata_goa_authorizer_process_request;
+	interface->is_authorized_for_domain =
+		gdata_goa_authorizer_is_authorized_for_domain;
+	interface->refresh_authorization =
+		gdata_goa_authorizer_refresh_authorization;
+}
+
+static void
+e_gdata_goa_authorizer_init (EGDataGoaAuthorizer *authorizer)
+{
+	GHashTable *authorization_domains;
+
+	authorization_domains = g_hash_table_new_full (
+		(GHashFunc) g_direct_hash,
+		(GEqualFunc) g_direct_equal,
+		(GDestroyNotify) g_object_unref,
+		(GDestroyNotify) NULL);
+
+	authorizer->priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
+	authorizer->priv->mutex = g_mutex_new ();
+	authorizer->priv->authorization_domains = authorization_domains;
+}
+
+EGDataGoaAuthorizer *
+e_gdata_goa_authorizer_new (GoaObject *goa_object)
+{
+	g_return_val_if_fail (GOA_IS_OBJECT (goa_object), NULL);
+
+	return g_object_new (
+		E_TYPE_GDATA_GOA_AUTHORIZER,
+		"goa-object", goa_object, NULL);
+}
+
+GoaObject *
+e_gdata_goa_authorizer_get_goa_object (EGDataGoaAuthorizer *authorizer)
+{
+	g_return_val_if_fail (E_IS_GDATA_GOA_AUTHORIZER (authorizer), NULL);
+
+	return authorizer->priv->goa_object;
+}
diff --git a/addressbook/backends/google/e-gdata-goa-authorizer.h b/addressbook/backends/google/e-gdata-goa-authorizer.h
new file mode 100644
index 0000000..fb50a1f
--- /dev/null
+++ b/addressbook/backends/google/e-gdata-goa-authorizer.h
@@ -0,0 +1,68 @@
+/*
+ * e-gdata-goa-authorizer.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_GDATA_GOA_AUTHORIZER_H
+#define E_GDATA_GOA_AUTHORIZER_H
+
+#include <gdata/gdata.h>
+
+#define GOA_API_IS_SUBJECT_TO_CHANGE
+#include <goa/goa.h>
+
+/* Standard GObject macros */
+#define E_TYPE_GDATA_GOA_AUTHORIZER \
+	(e_gdata_goa_authorizer_get_type ())
+#define E_GDATA_GOA_AUTHORIZER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_GDATA_GOA_AUTHORIZER, EGDataGoaAuthorizer))
+#define E_GDATA_GOA_AUTHORIZER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_GDATA_GOA_AUTHORIZER, EGDataGoaAuthorizerClass))
+#define E_IS_GDATA_GOA_AUTHORIZER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_GDATA_GOA_AUTHORIZER))
+#define E_IS_GDATA_GOA_AUTHORIZER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_GDATA_GOA_AUTHORIZER))
+#define E_GDATA_GOA_AUTHORIZER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_GDATA_GOA_AUTHORIZER, EGDataGoaAuthorizerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EGDataGoaAuthorizer EGDataGoaAuthorizer;
+typedef struct _EGDataGoaAuthorizerClass EGDataGoaAuthorizerClass;
+typedef struct _EGDataGoaAuthorizerPrivate EGDataGoaAuthorizerPrivate;
+
+struct _EGDataGoaAuthorizer {
+	GObject parent;
+	EGDataGoaAuthorizerPrivate *priv;
+};
+
+struct _EGDataGoaAuthorizerClass {
+	GObjectClass parent_class;
+};
+
+GType		e_gdata_goa_authorizer_get_type (void);
+EGDataGoaAuthorizer *
+		e_gdata_goa_authorizer_new
+					(GoaObject *goa_object);
+GoaObject *	e_gdata_goa_authorizer_get_goa_object
+					(EGDataGoaAuthorizer *authorizer);
+
+#endif /* E_GDATA_GOA_AUTHORIZER_H */
diff --git a/addressbook/libedata-book/Makefile.am b/addressbook/libedata-book/Makefile.am
index f25c1d6..bd04300 100644
--- a/addressbook/libedata-book/Makefile.am
+++ b/addressbook/libedata-book/Makefile.am
@@ -86,7 +86,8 @@ e_addressbook_factory_CPPFLAGS = \
 	-I$(top_srcdir)/addressbook/libegdbus \
 	-I$(top_builddir) \
 	-I$(top_builddir)/addressbook \
-	$(EVOLUTION_ADDRESSBOOK_CFLAGS)
+	$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
+	$(GOA_CFLAGS)
 
 e_addressbook_factory_SOURCES = \
 	e-data-book-factory.c \
@@ -97,7 +98,8 @@ e_addressbook_factory_LDADD = \
 	$(top_builddir)/addressbook/libedata-book/libedata-book-1.2.la \
 	$(top_builddir)/libedataserver/libedataserver-1.2.la \
 	$(top_builddir)/libebackend/libebackend-1.2.la \
-	$(EVOLUTION_ADDRESSBOOK_LIBS)
+	$(EVOLUTION_ADDRESSBOOK_LIBS) \
+	$(GOA_LIBS)
 
 %-$(API_VERSION).pc: %.pc
 	 cp $< $@
diff --git a/addressbook/libedata-book/e-data-book-factory.c b/addressbook/libedata-book/e-data-book-factory.c
index d395f0b..133652a 100644
--- a/addressbook/libedata-book/e-data-book-factory.c
+++ b/addressbook/libedata-book/e-data-book-factory.c
@@ -32,6 +32,15 @@
 #endif
 #endif
 
+#if HAVE_GOA
+#define GOA_API_IS_SUBJECT_TO_CHANGE
+#include <goa/goa.h>
+
+/* This is the property name or URL parameter under which we
+ * embed the GoaAccount ID into an EAccount or ESource object. */
+#define GOA_KEY "goa-account-id"
+#endif
+
 #include <libebackend/e-data-server-module.h>
 #include <libebackend/e-offline-listener.h>
 #include "e-book-backend-factory.h"
@@ -98,6 +107,11 @@ struct _EDataBookFactoryPrivate {
 
 	/* whether should be online */
 	gboolean is_online;
+
+#ifdef HAVE_GOA
+	GoaClient *goa_client;
+	GHashTable *goa_accounts;
+#endif
 };
 
 /* Forward Declarations */
@@ -425,6 +439,27 @@ impl_BookFactory_get_book (EGdbusBookFactory *object,
 		priv->exit_timeout = 0;
 	}
 
+#ifdef HAVE_GOA
+	{
+		GoaObject *goa_object = NULL;
+		const gchar *goa_account_id;
+
+		/* Embed the corresponding GoaObject in the EBookBackend
+		 * so the backend can retrieve it.  We're not ready to add
+		 * formal API for this to EBookBackend just yet. */
+		goa_account_id = e_source_get_property (source, GOA_KEY);
+		if (goa_account_id != NULL)
+			goa_object = g_hash_table_lookup (
+				factory->priv->goa_accounts, goa_account_id);
+		if (GOA_IS_OBJECT (goa_object))
+			g_object_set_data_full (
+				G_OBJECT (backend),
+				"GNOME Online Account",
+				g_object_ref (goa_object),
+				(GDestroyNotify) g_object_unref);
+	}
+#endif
+
 	path = construct_book_factory_path ();
 	book = e_data_book_new (backend, source);
 	g_hash_table_insert (priv->books, g_strdup (path), book);
@@ -485,6 +520,15 @@ e_data_book_factory_dispose (GObject *object)
 		priv->gdbus_object = NULL;
 	}
 
+#ifdef HAVE_GOA
+	if (priv->goa_client != NULL) {
+		g_object_unref (priv->goa_client);
+		priv->goa_client = NULL;
+	}
+
+	g_hash_table_remove_all (priv->goa_accounts);
+#endif
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (e_data_book_factory_parent_class)->dispose (object);
 }
@@ -505,6 +549,10 @@ e_data_book_factory_finalize (GObject *object)
 	g_mutex_free (priv->books_lock);
 	g_mutex_free (priv->connections_lock);
 
+#ifdef HAVE_GOA
+	g_hash_table_destroy (priv->goa_accounts);
+#endif
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (e_data_book_factory_parent_class)->finalize (object);
 }
@@ -560,6 +608,44 @@ e_data_book_factory_init (EDataBookFactory *factory)
 		g_error ("%s", error->message);
 
 	e_data_book_factory_register_backends (factory);
+
+#ifdef HAVE_GOA
+	factory->priv->goa_accounts = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_object_unref);
+
+	/* Failure here is non-fatal, just emit a terminal warning. */
+	factory->priv->goa_client = goa_client_new_sync (NULL, &error);
+
+	if (factory->priv->goa_client != NULL) {
+		GList *list, *iter;
+
+		list = goa_client_get_accounts (factory->priv->goa_client);
+
+		for (iter = list; iter != NULL; iter = g_list_next (iter)) {
+			GoaObject *goa_object;
+			GoaAccount *goa_account;
+			const gchar *goa_account_id;
+
+			goa_object = GOA_OBJECT (iter->data);
+			goa_account = goa_object_peek_account (goa_object);
+			goa_account_id = goa_account_get_id (goa_account);
+
+			/* Takes ownership of the GoaObject. */
+			g_hash_table_insert (
+				factory->priv->goa_accounts,
+				g_strdup (goa_account_id), goa_object);
+		}
+
+		g_list_free (list);
+
+	} else if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_error_free (error);
+	}
+#endif
 }
 
 static guint
diff --git a/configure.ac b/configure.ac
index c4e3e7f..ba30c7f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -35,10 +35,12 @@ dnl Required Package Versions
 m4_define([glib_minimum_version], [2.28])
 m4_define([gtk_minimum_version], [3.0])
 m4_define([gconf_minimum_version], [2.0.0])		dnl XXX Just a Guess
+m4_define([gnome_keyring_minimum_version], [2.20.1])
+m4_define([goa_minimum_version], [3.1.1])
 m4_define([libxml_minimum_version], [2.0.0])		dnl XXX Just a Guess
 m4_define([libsoup_minimum_version], [2.31.2])
 m4_define([libgdata_minimum_version], [0.7.0])
-m4_define([gnome_keyring_minimum_version], [2.20.1])
+m4_define([oauth_minimum_version], [0.9.4])
 m4_define([sqlite_minimum_version], [3.5])
 m4_define([libical_minimum_version], [0.43])
 m4_define([gweather_minimum_version], [2.90.0])
@@ -342,6 +344,37 @@ if `$PKG_CONFIG --atleast-version=0.9 libgdata`; then
 	AC_DEFINE(HAVE_LIBGDATA_0_9,1,[libgdata is 0.9 or higher])
 fi
 
+dnl *******************************
+dnl Check for GNOME Online Accounts
+dnl *******************************
+AC_ARG_ENABLE([goa],
+	[AS_HELP_STRING([--enable-goa],
+	[enable GNOME Online Accounts support (default=yes)])],
+	[enable_goa=$enableval], [enable_goa=yes])
+AC_MSG_CHECKING([if GNOME Online Accounts support is enabled])
+AC_MSG_RESULT([$enable_goa])
+if test "x$enable_goa" = xyes; then
+	PKG_CHECK_MODULES([GOA], [goa-1.0 >= goa_minimum_version],,
+	[AC_MSG_ERROR([goa-1.0 not found (or version < goa_minimum_version),
+	If you want to disable GNOME Online Accounts support,
+	please append --disable-goa to configure.])])
+
+	PKG_CHECK_MODULES([OAUTH], [oauth >= oauth_minimum_version],,
+	[AC_MSG_ERROR([oauth not found (or version < oauth_minimum_version),
+	If you want to disable GNOME Online Accounts support,
+	please append --disable-goa to configure.])])
+
+	if `$PKG_CONFIG --atleast-version=0.9 libgdata`; then
+		AC_DEFINE(HAVE_GOA,1,[Have goa-1.0 and libgdata >= 0.9])
+	else
+		AC_MSG_ERROR([
+	libgdata 0.9 is required for GNOME Online Accounts support.
+	If you want to disable GNOME Online Accounts support, please
+	append --disable-goa to configure.])
+	fi
+fi
+AM_CONDITIONAL(HAVE_GOA, [test x$enable_goa = xyes])
+
 if test x$os_win32 = xno; then
   dnl ***********************************
   dnl Check for GNOME Keyring.
@@ -1567,4 +1600,5 @@ echo "
 	Gtk Doc:		$enable_gtk_doc
 	Introspection:		$enable_introspection
 	Vala bindings:		$enable_vala_bindings
+	GNOME Online Accounts	$enable_goa
 "



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