aboutsummaryrefslogtreecommitdiffstats
path: root/modules/online-accounts/evolution-online-accounts.c
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2011-06-10 04:28:36 +0800
committerMatthew Barnes <mbarnes@redhat.com>2011-06-25 07:05:15 +0800
commitdd6e64ad305e1ab181c6276ed4c77d910a36a4fa (patch)
treec04828633069b8c4e7531733d4960f58def4d762 /modules/online-accounts/evolution-online-accounts.c
parentf070b6032f46dddb64220ee3ec26d79fa1bbe8bb (diff)
downloadgsoc2013-evolution-dd6e64ad305e1ab181c6276ed4c77d910a36a4fa.tar.gz
gsoc2013-evolution-dd6e64ad305e1ab181c6276ed4c77d910a36a4fa.tar.zst
gsoc2013-evolution-dd6e64ad305e1ab181c6276ed4c77d910a36a4fa.zip
Prototype an online-accounts module.
Integrates with the GNOME Online Accounts service. Creates Evolution sources for a GOA Google account and keeps them synchronized. Also registers a new CamelSaslXOAuth class for use with GMail. Authentication of Google Calendars and Google Contacts using OAuth is still under development.
Diffstat (limited to 'modules/online-accounts/evolution-online-accounts.c')
-rw-r--r--modules/online-accounts/evolution-online-accounts.c520
1 files changed, 520 insertions, 0 deletions
diff --git a/modules/online-accounts/evolution-online-accounts.c b/modules/online-accounts/evolution-online-accounts.c
new file mode 100644
index 0000000000..d127f4d096
--- /dev/null
+++ b/modules/online-accounts/evolution-online-accounts.c
@@ -0,0 +1,520 @@
+/*
+ * evolution-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/>
+ *
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+/* XXX Just use the deprecated APIs for now.
+ * We'll be switching away soon enough. */
+#undef E_CAL_DISABLE_DEPRECATED
+#undef E_BOOK_DISABLE_DEPRECATED
+
+#include <libecal/e-cal.h>
+#include <libebook/e-book.h>
+#include <libedataserver/e-uid.h>
+#include <libedataserver/e-account-list.h>
+
+#include <shell/e-shell.h>
+#include <e-util/e-account-utils.h>
+
+#include "camel-sasl-xoauth.h"
+#include "e-online-accounts-google.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))
+
+/* 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"
+
+typedef struct _EOnlineAccounts EOnlineAccounts;
+typedef struct _EOnlineAccountsClass EOnlineAccountsClass;
+
+typedef struct _AccountNode AccountNode;
+
+struct _EOnlineAccounts {
+ EExtension parent;
+
+ /* GoaAccount ID -> EAccount/ESource ID */
+ GHashTable *accounts;
+
+ GoaClient *goa_client;
+ EActivity *connecting;
+};
+
+struct _EOnlineAccountsClass {
+ EExtensionClass parent_class;
+};
+
+struct _AccountNode {
+ gchar *goa_id; /* GoaAccount ID */
+ gchar *evo_id; /* EAccount/ESource ID */
+};
+
+/* 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 EShell *
+online_accounts_get_shell (EOnlineAccounts *extension)
+{
+ EExtensible *extensible;
+
+ extensible = e_extension_get_extensible (E_EXTENSION (extension));
+
+ return E_SHELL (extensible);
+}
+
+static void
+online_accounts_account_added_cb (GoaClient *goa_client,
+ GoaObject *goa_object,
+ EOnlineAccounts *extension)
+{
+ GoaAccount *goa_account;
+ const gchar *provider_type;
+ const gchar *goa_id;
+ const gchar *evo_id;
+
+ goa_account = goa_object_get_account (goa_object);
+ provider_type = goa_account_get_provider_type (goa_account);
+
+ goa_id = goa_account_get_id (goa_account);
+ evo_id = g_hash_table_lookup (extension->accounts, goa_id);
+
+ if (g_strcmp0 (provider_type, "google") == 0) {
+ if (evo_id == NULL) {
+ gchar *uid = e_uid_new ();
+ g_hash_table_insert (
+ extension->accounts,
+ g_strdup (goa_id), uid);
+ evo_id = uid;
+ }
+
+ e_online_accounts_google_sync (goa_object, evo_id);
+ }
+
+ g_object_unref (goa_account);
+}
+
+static void
+online_accounts_account_changed_cb (GoaClient *goa_client,
+ GoaObject *goa_object,
+ EOnlineAccounts *extension)
+{
+ /* XXX We'll be able to handle changes more sanely once we have
+ * key-file based ESources with proper change notifications. */
+ online_accounts_account_added_cb (goa_client, goa_object, extension);
+}
+
+static void
+online_accounts_account_removed_cb (GoaClient *goa_client,
+ GoaObject *goa_object,
+ EOnlineAccounts *extension)
+{
+ GoaAccount *goa_account;
+ EAccountList *account_list;
+ ESourceList *source_list;
+ ECalSourceType type;
+ EAccount *account;
+ const gchar *goa_id;
+ const gchar *evo_id;
+
+ goa_account = goa_object_get_account (goa_object);
+ goa_id = goa_account_get_id (goa_account);
+ evo_id = g_hash_table_lookup (extension->accounts, goa_id);
+
+ if (evo_id == NULL)
+ goto exit;
+
+ /* Remove the mail account. */
+
+ account_list = e_get_account_list ();
+ account = e_get_account_by_uid (evo_id);
+
+ if (account != NULL)
+ e_account_list_remove (account_list, account);
+
+ /* Remove the address book. */
+
+ if (e_book_get_addressbooks (&source_list, NULL)) {
+ e_source_list_remove_source_by_uid (source_list, evo_id);
+ g_object_unref (source_list);
+ }
+
+ /* Remove the calendar. */
+
+ for (type = 0; type < E_CAL_SOURCE_TYPE_LAST; type++) {
+ if (e_cal_get_sources (&source_list, type, NULL)) {
+ e_source_list_remove_source_by_uid (
+ source_list, evo_id);
+ g_object_unref (source_list);
+ }
+ }
+
+exit:
+ g_object_unref (goa_account);
+}
+
+static gint
+online_accounts_compare_id (GoaObject *goa_object,
+ const gchar *goa_id)
+{
+ GoaAccount *goa_account;
+ gint result;
+
+ goa_account = goa_object_get_account (goa_object);
+ result = g_strcmp0 (goa_account_get_id (goa_account), goa_id);
+ g_object_unref (goa_account);
+
+ return result;
+}
+
+static void
+online_accounts_handle_uid (EOnlineAccounts *extension,
+ const gchar *goa_id,
+ const gchar *evo_id)
+{
+ const gchar *match;
+
+ /* If the GNOME Online Account ID is already registered, the
+ * corresponding Evolution ID better match what was passed in. */
+ match = g_hash_table_lookup (extension->accounts, goa_id);
+ g_return_if_fail (match == NULL || g_strcmp0 (match, evo_id) == 0);
+
+ if (match == NULL)
+ g_hash_table_insert (
+ extension->accounts,
+ g_strdup (goa_id),
+ g_strdup (evo_id));
+}
+
+static void
+online_accounts_search_source_list (EOnlineAccounts *extension,
+ GList *goa_objects,
+ ESourceList *source_list)
+{
+ GSList *list_a;
+
+ list_a = e_source_list_peek_groups (source_list);
+
+ while (list_a != NULL) {
+ ESourceGroup *source_group;
+ GQueue trash = G_QUEUE_INIT;
+ GSList *list_b;
+
+ source_group = E_SOURCE_GROUP (list_a->data);
+ list_a = g_slist_next (list_a);
+
+ list_b = e_source_group_peek_sources (source_group);
+
+ while (list_b != NULL) {
+ ESource *source;
+ const gchar *property;
+ const gchar *uid;
+ GList *match;
+
+ source = E_SOURCE (list_b->data);
+ list_b = g_slist_next (list_b);
+
+ uid = e_source_peek_uid (source);
+ property = e_source_get_property (source, GOA_KEY);
+
+ if (property == NULL)
+ continue;
+
+ /* Verify the GOA account still exists. */
+ match = g_list_find_custom (
+ goa_objects, property, (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)
+ online_accounts_handle_uid (
+ extension, property, uid);
+ else
+ g_queue_push_tail (&trash, source);
+ }
+
+ /* Empty the trash. */
+ while (!g_queue_is_empty (&trash)) {
+ ESource *source = g_queue_pop_head (&trash);
+ e_source_group_remove_source (source_group, source);
+ }
+ }
+}
+
+static void
+online_accounts_populate_accounts_table (EOnlineAccounts *extension,
+ GList *goa_objects)
+{
+ EAccountList *account_list;
+ ESourceList *source_list;
+ EIterator *iterator;
+ ECalSourceType type;
+ GQueue trash = G_QUEUE_INIT;
+
+ /* XXX All this messy logic will be much prettier once the new
+ * key-file based ESource API is merged. For now though,
+ * we trudge through it the old and cumbersome way. */
+
+ /* Search mail accounts. */
+
+ account_list = e_get_account_list ();
+ iterator = e_list_get_iterator (E_LIST (account_list));
+
+ while (e_iterator_is_valid (iterator)) {
+ EAccount *account;
+ CamelURL *url;
+ const gchar *param;
+
+ /* XXX EIterator misuses const. */
+ account = (EAccount *) e_iterator_get (iterator);
+ e_iterator_next (iterator);
+
+ if (account->source == NULL)
+ continue;
+
+ if (account->source->url == NULL)
+ continue;
+
+ url = camel_url_new (account->source->url, NULL);
+ if (url == NULL)
+ continue;
+
+ param = camel_url_get_param (url, GOA_KEY);
+ if (param != NULL) {
+ GList *match;
+
+ /* Verify the GOA account still exists. */
+ match = g_list_find_custom (
+ goa_objects, param, (GCompareFunc)
+ online_accounts_compare_id);
+
+ /* If a matching GoaObject was found, add its ID
+ * to our accounts hash table. Otherwise remove
+ * the EAccount after we finish looping. */
+ if (match != NULL)
+ online_accounts_handle_uid (
+ extension, param, account->uid);
+ else
+ g_queue_push_tail (&trash, account);
+ }
+
+ camel_url_free (url);
+ }
+
+ g_object_unref (iterator);
+
+ /* Empty the trash. */
+ while (!g_queue_is_empty (&trash)) {
+ EAccount *account = g_queue_pop_head (&trash);
+ e_account_list_remove (account_list, account);
+ }
+
+ /* Search address book sources. */
+
+ if (e_book_get_addressbooks (&source_list, NULL)) {
+ online_accounts_search_source_list (
+ extension, goa_objects, source_list);
+ g_object_unref (source_list);
+ }
+
+ /* Search calendar-related sources. */
+
+ for (type = 0; type < E_CAL_SOURCE_TYPE_LAST; type++) {
+ if (e_cal_get_sources (&source_list, type, NULL)) {
+ online_accounts_search_source_list (
+ extension, goa_objects, source_list);
+ g_object_unref (source_list);
+ }
+ }
+}
+
+static void
+online_accounts_connect_done (GObject *source_object,
+ GAsyncResult *result,
+ EOnlineAccounts *extension)
+{
+ GList *list, *link;
+ GError *error = NULL;
+
+ extension->goa_client = goa_client_new_finish (result, &error);
+
+ /* FIXME Add an EAlert for this? */
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ list = goa_client_get_accounts (extension->goa_client);
+
+ /* This populates a hash table of GOA ID -> Evo ID strings by
+ * searching through all Evolution sources for ones tagged with
+ * a GOA ID. If a GOA ID tag is found, but no corresponding GOA
+ * account (presumably meaning the GOA account was deleted between
+ * Evo sessions), then the EAccount or ESource on which the tag 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-changed",
+ G_CALLBACK (online_accounts_account_changed_cb), extension);
+ g_signal_connect (
+ extension->goa_client, "account-removed",
+ G_CALLBACK (online_accounts_account_removed_cb), extension);
+
+ /* This will allow the Evolution Setup Assistant to proceed. */
+ g_object_unref (extension->connecting);
+ extension->connecting = NULL;
+}
+
+static void
+online_accounts_connect (EShell *shell,
+ EActivity *activity,
+ EOnlineAccounts *extension)
+{
+ /* This will inhibit the Evolution Setup Assistant until
+ * we've synchronized with the OnlineAccounts service. */
+ extension->connecting = g_object_ref (activity);
+
+ /* We don't really need to reference the extension in the
+ * async closure since its lifetime is bound to the EShell. */
+ goa_client_new (
+ NULL, (GAsyncReadyCallback)
+ online_accounts_connect_done, extension);
+}
+
+static void
+online_accounts_dispose (GObject *object)
+{
+ EOnlineAccounts *extension;
+
+ extension = E_ONLINE_ACCOUNTS (object);
+
+ /* This should never fail... in theory. */
+ g_warn_if_fail (extension->connecting == NULL);
+
+ 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;
+ }
+
+ /* 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->accounts);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_online_accounts_parent_class)->finalize (object);
+}
+
+static void
+online_accounts_constructed (GObject *object)
+{
+ EOnlineAccounts *extension;
+ EShell *shell;
+
+ extension = E_ONLINE_ACCOUNTS (object);
+ shell = online_accounts_get_shell (extension);
+
+ /* This event is emitted from the "startup-wizard" module. */
+ g_signal_connect (
+ shell, "event::load-accounts",
+ G_CALLBACK (online_accounts_connect), 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_SHELL;
+}
+
+static void
+e_online_accounts_class_finalize (EOnlineAccountsClass *class)
+{
+}
+
+static void
+e_online_accounts_init (EOnlineAccounts *extension)
+{
+ extension->accounts = 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);
+ camel_sasl_xoauth_type_register (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
+