137eaf53dca3fbb4f3584c15fe61deb489d28347
[platform/upstream/evolution-data-server.git] / modules / online-accounts / module-online-accounts.c
1 /*
2  * module-online-accounts.c
3  *
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.
8  *
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.
13  *
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/>
16  *
17  */
18
19 /* XXX Yeah, yeah... */
20 #define GOA_API_IS_SUBJECT_TO_CHANGE
21
22 #include <config.h>
23 #include <goa/goa.h>
24 #include <gnome-keyring.h>
25
26 #include <libebackend/libebackend.h>
27
28 #include "goaewsclient.h"
29
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))
36
37 #define CAMEL_OAUTH_MECHANISM_NAME  "XOAUTH"
38
39 typedef struct _EOnlineAccounts EOnlineAccounts;
40 typedef struct _EOnlineAccountsClass EOnlineAccountsClass;
41
42 struct _EOnlineAccounts {
43         EExtension parent;
44
45         GoaClient *goa_client;
46         GCancellable *create_client;
47
48         /* GoaAccount ID -> ESource UID */
49         GHashTable *goa_to_eds;
50 };
51
52 struct _EOnlineAccountsClass {
53         EExtensionClass parent_class;
54 };
55
56 /* The keyring definintions are copied from e-authentication-session.c */
57
58 #define KEYRING_ITEM_ATTRIBUTE_NAME     "e-source-uid"
59 #define KEYRING_ITEM_DISPLAY_FORMAT     "Evolution Data Source %s"
60
61 #ifdef HAVE_GOA_PASSWORD_BASED
62 static GnomeKeyringPasswordSchema schema = {
63         GNOME_KEYRING_ITEM_GENERIC_SECRET,
64         {
65                 { KEYRING_ITEM_ATTRIBUTE_NAME,
66                   GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
67                 { NULL, 0 }
68         }
69 };
70 #endif /* HAVE_GOA_PASSWORD_BASED */
71
72 /* Module Entry Points */
73 void e_module_load (GTypeModule *type_module);
74 void e_module_unload (GTypeModule *type_module);
75
76 /* Forward Declarations */
77 GType e_online_accounts_get_type (void);
78
79 G_DEFINE_DYNAMIC_TYPE (
80         EOnlineAccounts,
81         e_online_accounts,
82         E_TYPE_EXTENSION)
83
84 static const gchar *
85 online_accounts_get_backend_name (const gchar *goa_provider_type)
86 {
87         const gchar *eds_backend_name = NULL;
88
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. */
93
94         if (g_strcmp0 (goa_provider_type, "exchange") == 0)
95                 eds_backend_name = "ews";
96
97         if (g_strcmp0 (goa_provider_type, "google") == 0)
98                 eds_backend_name = "google";
99
100         else if (g_strcmp0 (goa_provider_type, "yahoo") == 0)
101                 eds_backend_name = "yahoo";
102
103         return eds_backend_name;
104 }
105
106 static ESourceRegistryServer *
107 online_accounts_get_server (EOnlineAccounts *extension)
108 {
109         EExtensible *extensible;
110
111         extensible = e_extension_get_extensible (E_EXTENSION (extension));
112
113         return E_SOURCE_REGISTRY_SERVER (extensible);
114 }
115
116 static gboolean
117 online_accounts_provider_type_to_backend_name (GBinding *binding,
118                                                const GValue *source_value,
119                                                GValue *target_value,
120                                                gpointer unused)
121 {
122         const gchar *provider_type;
123         const gchar *backend_name;
124
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);
129
130         return TRUE;
131 }
132
133 static gboolean
134 online_accounts_object_is_non_null (GBinding *binding,
135                                     const GValue *source_value,
136                                     GValue *target_value,
137                                     gpointer unused)
138 {
139         gpointer v_object;
140
141         v_object = g_value_get_object (source_value);
142         g_value_set_boolean (target_value, v_object != NULL);
143
144         return TRUE;
145 }
146
147 static ESource *
148 online_accounts_new_source (EOnlineAccounts *extension)
149 {
150         ESourceRegistryServer *server;
151         ESource *source;
152         GFile *file;
153         GError *error = NULL;
154
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);
161
162         if (error != NULL) {
163                 g_warn_if_fail (source == NULL);
164                 g_warning ("%s: %s", G_STRFUNC, error->message);
165                 g_error_free (error);
166         }
167
168         return source;
169 }
170
171 static void
172 online_accounts_config_exchange (EOnlineAccounts *extension,
173                                  ESource *source,
174                                  GoaObject *goa_object)
175 {
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;
181         gpointer class;
182         GError *error = NULL;
183
184         if (goa_object_peek_exchange (goa_object) == NULL)
185                 return;
186
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"));
190         if (class != NULL) {
191                 g_type_class_unref (class);
192         } else {
193                 g_critical (
194                         "%s: Could not locate EEwsBackendClass. "
195                         "Is Evolution-EWS installed?", G_STRFUNC);
196                 return;
197         }
198
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.
203          *
204          *     So I've copied a slab of code from GOA which was in turn
205          *     copied from Evolution-EWS which does the autodiscovery.
206          *
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. */
211
212         goa_ews_autodiscover_sync (
213                 goa_object, &as_url, &oab_url, NULL, &error);
214
215         if (error != NULL) {
216                 g_warning ("%s: %s", G_STRFUNC, error->message);
217                 g_error_free (error);
218                 return;
219         }
220
221         g_return_if_fail (as_url != NULL);
222         g_return_if_fail (oab_url != NULL);
223
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. */
227
228         extension_name = e_source_camel_get_extension_name ("ews");
229         source_extension = e_source_get_extension (source, extension_name);
230
231         /* This will be NULL if Evolution-EWS is not installed. */
232         if (source_extension != NULL) {
233                 g_object_set (
234                         source_extension,
235                         "hosturl", as_url,
236                         "oaburl", oab_url,
237                         NULL);
238         } else {
239                 g_critical (
240                         "%s: Failed to create [%s] extension",
241                         G_STRFUNC, extension_name);
242         }
243
244         g_free (as_url);
245         g_free (oab_url);
246 #endif /* HAVE_GOA_PASSWORD_BASED */
247 }
248
249 static void
250 online_accounts_config_oauth (EOnlineAccounts *extension,
251                               ESource *source,
252                               GoaObject *goa_object)
253 {
254         ESourceExtension *source_extension;
255         const gchar *extension_name;
256
257         if (goa_object_peek_oauth_based (goa_object) == NULL)
258                 return;
259
260         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
261         source_extension = e_source_get_extension (source, extension_name);
262
263         e_source_authentication_set_method (
264                 E_SOURCE_AUTHENTICATION (source_extension),
265                 CAMEL_OAUTH_MECHANISM_NAME);
266 }
267
268 static void
269 online_accounts_config_password (EOnlineAccounts *extension,
270                                  ESource *source,
271                                  GoaObject *goa_object)
272 {
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;
279         const gchar *uid;
280         gchar *arg_id;
281         gchar *display_name;
282         gchar *password = NULL;
283         GError *error = NULL;
284
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. */
290
291         goa_password_based = goa_object_get_password_based (goa_object);
292
293         if (goa_password_based == NULL)
294                 return;
295
296         closure = e_async_closure_new ();
297
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);
304
305         goa_password_based_call_get_password (
306                 goa_password_based, arg_id, NULL,
307                 e_async_closure_callback, closure);
308
309         g_free (arg_id);
310
311         result = e_async_closure_wait (closure);
312
313         goa_password_based_call_get_password_finish (
314                 goa_password_based, &password, result, &error);
315
316         if (error != NULL) {
317                 g_warning ("%s: %s", G_STRFUNC, error->message);
318                 g_error_free (error);
319                 goto exit;
320         }
321
322         uid = e_source_get_uid (source);
323         display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
324
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);
333
334         g_free (display_name);
335
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);
343         }
344
345 exit:
346         e_async_closure_free (closure);
347         g_object_unref (goa_password_based);
348 #endif /* HAVE_GOA_PASSWORD_BASED */
349 }
350
351 static void
352 online_accounts_config_collection (EOnlineAccounts *extension,
353                                    ESource *source,
354                                    GoaObject *goa_object)
355 {
356         GoaAccount *goa_account;
357         ESourceExtension *source_extension;
358         const gchar *extension_name;
359
360         goa_account = goa_object_get_account (goa_object);
361
362         g_object_bind_property (
363                 goa_account, "presentation-identity",
364                 source, "display-name",
365                 G_BINDING_SYNC_CREATE);
366
367         extension_name = E_SOURCE_EXTENSION_GOA;
368         source_extension = e_source_get_extension (source, extension_name);
369
370         g_object_bind_property (
371                 goa_account, "id",
372                 source_extension, "account-id",
373                 G_BINDING_SYNC_CREATE);
374
375         extension_name = E_SOURCE_EXTENSION_COLLECTION;
376         source_extension = e_source_get_extension (source, extension_name);
377
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,
383                 NULL,
384                 NULL, (GDestroyNotify) NULL);
385
386         g_object_bind_property (
387                 goa_account, "identity",
388                 source_extension, "identity",
389                 G_BINDING_SYNC_CREATE);
390
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,
396                 NULL,
397                 NULL, (GDestroyNotify) NULL);
398
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,
404                 NULL,
405                 NULL, (GDestroyNotify) NULL);
406
407         g_object_bind_property_full (
408                 goa_object, "mail",
409                 source_extension, "mail-enabled",
410                 G_BINDING_SYNC_CREATE,
411                 online_accounts_object_is_non_null,
412                 NULL,
413                 NULL, (GDestroyNotify) NULL);
414
415         g_object_unref (goa_account);
416
417         /* Handle optional GOA interfaces. */
418         online_accounts_config_exchange (extension, source, goa_object);
419         online_accounts_config_password (extension, source, goa_object);
420
421         /* The data source should not be removable by clients. */
422         e_server_side_source_set_removable (
423                 E_SERVER_SIDE_SOURCE (source), FALSE);
424 }
425
426 static void
427 online_accounts_config_mail_account (EOnlineAccounts *extension,
428                                      ESource *source,
429                                      GoaObject *goa_object)
430 {
431         online_accounts_config_oauth (extension, source, goa_object);
432
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. */
437 }
438
439 static void
440 online_accounts_config_mail_identity (EOnlineAccounts *extension,
441                                       ESource *source,
442                                       GoaObject *goa_object)
443 {
444         GoaMail *goa_mail;
445         ESourceExtension *source_extension;
446         const gchar *extension_name;
447
448         goa_mail = goa_object_get_mail (goa_object);
449         g_return_if_fail (goa_mail != NULL);
450
451         extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
452         source_extension = e_source_get_extension (source, extension_name);
453
454         g_object_bind_property (
455                 goa_mail, "email-address",
456                 source_extension, "address",
457                 G_BINDING_SYNC_CREATE);
458
459         g_object_unref (goa_mail);
460 }
461
462 static void
463 online_accounts_config_mail_transport (EOnlineAccounts *extension,
464                                        ESource *source,
465                                        GoaObject *goa_object)
466 {
467         online_accounts_config_oauth (extension, source, goa_object);
468
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. */
473 }
474
475 static void
476 online_accounts_create_collection (EOnlineAccounts *extension,
477                                    EBackendFactory *backend_factory,
478                                    GoaObject *goa_object)
479 {
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;
488
489         server = online_accounts_get_server (extension);
490
491         collection_source = online_accounts_new_source (extension);
492         g_return_if_fail (E_IS_SOURCE (collection_source));
493
494         mail_account_source = online_accounts_new_source (extension);
495         g_return_if_fail (E_IS_SOURCE (mail_account_source));
496
497         mail_identity_source = online_accounts_new_source (extension);
498         g_return_if_fail (E_IS_SOURCE (mail_identity_source));
499
500         mail_transport_source = online_accounts_new_source (extension);
501         g_return_if_fail (E_IS_SOURCE (mail_transport_source));
502
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);
508
509         /* Give the factory first crack at mail configuration. */
510         e_collection_backend_factory_prepare_mail (
511                 E_COLLECTION_BACKEND_FACTORY (backend_factory),
512                 mail_account_source,
513                 mail_identity_source,
514                 mail_transport_source);
515
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);
525
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);
531
532         goa_account = goa_object_get_account (goa_object);
533         account_id = goa_account_get_id (goa_account);
534
535         g_hash_table_insert (
536                 extension->goa_to_eds,
537                 g_strdup (account_id),
538                 g_strdup (parent_uid));
539
540         g_object_unref (goa_account);
541
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);
546 }
547
548 static void
549 online_accounts_remove_collection (EOnlineAccounts *extension,
550                                    ESource *source)
551 {
552         GError *error = NULL;
553
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);
557
558         if (error != NULL) {
559                 g_warning ("%s: %s", G_STRFUNC, error->message);
560                 g_error_free (error);
561         }
562 }
563
564 static void
565 online_accounts_account_added_cb (GoaClient *goa_client,
566                                   GoaObject *goa_object,
567                                   EOnlineAccounts *extension)
568 {
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;
576
577         server = online_accounts_get_server (extension);
578
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);
582
583         account_id = goa_account_get_id (goa_account);
584         source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
585
586         if (source_uid == NULL && backend_name != NULL)
587                 backend_factory = e_data_factory_ref_backend_factory (
588                         E_DATA_FACTORY (server), backend_name);
589
590         if (backend_factory != NULL) {
591                 online_accounts_create_collection (
592                         extension, backend_factory, goa_object);
593                 g_object_unref (backend_factory);
594         }
595
596         g_object_unref (goa_account);
597 }
598
599 static void
600 online_accounts_account_removed_cb (GoaClient *goa_client,
601                                     GoaObject *goa_object,
602                                     EOnlineAccounts *extension)
603 {
604         ESource *source = NULL;
605         ESourceRegistryServer *server;
606         GoaAccount *goa_account;
607         const gchar *account_id;
608         const gchar *source_uid;
609
610         server = online_accounts_get_server (extension);
611
612         goa_account = goa_object_get_account (goa_object);
613
614         account_id = goa_account_get_id (goa_account);
615         source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
616
617         if (source_uid != NULL)
618                 source = e_source_registry_server_ref_source (
619                         server, source_uid);
620
621         if (source != NULL) {
622                 online_accounts_remove_collection (extension, source);
623                 g_object_unref (source);
624         }
625
626         g_object_unref (goa_account);
627 }
628
629 static gint
630 online_accounts_compare_id (GoaObject *goa_object,
631                             const gchar *target_id)
632 {
633         GoaAccount *goa_account;
634         const gchar *account_id;
635         gint result;
636
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);
641
642         return result;
643 }
644
645 static void
646 online_accounts_populate_accounts_table (EOnlineAccounts *extension,
647                                          GList *goa_objects)
648 {
649         ESourceRegistryServer *server;
650         GQueue trash = G_QUEUE_INIT;
651         GList *list, *link;
652         const gchar *extension_name;
653
654         server = online_accounts_get_server (extension);
655
656         extension_name = E_SOURCE_EXTENSION_GOA;
657         list = e_source_registry_server_list_sources (server, extension_name);
658
659         for (link = list; link != NULL; link = g_list_next (link)) {
660                 ESource *source;
661                 ESourceGoa *goa_ext;
662                 const gchar *account_id;
663                 const gchar *source_uid;
664                 GList *match;
665
666                 source = E_SOURCE (link->data);
667                 source_uid = e_source_get_uid (source);
668
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);
672
673                 if (account_id == NULL)
674                         continue;
675
676                 /* Verify the GOA account still exists. */
677                 match = g_list_find_custom (
678                         goa_objects, account_id,
679                         (GCompareFunc) online_accounts_compare_id);
680
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. */
684                 if (match != NULL) {
685                         GoaObject *goa_object;
686
687                         g_hash_table_insert (
688                                 extension->goa_to_eds,
689                                 g_strdup (account_id),
690                                 g_strdup (source_uid));
691
692                         goa_object = GOA_OBJECT (match->data);
693                         online_accounts_config_collection (
694                                 extension, source, goa_object);
695                 } else {
696                         g_queue_push_tail (&trash, source);
697                 }
698         }
699
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);
704         }
705
706         g_list_free_full (list, (GDestroyNotify) g_object_unref);
707 }
708
709 static void
710 online_accounts_create_client_cb (GObject *source_object,
711                                   GAsyncResult *result,
712                                   gpointer user_data)
713 {
714         EOnlineAccounts *extension;
715         GoaClient *goa_client;
716         GList *list, *link;
717         GError *error = NULL;
718
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. */
722
723         goa_client = goa_client_new_finish (result, &error);
724
725         if (error != NULL) {
726                 g_warn_if_fail (goa_client == NULL);
727                 g_warning (
728                         "Unable to connect to the GNOME Online "
729                         "Accounts service: %s", error->message);
730                 g_error_free (error);
731                 return;
732         }
733
734         g_return_if_fail (GOA_IS_CLIENT (goa_client));
735
736         /* Should be safe to dereference the EOnlineAccounts now. */
737
738         extension = E_ONLINE_ACCOUNTS (user_data);
739         extension->goa_client = goa_client;  /* takes ownership */
740
741         /* Don't need the GCancellable anymore. */
742         g_object_unref (extension->create_client);
743         extension->create_client = NULL;
744
745         list = goa_client_get_accounts (extension->goa_client);
746
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);
754
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),
759                         extension);
760
761         g_list_free_full (list, (GDestroyNotify) g_object_unref);
762
763         /* Listen for Online Account changes. */
764         g_signal_connect (
765                 extension->goa_client, "account-added",
766                 G_CALLBACK (online_accounts_account_added_cb), extension);
767         g_signal_connect (
768                 extension->goa_client, "account-removed",
769                 G_CALLBACK (online_accounts_account_removed_cb), extension);
770 }
771
772 static void
773 online_accounts_bus_acquired_cb (EDBusServer *server,
774                                  GDBusConnection *connection,
775                                  EOnlineAccounts *extension)
776 {
777         /* Connect to the GNOME Online Accounts service. */
778
779         /* Note we don't reference the extension.  If the
780          * extension gets destroyed before this completes
781          * we cancel the operation from dispose(). */
782         goa_client_new (
783                 extension->create_client,
784                 online_accounts_create_client_cb,
785                 extension);
786 }
787
788 static void
789 online_accounts_dispose (GObject *object)
790 {
791         EOnlineAccounts *extension;
792
793         extension = E_ONLINE_ACCOUNTS (object);
794
795         if (extension->goa_client != NULL) {
796                 g_signal_handlers_disconnect_matched (
797                         extension->goa_client,
798                         G_SIGNAL_MATCH_DATA,
799                         0, 0, NULL, NULL, object);
800                 g_object_unref (extension->goa_client);
801                 extension->goa_client = NULL;
802         }
803
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;
810         }
811
812         /* Chain up to parent's dispose() method. */
813         G_OBJECT_CLASS (e_online_accounts_parent_class)->dispose (object);
814 }
815
816 static void
817 online_accounts_finalize (GObject *object)
818 {
819         EOnlineAccounts *extension;
820
821         extension = E_ONLINE_ACCOUNTS (object);
822
823         g_hash_table_destroy (extension->goa_to_eds);
824
825         /* Chain up to parent's finalize() method. */
826         G_OBJECT_CLASS (e_online_accounts_parent_class)->finalize (object);
827 }
828
829 static void
830 online_accounts_constructed (GObject *object)
831 {
832         EExtension *extension;
833         EExtensible *extensible;
834
835         extension = E_EXTENSION (object);
836         extensible = e_extension_get_extensible (extension);
837
838         /* Wait for the registry service to acquire its well-known
839          * bus name so we don't do anything destructive beforehand. */
840
841         g_signal_connect (
842                 extensible, "bus-acquired",
843                 G_CALLBACK (online_accounts_bus_acquired_cb), extension);
844
845         /* Chain up to parent's constructed() method. */
846         G_OBJECT_CLASS (e_online_accounts_parent_class)->constructed (object);
847 }
848
849 static void
850 e_online_accounts_class_init (EOnlineAccountsClass *class)
851 {
852         GObjectClass *object_class;
853         EExtensionClass *extension_class;
854
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;
859
860         extension_class = E_EXTENSION_CLASS (class);
861         extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
862 }
863
864 static void
865 e_online_accounts_class_finalize (EOnlineAccountsClass *class)
866 {
867 }
868
869 static void
870 e_online_accounts_init (EOnlineAccounts *extension)
871 {
872         /* Used to cancel unfinished goa_client_new(). */
873         extension->create_client = g_cancellable_new ();
874
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);
880 }
881
882 G_MODULE_EXPORT void
883 e_module_load (GTypeModule *type_module)
884 {
885         e_online_accounts_register_type (type_module);
886 }
887
888 G_MODULE_EXPORT void
889 e_module_unload (GTypeModule *type_module)
890 {
891 }
892