2 * module-online-accounts.c
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
19 /* XXX Yeah, yeah... */
20 #define GOA_API_IS_SUBJECT_TO_CHANGE
24 #include <gnome-keyring.h>
26 #include <libebackend/libebackend.h>
28 #include "goaewsclient.h"
30 /* Standard GObject macros */
31 #define E_TYPE_ONLINE_ACCOUNTS \
32 (e_online_accounts_get_type ())
33 #define E_ONLINE_ACCOUNTS(obj) \
34 (G_TYPE_CHECK_INSTANCE_CAST \
35 ((obj), E_TYPE_ONLINE_ACCOUNTS, EOnlineAccounts))
37 #define CAMEL_OAUTH_MECHANISM_NAME "XOAUTH"
39 typedef struct _EOnlineAccounts EOnlineAccounts;
40 typedef struct _EOnlineAccountsClass EOnlineAccountsClass;
42 struct _EOnlineAccounts {
45 GoaClient *goa_client;
46 GCancellable *create_client;
48 /* GoaAccount ID -> ESource UID */
49 GHashTable *goa_to_eds;
52 struct _EOnlineAccountsClass {
53 EExtensionClass parent_class;
56 /* The keyring definintions are copied from e-authentication-session.c */
58 #define KEYRING_ITEM_ATTRIBUTE_NAME "e-source-uid"
59 #define KEYRING_ITEM_DISPLAY_FORMAT "Evolution Data Source %s"
61 #ifdef HAVE_GOA_PASSWORD_BASED
62 static GnomeKeyringPasswordSchema schema = {
63 GNOME_KEYRING_ITEM_GENERIC_SECRET,
65 { KEYRING_ITEM_ATTRIBUTE_NAME,
66 GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
70 #endif /* HAVE_GOA_PASSWORD_BASED */
72 /* Module Entry Points */
73 void e_module_load (GTypeModule *type_module);
74 void e_module_unload (GTypeModule *type_module);
76 /* Forward Declarations */
77 GType e_online_accounts_get_type (void);
79 G_DEFINE_DYNAMIC_TYPE (
85 online_accounts_get_backend_name (const gchar *goa_provider_type)
87 const gchar *eds_backend_name = NULL;
89 /* This is a mapping between GoaAccount provider types and
90 * ESourceCollection backend names. It requires knowledge
91 * of other registry modules, possibly even from 3rd party
92 * packages. No way around it. */
94 if (g_strcmp0 (goa_provider_type, "exchange") == 0)
95 eds_backend_name = "ews";
97 if (g_strcmp0 (goa_provider_type, "google") == 0)
98 eds_backend_name = "google";
100 else if (g_strcmp0 (goa_provider_type, "yahoo") == 0)
101 eds_backend_name = "yahoo";
103 return eds_backend_name;
106 static ESourceRegistryServer *
107 online_accounts_get_server (EOnlineAccounts *extension)
109 EExtensible *extensible;
111 extensible = e_extension_get_extensible (E_EXTENSION (extension));
113 return E_SOURCE_REGISTRY_SERVER (extensible);
117 online_accounts_provider_type_to_backend_name (GBinding *binding,
118 const GValue *source_value,
119 GValue *target_value,
122 const gchar *provider_type;
123 const gchar *backend_name;
125 provider_type = g_value_get_string (source_value);
126 backend_name = online_accounts_get_backend_name (provider_type);
127 g_return_val_if_fail (backend_name != NULL, FALSE);
128 g_value_set_string (target_value, backend_name);
134 online_accounts_object_is_non_null (GBinding *binding,
135 const GValue *source_value,
136 GValue *target_value,
141 v_object = g_value_get_object (source_value);
142 g_value_set_boolean (target_value, v_object != NULL);
148 online_accounts_new_source (EOnlineAccounts *extension)
150 ESourceRegistryServer *server;
153 GError *error = NULL;
155 /* This being a brand new data source, creating the instance
156 * should never fail but we'll check for errors just the same. */
157 server = online_accounts_get_server (extension);
158 file = e_server_side_source_new_user_file (NULL);
159 source = e_server_side_source_new (server, file, &error);
160 g_object_unref (file);
163 g_warn_if_fail (source == NULL);
164 g_warning ("%s: %s", G_STRFUNC, error->message);
165 g_error_free (error);
172 online_accounts_config_exchange (EOnlineAccounts *extension,
174 GoaObject *goa_object)
176 #ifdef HAVE_GOA_PASSWORD_BASED
177 ESourceExtension *source_extension;
178 const gchar *extension_name;
179 gchar *as_url = NULL;
180 gchar *oab_url = NULL;
182 GError *error = NULL;
184 if (goa_object_peek_exchange (goa_object) == NULL)
187 /* This should force the ESourceCamelEws type to be registered.
188 * It will also tell us if Evolution-EWS is even installed. */
189 class = g_type_class_ref (g_type_from_name ("EEwsBackend"));
191 g_type_class_unref (class);
194 "%s: Could not locate EEwsBackendClass. "
195 "Is Evolution-EWS installed?", G_STRFUNC);
199 /* XXX GNOME Online Accounts already runs autodiscover to test
200 * the user-entered values but doesn't share the discovered
201 * URLs. It only provides us a host name and expects us to
202 * re-run autodiscover for ourselves.
204 * So I've copied a slab of code from GOA which was in turn
205 * copied from Evolution-EWS which does the autodiscovery.
207 * I've already complained to Debarshi Ray about the lack
208 * of useful info in GOA's Exchange interface so hopefully
209 * it will someday publish discovered URLs and then we can
210 * remove this hack. */
212 goa_ews_autodiscover_sync (
213 goa_object, &as_url, &oab_url, NULL, &error);
216 g_warning ("%s: %s", G_STRFUNC, error->message);
217 g_error_free (error);
221 g_return_if_fail (as_url != NULL);
222 g_return_if_fail (oab_url != NULL);
224 /* XXX We don't have direct access to CamelEwsSettings from here
225 * since it's defined in Evolution-EWS. But we can find out
226 * its extension name and set properties by name. */
228 extension_name = e_source_camel_get_extension_name ("ews");
229 source_extension = e_source_get_extension (source, extension_name);
231 /* This will be NULL if Evolution-EWS is not installed. */
232 if (source_extension != NULL) {
240 "%s: Failed to create [%s] extension",
241 G_STRFUNC, extension_name);
246 #endif /* HAVE_GOA_PASSWORD_BASED */
250 online_accounts_config_oauth (EOnlineAccounts *extension,
252 GoaObject *goa_object)
254 ESourceExtension *source_extension;
255 const gchar *extension_name;
257 if (goa_object_peek_oauth_based (goa_object) == NULL)
260 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
261 source_extension = e_source_get_extension (source, extension_name);
263 e_source_authentication_set_method (
264 E_SOURCE_AUTHENTICATION (source_extension),
265 CAMEL_OAUTH_MECHANISM_NAME);
269 online_accounts_config_password (EOnlineAccounts *extension,
271 GoaObject *goa_object)
273 #ifdef HAVE_GOA_PASSWORD_BASED
274 GoaAccount *goa_account;
275 GoaPasswordBased *goa_password_based;
276 GnomeKeyringResult keyring_result;
277 EAsyncClosure *closure;
278 GAsyncResult *result;
282 gchar *password = NULL;
283 GError *error = NULL;
285 /* If the GNOME Online Account is password-based, we use its
286 * password to seed our own keyring entry for the collection
287 * source which avoids having to special-case authentication
288 * like we do for OAuth. Plus, if the stored password is no
289 * good we'll prompt for a new one instead of just giving up. */
291 goa_password_based = goa_object_get_password_based (goa_object);
293 if (goa_password_based == NULL)
296 closure = e_async_closure_new ();
298 /* XXX The GOA documentation doesn't explain the string
299 * argument in goa_password_based_get_password() so
300 * we'll pass in the identity and hope for the best. */
301 goa_account = goa_object_get_account (goa_object);
302 arg_id = goa_account_dup_identity (goa_account);
303 g_object_unref (goa_account);
305 goa_password_based_call_get_password (
306 goa_password_based, arg_id, NULL,
307 e_async_closure_callback, closure);
311 result = e_async_closure_wait (closure);
313 goa_password_based_call_get_password_finish (
314 goa_password_based, &password, result, &error);
317 g_warning ("%s: %s", G_STRFUNC, error->message);
318 g_error_free (error);
322 uid = e_source_get_uid (source);
323 display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
325 /* XXX Just call gnome-keyring synchronously. I know it's
326 * evil, but I want to know the password has been stored
327 * before returning from this function. We'll be moving
328 * to libsecret soon anyway, which is more GIO-based, so
329 * we could then reuse the EAsyncClosure here. */
330 keyring_result = gnome_keyring_store_password_sync (
331 &schema, GNOME_KEYRING_DEFAULT, display_name,
332 password, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
334 g_free (display_name);
336 /* If we fail to store the password, we'll just end up prompting
337 * for a password like normal. Annoying, maybe, but not the end
338 * of the world. Still leave a breadcrumb for debugging though. */
339 if (keyring_result != GNOME_KEYRING_RESULT_OK) {
340 const gchar *message;
341 message = gnome_keyring_result_to_message (keyring_result);
342 g_warning ("%s: %s", G_STRFUNC, message);
346 e_async_closure_free (closure);
347 g_object_unref (goa_password_based);
348 #endif /* HAVE_GOA_PASSWORD_BASED */
352 online_accounts_config_collection (EOnlineAccounts *extension,
354 GoaObject *goa_object)
356 GoaAccount *goa_account;
357 ESourceExtension *source_extension;
358 const gchar *extension_name;
360 goa_account = goa_object_get_account (goa_object);
362 g_object_bind_property (
363 goa_account, "presentation-identity",
364 source, "display-name",
365 G_BINDING_SYNC_CREATE);
367 extension_name = E_SOURCE_EXTENSION_GOA;
368 source_extension = e_source_get_extension (source, extension_name);
370 g_object_bind_property (
372 source_extension, "account-id",
373 G_BINDING_SYNC_CREATE);
375 extension_name = E_SOURCE_EXTENSION_COLLECTION;
376 source_extension = e_source_get_extension (source, extension_name);
378 g_object_bind_property_full (
379 goa_account, "provider-type",
380 source_extension, "backend-name",
381 G_BINDING_SYNC_CREATE,
382 online_accounts_provider_type_to_backend_name,
384 NULL, (GDestroyNotify) NULL);
386 g_object_bind_property (
387 goa_account, "identity",
388 source_extension, "identity",
389 G_BINDING_SYNC_CREATE);
391 g_object_bind_property_full (
392 goa_object, "calendar",
393 source_extension, "calendar-enabled",
394 G_BINDING_SYNC_CREATE,
395 online_accounts_object_is_non_null,
397 NULL, (GDestroyNotify) NULL);
399 g_object_bind_property_full (
400 goa_object, "contacts",
401 source_extension, "contacts-enabled",
402 G_BINDING_SYNC_CREATE,
403 online_accounts_object_is_non_null,
405 NULL, (GDestroyNotify) NULL);
407 g_object_bind_property_full (
409 source_extension, "mail-enabled",
410 G_BINDING_SYNC_CREATE,
411 online_accounts_object_is_non_null,
413 NULL, (GDestroyNotify) NULL);
415 g_object_unref (goa_account);
417 /* Handle optional GOA interfaces. */
418 online_accounts_config_exchange (extension, source, goa_object);
419 online_accounts_config_password (extension, source, goa_object);
421 /* The data source should not be removable by clients. */
422 e_server_side_source_set_removable (
423 E_SERVER_SIDE_SOURCE (source), FALSE);
427 online_accounts_config_mail_account (EOnlineAccounts *extension,
429 GoaObject *goa_object)
431 online_accounts_config_oauth (extension, source, goa_object);
433 /* XXX Need to defer the network security settings to the
434 * provider-specific module since "imap-use-tls" tells
435 * us neither the port number, nor whether to use IMAP
436 * over SSL versus STARTTLS. The module will know. */
440 online_accounts_config_mail_identity (EOnlineAccounts *extension,
442 GoaObject *goa_object)
445 ESourceExtension *source_extension;
446 const gchar *extension_name;
448 goa_mail = goa_object_get_mail (goa_object);
449 g_return_if_fail (goa_mail != NULL);
451 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
452 source_extension = e_source_get_extension (source, extension_name);
454 g_object_bind_property (
455 goa_mail, "email-address",
456 source_extension, "address",
457 G_BINDING_SYNC_CREATE);
459 g_object_unref (goa_mail);
463 online_accounts_config_mail_transport (EOnlineAccounts *extension,
465 GoaObject *goa_object)
467 online_accounts_config_oauth (extension, source, goa_object);
469 /* XXX Need to defer the network security settings to the
470 * provider-specific module since "smtp-use-tls" tells
471 * us neither the port number, nor whether to use SMTP
472 * over SSL versus STARTTLS. The module will know. */
476 online_accounts_create_collection (EOnlineAccounts *extension,
477 EBackendFactory *backend_factory,
478 GoaObject *goa_object)
480 GoaAccount *goa_account;
481 ESourceRegistryServer *server;
482 ESource *collection_source;
483 ESource *mail_account_source;
484 ESource *mail_identity_source;
485 ESource *mail_transport_source;
486 const gchar *account_id;
487 const gchar *parent_uid;
489 server = online_accounts_get_server (extension);
491 collection_source = online_accounts_new_source (extension);
492 g_return_if_fail (E_IS_SOURCE (collection_source));
494 mail_account_source = online_accounts_new_source (extension);
495 g_return_if_fail (E_IS_SOURCE (mail_account_source));
497 mail_identity_source = online_accounts_new_source (extension);
498 g_return_if_fail (E_IS_SOURCE (mail_identity_source));
500 mail_transport_source = online_accounts_new_source (extension);
501 g_return_if_fail (E_IS_SOURCE (mail_transport_source));
503 /* Configure parent/child relationships. */
504 parent_uid = e_source_get_uid (collection_source);
505 e_source_set_parent (mail_account_source, parent_uid);
506 e_source_set_parent (mail_identity_source, parent_uid);
507 e_source_set_parent (mail_transport_source, parent_uid);
509 /* Give the factory first crack at mail configuration. */
510 e_collection_backend_factory_prepare_mail (
511 E_COLLECTION_BACKEND_FACTORY (backend_factory),
513 mail_identity_source,
514 mail_transport_source);
516 /* Now it's our turn. */
517 online_accounts_config_collection (
518 extension, collection_source, goa_object);
519 online_accounts_config_mail_account (
520 extension, mail_account_source, goa_object);
521 online_accounts_config_mail_identity (
522 extension, mail_identity_source, goa_object);
523 online_accounts_config_mail_transport (
524 extension, mail_transport_source, goa_object);
526 /* Export the new source collection. */
527 e_source_registry_server_add_source (server, collection_source);
528 e_source_registry_server_add_source (server, mail_account_source);
529 e_source_registry_server_add_source (server, mail_identity_source);
530 e_source_registry_server_add_source (server, mail_transport_source);
532 goa_account = goa_object_get_account (goa_object);
533 account_id = goa_account_get_id (goa_account);
535 g_hash_table_insert (
536 extension->goa_to_eds,
537 g_strdup (account_id),
538 g_strdup (parent_uid));
540 g_object_unref (goa_account);
542 g_object_unref (collection_source);
543 g_object_unref (mail_account_source);
544 g_object_unref (mail_identity_source);
545 g_object_unref (mail_transport_source);
549 online_accounts_remove_collection (EOnlineAccounts *extension,
552 GError *error = NULL;
554 /* This removes the entire subtree rooted at source.
555 * Deletes the corresponding on-disk key files too. */
556 e_source_remove_sync (source, NULL, &error);
559 g_warning ("%s: %s", G_STRFUNC, error->message);
560 g_error_free (error);
565 online_accounts_account_added_cb (GoaClient *goa_client,
566 GoaObject *goa_object,
567 EOnlineAccounts *extension)
569 GoaAccount *goa_account;
570 ESourceRegistryServer *server;
571 EBackendFactory *backend_factory = NULL;
572 const gchar *provider_type;
573 const gchar *backend_name;
574 const gchar *account_id;
575 const gchar *source_uid;
577 server = online_accounts_get_server (extension);
579 goa_account = goa_object_get_account (goa_object);
580 provider_type = goa_account_get_provider_type (goa_account);
581 backend_name = online_accounts_get_backend_name (provider_type);
583 account_id = goa_account_get_id (goa_account);
584 source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
586 if (source_uid == NULL && backend_name != NULL)
587 backend_factory = e_data_factory_ref_backend_factory (
588 E_DATA_FACTORY (server), backend_name);
590 if (backend_factory != NULL) {
591 online_accounts_create_collection (
592 extension, backend_factory, goa_object);
593 g_object_unref (backend_factory);
596 g_object_unref (goa_account);
600 online_accounts_account_removed_cb (GoaClient *goa_client,
601 GoaObject *goa_object,
602 EOnlineAccounts *extension)
604 ESource *source = NULL;
605 ESourceRegistryServer *server;
606 GoaAccount *goa_account;
607 const gchar *account_id;
608 const gchar *source_uid;
610 server = online_accounts_get_server (extension);
612 goa_account = goa_object_get_account (goa_object);
614 account_id = goa_account_get_id (goa_account);
615 source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
617 if (source_uid != NULL)
618 source = e_source_registry_server_ref_source (
621 if (source != NULL) {
622 online_accounts_remove_collection (extension, source);
623 g_object_unref (source);
626 g_object_unref (goa_account);
630 online_accounts_compare_id (GoaObject *goa_object,
631 const gchar *target_id)
633 GoaAccount *goa_account;
634 const gchar *account_id;
637 goa_account = goa_object_get_account (goa_object);
638 account_id = goa_account_get_id (goa_account);
639 result = g_strcmp0 (account_id, target_id);
640 g_object_unref (goa_account);
646 online_accounts_populate_accounts_table (EOnlineAccounts *extension,
649 ESourceRegistryServer *server;
650 GQueue trash = G_QUEUE_INIT;
652 const gchar *extension_name;
654 server = online_accounts_get_server (extension);
656 extension_name = E_SOURCE_EXTENSION_GOA;
657 list = e_source_registry_server_list_sources (server, extension_name);
659 for (link = list; link != NULL; link = g_list_next (link)) {
662 const gchar *account_id;
663 const gchar *source_uid;
666 source = E_SOURCE (link->data);
667 source_uid = e_source_get_uid (source);
669 extension_name = E_SOURCE_EXTENSION_GOA;
670 goa_ext = e_source_get_extension (source, extension_name);
671 account_id = e_source_goa_get_account_id (goa_ext);
673 if (account_id == NULL)
676 /* Verify the GOA account still exists. */
677 match = g_list_find_custom (
678 goa_objects, account_id,
679 (GCompareFunc) online_accounts_compare_id);
681 /* If a matching GoaObject was found, add its ID
682 * to our accounts hash table. Otherwise remove
683 * the ESource after we finish looping. */
685 GoaObject *goa_object;
687 g_hash_table_insert (
688 extension->goa_to_eds,
689 g_strdup (account_id),
690 g_strdup (source_uid));
692 goa_object = GOA_OBJECT (match->data);
693 online_accounts_config_collection (
694 extension, source, goa_object);
696 g_queue_push_tail (&trash, source);
700 /* Empty the trash. */
701 while (!g_queue_is_empty (&trash)) {
702 ESource *source = g_queue_pop_head (&trash);
703 online_accounts_remove_collection (extension, source);
706 g_list_free_full (list, (GDestroyNotify) g_object_unref);
710 online_accounts_create_client_cb (GObject *source_object,
711 GAsyncResult *result,
714 EOnlineAccounts *extension;
715 GoaClient *goa_client;
717 GError *error = NULL;
719 /* If we get back a G_IO_ERROR_CANCELLED then it means the
720 * EOnlineAccounts is already finalized, so be careful not
721 * to touch it until after we have a valid GoaClient. */
723 goa_client = goa_client_new_finish (result, &error);
726 g_warn_if_fail (goa_client == NULL);
728 "Unable to connect to the GNOME Online "
729 "Accounts service: %s", error->message);
730 g_error_free (error);
734 g_return_if_fail (GOA_IS_CLIENT (goa_client));
736 /* Should be safe to dereference the EOnlineAccounts now. */
738 extension = E_ONLINE_ACCOUNTS (user_data);
739 extension->goa_client = goa_client; /* takes ownership */
741 /* Don't need the GCancellable anymore. */
742 g_object_unref (extension->create_client);
743 extension->create_client = NULL;
745 list = goa_client_get_accounts (extension->goa_client);
747 /* This populates a hash table of GOA ID -> ESource UID strings by
748 * searching through available data sources for ones with a "GNOME
749 * Online Accounts" extension. If such an extension is found, but
750 * no corresponding GoaAccount (presumably meaning the GOA account
751 * was somehow deleted between E-D-S sessions) then the ESource in
752 * which the extension was found gets deleted. */
753 online_accounts_populate_accounts_table (extension, list);
755 for (link = list; link != NULL; link = g_list_next (link))
756 online_accounts_account_added_cb (
757 extension->goa_client,
758 GOA_OBJECT (link->data),
761 g_list_free_full (list, (GDestroyNotify) g_object_unref);
763 /* Listen for Online Account changes. */
765 extension->goa_client, "account-added",
766 G_CALLBACK (online_accounts_account_added_cb), extension);
768 extension->goa_client, "account-removed",
769 G_CALLBACK (online_accounts_account_removed_cb), extension);
773 online_accounts_bus_acquired_cb (EDBusServer *server,
774 GDBusConnection *connection,
775 EOnlineAccounts *extension)
777 /* Connect to the GNOME Online Accounts service. */
779 /* Note we don't reference the extension. If the
780 * extension gets destroyed before this completes
781 * we cancel the operation from dispose(). */
783 extension->create_client,
784 online_accounts_create_client_cb,
789 online_accounts_dispose (GObject *object)
791 EOnlineAccounts *extension;
793 extension = E_ONLINE_ACCOUNTS (object);
795 if (extension->goa_client != NULL) {
796 g_signal_handlers_disconnect_matched (
797 extension->goa_client,
799 0, 0, NULL, NULL, object);
800 g_object_unref (extension->goa_client);
801 extension->goa_client = NULL;
804 /* This cancels goa_client_new() in case it still
805 * hasn't completed. We're no longer interested. */
806 if (extension->create_client != NULL) {
807 g_cancellable_cancel (extension->create_client);
808 g_object_unref (extension->create_client);
809 extension->create_client = NULL;
812 /* Chain up to parent's dispose() method. */
813 G_OBJECT_CLASS (e_online_accounts_parent_class)->dispose (object);
817 online_accounts_finalize (GObject *object)
819 EOnlineAccounts *extension;
821 extension = E_ONLINE_ACCOUNTS (object);
823 g_hash_table_destroy (extension->goa_to_eds);
825 /* Chain up to parent's finalize() method. */
826 G_OBJECT_CLASS (e_online_accounts_parent_class)->finalize (object);
830 online_accounts_constructed (GObject *object)
832 EExtension *extension;
833 EExtensible *extensible;
835 extension = E_EXTENSION (object);
836 extensible = e_extension_get_extensible (extension);
838 /* Wait for the registry service to acquire its well-known
839 * bus name so we don't do anything destructive beforehand. */
842 extensible, "bus-acquired",
843 G_CALLBACK (online_accounts_bus_acquired_cb), extension);
845 /* Chain up to parent's constructed() method. */
846 G_OBJECT_CLASS (e_online_accounts_parent_class)->constructed (object);
850 e_online_accounts_class_init (EOnlineAccountsClass *class)
852 GObjectClass *object_class;
853 EExtensionClass *extension_class;
855 object_class = G_OBJECT_CLASS (class);
856 object_class->dispose = online_accounts_dispose;
857 object_class->finalize = online_accounts_finalize;
858 object_class->constructed = online_accounts_constructed;
860 extension_class = E_EXTENSION_CLASS (class);
861 extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
865 e_online_accounts_class_finalize (EOnlineAccountsClass *class)
870 e_online_accounts_init (EOnlineAccounts *extension)
872 /* Used to cancel unfinished goa_client_new(). */
873 extension->create_client = g_cancellable_new ();
875 extension->goa_to_eds = g_hash_table_new_full (
876 (GHashFunc) g_str_hash,
877 (GEqualFunc) g_str_equal,
878 (GDestroyNotify) g_free,
879 (GDestroyNotify) g_free);
883 e_module_load (GTypeModule *type_module)
885 e_online_accounts_register_type (type_module);
889 e_module_unload (GTypeModule *type_module)