online-accounts: Mail sources need to be writable.
[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         EServerSideSource *server_side_source;
432
433         online_accounts_config_oauth (extension, source, goa_object);
434
435         /* XXX Need to defer the network security settings to the
436          *     provider-specific module since "imap-use-tls" tells
437          *     us neither the port number, nor whether to use IMAP
438          *     over SSL versus STARTTLS.  The module will know. */
439
440         /* Clients may change the source by may not remove it. */
441         server_side_source = E_SERVER_SIDE_SOURCE (source);
442         e_server_side_source_set_writable (server_side_source, TRUE);
443         e_server_side_source_set_removable (server_side_source, FALSE);
444 }
445
446 static void
447 online_accounts_config_mail_identity (EOnlineAccounts *extension,
448                                       ESource *source,
449                                       GoaObject *goa_object)
450 {
451         GoaMail *goa_mail;
452         ESourceExtension *source_extension;
453         EServerSideSource *server_side_source;
454         const gchar *extension_name;
455
456         goa_mail = goa_object_get_mail (goa_object);
457         g_return_if_fail (goa_mail != NULL);
458
459         extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
460         source_extension = e_source_get_extension (source, extension_name);
461
462         g_object_bind_property (
463                 goa_mail, "email-address",
464                 source_extension, "address",
465                 G_BINDING_SYNC_CREATE);
466
467         g_object_unref (goa_mail);
468
469         /* Clients may change the source by may not remove it. */
470         server_side_source = E_SERVER_SIDE_SOURCE (source);
471         e_server_side_source_set_writable (server_side_source, TRUE);
472         e_server_side_source_set_removable (server_side_source, FALSE);
473 }
474
475 static void
476 online_accounts_config_mail_transport (EOnlineAccounts *extension,
477                                        ESource *source,
478                                        GoaObject *goa_object)
479 {
480         EServerSideSource *server_side_source;
481
482         online_accounts_config_oauth (extension, source, goa_object);
483
484         /* XXX Need to defer the network security settings to the
485          *     provider-specific module since "smtp-use-tls" tells
486          *     us neither the port number, nor whether to use SMTP
487          *     over SSL versus STARTTLS.  The module will know. */
488
489         /* Clients may change the source by may not remove it. */
490         server_side_source = E_SERVER_SIDE_SOURCE (source);
491         e_server_side_source_set_writable (server_side_source, TRUE);
492         e_server_side_source_set_removable (server_side_source, FALSE);
493 }
494
495 static void
496 online_accounts_create_collection (EOnlineAccounts *extension,
497                                    EBackendFactory *backend_factory,
498                                    GoaObject *goa_object)
499 {
500         GoaAccount *goa_account;
501         ESourceRegistryServer *server;
502         ESource *collection_source;
503         ESource *mail_account_source;
504         ESource *mail_identity_source;
505         ESource *mail_transport_source;
506         const gchar *account_id;
507         const gchar *parent_uid;
508
509         server = online_accounts_get_server (extension);
510
511         collection_source = online_accounts_new_source (extension);
512         g_return_if_fail (E_IS_SOURCE (collection_source));
513
514         mail_account_source = online_accounts_new_source (extension);
515         g_return_if_fail (E_IS_SOURCE (mail_account_source));
516
517         mail_identity_source = online_accounts_new_source (extension);
518         g_return_if_fail (E_IS_SOURCE (mail_identity_source));
519
520         mail_transport_source = online_accounts_new_source (extension);
521         g_return_if_fail (E_IS_SOURCE (mail_transport_source));
522
523         /* Configure parent/child relationships. */
524         parent_uid = e_source_get_uid (collection_source);
525         e_source_set_parent (mail_account_source, parent_uid);
526         e_source_set_parent (mail_identity_source, parent_uid);
527         e_source_set_parent (mail_transport_source, parent_uid);
528
529         /* Give the factory first crack at mail configuration. */
530         e_collection_backend_factory_prepare_mail (
531                 E_COLLECTION_BACKEND_FACTORY (backend_factory),
532                 mail_account_source,
533                 mail_identity_source,
534                 mail_transport_source);
535
536         /* Now it's our turn. */
537         online_accounts_config_collection (
538                 extension, collection_source, goa_object);
539         online_accounts_config_mail_account (
540                 extension, mail_account_source, goa_object);
541         online_accounts_config_mail_identity (
542                 extension, mail_identity_source, goa_object);
543         online_accounts_config_mail_transport (
544                 extension, mail_transport_source, goa_object);
545
546         /* Export the new source collection. */
547         e_source_registry_server_add_source (server, collection_source);
548         e_source_registry_server_add_source (server, mail_account_source);
549         e_source_registry_server_add_source (server, mail_identity_source);
550         e_source_registry_server_add_source (server, mail_transport_source);
551
552         goa_account = goa_object_get_account (goa_object);
553         account_id = goa_account_get_id (goa_account);
554
555         g_hash_table_insert (
556                 extension->goa_to_eds,
557                 g_strdup (account_id),
558                 g_strdup (parent_uid));
559
560         g_object_unref (goa_account);
561
562         g_object_unref (collection_source);
563         g_object_unref (mail_account_source);
564         g_object_unref (mail_identity_source);
565         g_object_unref (mail_transport_source);
566 }
567
568 static void
569 online_accounts_remove_collection (EOnlineAccounts *extension,
570                                    ESource *source)
571 {
572         GError *error = NULL;
573
574         /* This removes the entire subtree rooted at source.
575          * Deletes the corresponding on-disk key files too. */
576         e_source_remove_sync (source, NULL, &error);
577
578         if (error != NULL) {
579                 g_warning ("%s: %s", G_STRFUNC, error->message);
580                 g_error_free (error);
581         }
582 }
583
584 static void
585 online_accounts_account_added_cb (GoaClient *goa_client,
586                                   GoaObject *goa_object,
587                                   EOnlineAccounts *extension)
588 {
589         GoaAccount *goa_account;
590         ESourceRegistryServer *server;
591         EBackendFactory *backend_factory = NULL;
592         const gchar *provider_type;
593         const gchar *backend_name;
594         const gchar *account_id;
595         const gchar *source_uid;
596
597         server = online_accounts_get_server (extension);
598
599         goa_account = goa_object_get_account (goa_object);
600         provider_type = goa_account_get_provider_type (goa_account);
601         backend_name = online_accounts_get_backend_name (provider_type);
602
603         account_id = goa_account_get_id (goa_account);
604         source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
605
606         if (source_uid == NULL && backend_name != NULL)
607                 backend_factory = e_data_factory_ref_backend_factory (
608                         E_DATA_FACTORY (server), backend_name);
609
610         if (backend_factory != NULL) {
611                 online_accounts_create_collection (
612                         extension, backend_factory, goa_object);
613                 g_object_unref (backend_factory);
614         }
615
616         g_object_unref (goa_account);
617 }
618
619 static void
620 online_accounts_account_removed_cb (GoaClient *goa_client,
621                                     GoaObject *goa_object,
622                                     EOnlineAccounts *extension)
623 {
624         ESource *source = NULL;
625         ESourceRegistryServer *server;
626         GoaAccount *goa_account;
627         const gchar *account_id;
628         const gchar *source_uid;
629
630         server = online_accounts_get_server (extension);
631
632         goa_account = goa_object_get_account (goa_object);
633
634         account_id = goa_account_get_id (goa_account);
635         source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
636
637         if (source_uid != NULL)
638                 source = e_source_registry_server_ref_source (
639                         server, source_uid);
640
641         if (source != NULL) {
642                 online_accounts_remove_collection (extension, source);
643                 g_object_unref (source);
644         }
645
646         g_object_unref (goa_account);
647 }
648
649 static gint
650 online_accounts_compare_id (GoaObject *goa_object,
651                             const gchar *target_id)
652 {
653         GoaAccount *goa_account;
654         const gchar *account_id;
655         gint result;
656
657         goa_account = goa_object_get_account (goa_object);
658         account_id = goa_account_get_id (goa_account);
659         result = g_strcmp0 (account_id, target_id);
660         g_object_unref (goa_account);
661
662         return result;
663 }
664
665 static void
666 online_accounts_populate_accounts_table (EOnlineAccounts *extension,
667                                          GList *goa_objects)
668 {
669         ESourceRegistryServer *server;
670         GQueue trash = G_QUEUE_INIT;
671         GList *list, *link;
672         const gchar *extension_name;
673
674         server = online_accounts_get_server (extension);
675
676         extension_name = E_SOURCE_EXTENSION_GOA;
677         list = e_source_registry_server_list_sources (server, extension_name);
678
679         for (link = list; link != NULL; link = g_list_next (link)) {
680                 ESource *source;
681                 ESourceGoa *goa_ext;
682                 const gchar *account_id;
683                 const gchar *source_uid;
684                 GList *match;
685
686                 source = E_SOURCE (link->data);
687                 source_uid = e_source_get_uid (source);
688
689                 extension_name = E_SOURCE_EXTENSION_GOA;
690                 goa_ext = e_source_get_extension (source, extension_name);
691                 account_id = e_source_goa_get_account_id (goa_ext);
692
693                 if (account_id == NULL)
694                         continue;
695
696                 /* Verify the GOA account still exists. */
697                 match = g_list_find_custom (
698                         goa_objects, account_id,
699                         (GCompareFunc) online_accounts_compare_id);
700
701                 /* If a matching GoaObject was found, add its ID
702                  * to our accounts hash table.  Otherwise remove
703                  * the ESource after we finish looping. */
704                 if (match != NULL) {
705                         GoaObject *goa_object;
706
707                         g_hash_table_insert (
708                                 extension->goa_to_eds,
709                                 g_strdup (account_id),
710                                 g_strdup (source_uid));
711
712                         goa_object = GOA_OBJECT (match->data);
713                         online_accounts_config_collection (
714                                 extension, source, goa_object);
715                 } else {
716                         g_queue_push_tail (&trash, source);
717                 }
718         }
719
720         /* Empty the trash. */
721         while (!g_queue_is_empty (&trash)) {
722                 ESource *source = g_queue_pop_head (&trash);
723                 online_accounts_remove_collection (extension, source);
724         }
725
726         g_list_free_full (list, (GDestroyNotify) g_object_unref);
727 }
728
729 static void
730 online_accounts_create_client_cb (GObject *source_object,
731                                   GAsyncResult *result,
732                                   gpointer user_data)
733 {
734         EOnlineAccounts *extension;
735         GoaClient *goa_client;
736         GList *list, *link;
737         GError *error = NULL;
738
739         /* If we get back a G_IO_ERROR_CANCELLED then it means the
740          * EOnlineAccounts is already finalized, so be careful not
741          * to touch it until after we have a valid GoaClient. */
742
743         goa_client = goa_client_new_finish (result, &error);
744
745         if (error != NULL) {
746                 g_warn_if_fail (goa_client == NULL);
747                 g_warning (
748                         "Unable to connect to the GNOME Online "
749                         "Accounts service: %s", error->message);
750                 g_error_free (error);
751                 return;
752         }
753
754         g_return_if_fail (GOA_IS_CLIENT (goa_client));
755
756         /* Should be safe to dereference the EOnlineAccounts now. */
757
758         extension = E_ONLINE_ACCOUNTS (user_data);
759         extension->goa_client = goa_client;  /* takes ownership */
760
761         /* Don't need the GCancellable anymore. */
762         g_object_unref (extension->create_client);
763         extension->create_client = NULL;
764
765         list = goa_client_get_accounts (extension->goa_client);
766
767         /* This populates a hash table of GOA ID -> ESource UID strings by
768          * searching through available data sources for ones with a "GNOME
769          * Online Accounts" extension.  If such an extension is found, but
770          * no corresponding GoaAccount (presumably meaning the GOA account
771          * was somehow deleted between E-D-S sessions) then the ESource in
772          * which the extension was found gets deleted. */
773         online_accounts_populate_accounts_table (extension, list);
774
775         for (link = list; link != NULL; link = g_list_next (link))
776                 online_accounts_account_added_cb (
777                         extension->goa_client,
778                         GOA_OBJECT (link->data),
779                         extension);
780
781         g_list_free_full (list, (GDestroyNotify) g_object_unref);
782
783         /* Listen for Online Account changes. */
784         g_signal_connect (
785                 extension->goa_client, "account-added",
786                 G_CALLBACK (online_accounts_account_added_cb), extension);
787         g_signal_connect (
788                 extension->goa_client, "account-removed",
789                 G_CALLBACK (online_accounts_account_removed_cb), extension);
790 }
791
792 static void
793 online_accounts_bus_acquired_cb (EDBusServer *server,
794                                  GDBusConnection *connection,
795                                  EOnlineAccounts *extension)
796 {
797         /* Connect to the GNOME Online Accounts service. */
798
799         /* Note we don't reference the extension.  If the
800          * extension gets destroyed before this completes
801          * we cancel the operation from dispose(). */
802         goa_client_new (
803                 extension->create_client,
804                 online_accounts_create_client_cb,
805                 extension);
806 }
807
808 static void
809 online_accounts_dispose (GObject *object)
810 {
811         EOnlineAccounts *extension;
812
813         extension = E_ONLINE_ACCOUNTS (object);
814
815         if (extension->goa_client != NULL) {
816                 g_signal_handlers_disconnect_matched (
817                         extension->goa_client,
818                         G_SIGNAL_MATCH_DATA,
819                         0, 0, NULL, NULL, object);
820                 g_object_unref (extension->goa_client);
821                 extension->goa_client = NULL;
822         }
823
824         /* This cancels goa_client_new() in case it still
825          * hasn't completed.  We're no longer interested. */
826         if (extension->create_client != NULL) {
827                 g_cancellable_cancel (extension->create_client);
828                 g_object_unref (extension->create_client);
829                 extension->create_client = NULL;
830         }
831
832         /* Chain up to parent's dispose() method. */
833         G_OBJECT_CLASS (e_online_accounts_parent_class)->dispose (object);
834 }
835
836 static void
837 online_accounts_finalize (GObject *object)
838 {
839         EOnlineAccounts *extension;
840
841         extension = E_ONLINE_ACCOUNTS (object);
842
843         g_hash_table_destroy (extension->goa_to_eds);
844
845         /* Chain up to parent's finalize() method. */
846         G_OBJECT_CLASS (e_online_accounts_parent_class)->finalize (object);
847 }
848
849 static void
850 online_accounts_constructed (GObject *object)
851 {
852         EExtension *extension;
853         EExtensible *extensible;
854
855         extension = E_EXTENSION (object);
856         extensible = e_extension_get_extensible (extension);
857
858         /* Wait for the registry service to acquire its well-known
859          * bus name so we don't do anything destructive beforehand. */
860
861         g_signal_connect (
862                 extensible, "bus-acquired",
863                 G_CALLBACK (online_accounts_bus_acquired_cb), extension);
864
865         /* Chain up to parent's constructed() method. */
866         G_OBJECT_CLASS (e_online_accounts_parent_class)->constructed (object);
867 }
868
869 static void
870 e_online_accounts_class_init (EOnlineAccountsClass *class)
871 {
872         GObjectClass *object_class;
873         EExtensionClass *extension_class;
874
875         object_class = G_OBJECT_CLASS (class);
876         object_class->dispose = online_accounts_dispose;
877         object_class->finalize = online_accounts_finalize;
878         object_class->constructed = online_accounts_constructed;
879
880         extension_class = E_EXTENSION_CLASS (class);
881         extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
882 }
883
884 static void
885 e_online_accounts_class_finalize (EOnlineAccountsClass *class)
886 {
887 }
888
889 static void
890 e_online_accounts_init (EOnlineAccounts *extension)
891 {
892         /* Used to cancel unfinished goa_client_new(). */
893         extension->create_client = g_cancellable_new ();
894
895         extension->goa_to_eds = g_hash_table_new_full (
896                 (GHashFunc) g_str_hash,
897                 (GEqualFunc) g_str_equal,
898                 (GDestroyNotify) g_free,
899                 (GDestroyNotify) g_free);
900 }
901
902 G_MODULE_EXPORT void
903 e_module_load (GTypeModule *type_module)
904 {
905         e_online_accounts_register_type (type_module);
906 }
907
908 G_MODULE_EXPORT void
909 e_module_unload (GTypeModule *type_module)
910 {
911 }
912