/* * 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 * */ #include #include /* 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 #include #include #include #include #include #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) { }