online-accounts: Refresh mail account settings on startup.
[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 <libsecret/secret.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 #define CAMEL_OAUTH2_MECHANISM_NAME "XOAUTH2"
39
40 typedef struct _EOnlineAccounts EOnlineAccounts;
41 typedef struct _EOnlineAccountsClass EOnlineAccountsClass;
42
43 struct _EOnlineAccounts {
44         EExtension parent;
45
46         GoaClient *goa_client;
47         GCancellable *create_client;
48
49         /* GoaAccount ID -> ESource UID */
50         GHashTable *goa_to_eds;
51 };
52
53 struct _EOnlineAccountsClass {
54         EExtensionClass parent_class;
55 };
56
57 /* The keyring definintions are copied from e-authentication-session.c */
58
59 #define KEYRING_ITEM_ATTRIBUTE_NAME     "e-source-uid"
60 #define KEYRING_ITEM_DISPLAY_FORMAT     "Evolution Data Source %s"
61
62 #ifdef HAVE_GOA_PASSWORD_BASED
63 /* XXX Probably want to share this with
64  *     evolution-source-registry-migrate-sources.c */
65 static SecretSchema schema = {
66         "org.gnome.Evolution.DataSource",
67         SECRET_SCHEMA_DONT_MATCH_NAME,
68         {
69                 { KEYRING_ITEM_ATTRIBUTE_NAME,
70                   SECRET_SCHEMA_ATTRIBUTE_STRING },
71                 { NULL, 0 }
72         }
73 };
74 #endif /* HAVE_GOA_PASSWORD_BASED */
75
76 /* Module Entry Points */
77 void e_module_load (GTypeModule *type_module);
78 void e_module_unload (GTypeModule *type_module);
79
80 /* Forward Declarations */
81 GType e_online_accounts_get_type (void);
82
83 G_DEFINE_DYNAMIC_TYPE (
84         EOnlineAccounts,
85         e_online_accounts,
86         E_TYPE_EXTENSION)
87
88 static const gchar *
89 online_accounts_get_backend_name (const gchar *goa_provider_type)
90 {
91         const gchar *eds_backend_name = NULL;
92
93         /* This is a mapping between GoaAccount provider types and
94          * ESourceCollection backend names.  It requires knowledge
95          * of other registry modules, possibly even from 3rd party
96          * packages.  No way around it. */
97
98         if (g_strcmp0 (goa_provider_type, "exchange") == 0)
99                 eds_backend_name = "ews";
100
101         if (g_strcmp0 (goa_provider_type, "google") == 0)
102                 eds_backend_name = "google";
103
104         else if (g_strcmp0 (goa_provider_type, "yahoo") == 0)
105                 eds_backend_name = "yahoo";
106
107         return eds_backend_name;
108 }
109
110 static ESourceRegistryServer *
111 online_accounts_get_server (EOnlineAccounts *extension)
112 {
113         EExtensible *extensible;
114
115         extensible = e_extension_get_extensible (E_EXTENSION (extension));
116
117         return E_SOURCE_REGISTRY_SERVER (extensible);
118 }
119
120 static gboolean
121 online_accounts_provider_type_to_backend_name (GBinding *binding,
122                                                const GValue *source_value,
123                                                GValue *target_value,
124                                                gpointer unused)
125 {
126         const gchar *provider_type;
127         const gchar *backend_name;
128
129         provider_type = g_value_get_string (source_value);
130         backend_name = online_accounts_get_backend_name (provider_type);
131         g_return_val_if_fail (backend_name != NULL, FALSE);
132         g_value_set_string (target_value, backend_name);
133
134         return TRUE;
135 }
136
137 static gboolean
138 online_accounts_object_is_non_null (GBinding *binding,
139                                     const GValue *source_value,
140                                     GValue *target_value,
141                                     gpointer unused)
142 {
143         gpointer v_object;
144
145         v_object = g_value_get_object (source_value);
146         g_value_set_boolean (target_value, v_object != NULL);
147
148         return TRUE;
149 }
150
151 static ESource *
152 online_accounts_new_source (EOnlineAccounts *extension)
153 {
154         ESourceRegistryServer *server;
155         ESource *source;
156         GFile *file;
157         GError *error = NULL;
158
159         /* This being a brand new data source, creating the instance
160          * should never fail but we'll check for errors just the same. */
161         server = online_accounts_get_server (extension);
162         file = e_server_side_source_new_user_file (NULL);
163         source = e_server_side_source_new (server, file, &error);
164         g_object_unref (file);
165
166         if (error != NULL) {
167                 g_warn_if_fail (source == NULL);
168                 g_warning ("%s: %s", G_STRFUNC, error->message);
169                 g_error_free (error);
170         }
171
172         return source;
173 }
174
175 static void
176 online_accounts_config_exchange (EOnlineAccounts *extension,
177                                  ESource *source,
178                                  GoaObject *goa_object)
179 {
180 #ifdef HAVE_GOA_PASSWORD_BASED
181         ESourceExtension *source_extension;
182         const gchar *extension_name;
183         gchar *as_url = NULL;
184         gchar *oab_url = NULL;
185         gpointer class;
186         GError *error = NULL;
187
188         if (goa_object_peek_exchange (goa_object) == NULL)
189                 return;
190
191         /* This should force the ESourceCamelEws type to be registered.
192          * It will also tell us if Evolution-EWS is even installed. */
193         class = g_type_class_ref (g_type_from_name ("EEwsBackend"));
194         if (class != NULL) {
195                 g_type_class_unref (class);
196         } else {
197                 g_critical (
198                         "%s: Could not locate EEwsBackendClass. "
199                         "Is Evolution-EWS installed?", G_STRFUNC);
200                 return;
201         }
202
203         /* XXX GNOME Online Accounts already runs autodiscover to test
204          *     the user-entered values but doesn't share the discovered
205          *     URLs.  It only provides us a host name and expects us to
206          *     re-run autodiscover for ourselves.
207          *
208          *     So I've copied a slab of code from GOA which was in turn
209          *     copied from Evolution-EWS which does the autodiscovery.
210          *
211          *     I've already complained to Debarshi Ray about the lack
212          *     of useful info in GOA's Exchange interface so hopefully
213          *     it will someday publish discovered URLs and then we can
214          *     remove this hack. */
215
216         goa_ews_autodiscover_sync (
217                 goa_object, &as_url, &oab_url, NULL, &error);
218
219         if (error != NULL) {
220                 g_warning ("%s: %s", G_STRFUNC, error->message);
221                 g_error_free (error);
222                 return;
223         }
224
225         g_return_if_fail (as_url != NULL);
226         g_return_if_fail (oab_url != NULL);
227
228         /* XXX We don't have direct access to CamelEwsSettings from here
229          *     since it's defined in Evolution-EWS.  But we can find out
230          *     its extension name and set properties by name. */
231
232         extension_name = e_source_camel_get_extension_name ("ews");
233         source_extension = e_source_get_extension (source, extension_name);
234
235         /* This will be NULL if Evolution-EWS is not installed. */
236         if (source_extension != NULL) {
237                 g_object_set (
238                         source_extension,
239                         "hosturl", as_url,
240                         "oaburl", oab_url,
241                         NULL);
242         } else {
243                 g_critical (
244                         "%s: Failed to create [%s] extension",
245                         G_STRFUNC, extension_name);
246         }
247
248         g_free (as_url);
249         g_free (oab_url);
250 #endif /* HAVE_GOA_PASSWORD_BASED */
251 }
252
253 static void
254 online_accounts_config_oauth (EOnlineAccounts *extension,
255                               ESource *source,
256                               GoaObject *goa_object)
257 {
258         ESourceExtension *source_extension;
259         const gchar *extension_name;
260
261         if (goa_object_peek_oauth_based (goa_object) == NULL)
262                 return;
263
264         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
265         source_extension = e_source_get_extension (source, extension_name);
266
267         e_source_authentication_set_method (
268                 E_SOURCE_AUTHENTICATION (source_extension),
269                 CAMEL_OAUTH_MECHANISM_NAME);
270 }
271
272 static void
273 online_accounts_config_oauth2 (EOnlineAccounts *extension,
274                                ESource *source,
275                                GoaObject *goa_object)
276 {
277         ESourceExtension *source_extension;
278         const gchar *extension_name;
279
280         if (goa_object_peek_oauth2_based (goa_object) == NULL)
281                 return;
282
283         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
284         source_extension = e_source_get_extension (source, extension_name);
285
286         e_source_authentication_set_method (
287                 E_SOURCE_AUTHENTICATION (source_extension),
288                 CAMEL_OAUTH2_MECHANISM_NAME);
289 }
290
291 static void
292 online_accounts_config_password (EOnlineAccounts *extension,
293                                  ESource *source,
294                                  GoaObject *goa_object)
295 {
296 #ifdef HAVE_GOA_PASSWORD_BASED
297         GoaAccount *goa_account;
298         GoaPasswordBased *goa_password_based;
299         EAsyncClosure *closure;
300         GAsyncResult *result;
301         const gchar *uid;
302         gchar *arg_id;
303         gchar *display_name;
304         gchar *password = NULL;
305         GError *error = NULL;
306
307         /* If the GNOME Online Account is password-based, we use its
308          * password to seed our own keyring entry for the collection
309          * source which avoids having to special-case authentication
310          * like we do for OAuth.  Plus, if the stored password is no
311          * good we'll prompt for a new one instead of just giving up. */
312
313         goa_password_based = goa_object_get_password_based (goa_object);
314
315         if (goa_password_based == NULL)
316                 return;
317
318         closure = e_async_closure_new ();
319
320         /* XXX The GOA documentation doesn't explain the string
321          *     argument in goa_password_based_get_password() so
322          *     we'll pass in the identity and hope for the best. */
323         goa_account = goa_object_get_account (goa_object);
324         arg_id = goa_account_dup_identity (goa_account);
325         g_object_unref (goa_account);
326
327         goa_password_based_call_get_password (
328                 goa_password_based, arg_id, NULL,
329                 e_async_closure_callback, closure);
330
331         g_free (arg_id);
332
333         result = e_async_closure_wait (closure);
334
335         goa_password_based_call_get_password_finish (
336                 goa_password_based, &password, result, &error);
337
338         if (error != NULL) {
339                 g_warning ("%s: %s", G_STRFUNC, error->message);
340                 g_error_free (error);
341                 goto exit;
342         }
343
344         uid = e_source_get_uid (source);
345         display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
346
347         secret_password_store (
348                 &schema, SECRET_COLLECTION_DEFAULT,
349                 display_name, password, NULL,
350                 e_async_closure_callback, closure,
351                 KEYRING_ITEM_ATTRIBUTE_NAME, uid,
352                 NULL);
353
354         result = e_async_closure_wait (closure);
355
356         secret_password_store_finish (result, &error);
357
358         g_free (display_name);
359
360         /* If we fail to store the password, we'll just end up prompting
361          * for a password like normal.  Annoying, maybe, but not the end
362          * of the world.  Still leave a breadcrumb for debugging though. */
363         if (error != NULL) {
364                 g_warning ("%s: %s", G_STRFUNC, error->message);
365                 g_error_free (error);
366         }
367
368 exit:
369         e_async_closure_free (closure);
370         g_object_unref (goa_password_based);
371 #endif /* HAVE_GOA_PASSWORD_BASED */
372 }
373
374 static void
375 online_accounts_config_collection (EOnlineAccounts *extension,
376                                    ESource *source,
377                                    GoaObject *goa_object)
378 {
379         GoaAccount *goa_account;
380         ESourceExtension *source_extension;
381         const gchar *extension_name;
382
383         goa_account = goa_object_get_account (goa_object);
384
385         g_object_bind_property (
386                 goa_account, "presentation-identity",
387                 source, "display-name",
388                 G_BINDING_SYNC_CREATE);
389
390         extension_name = E_SOURCE_EXTENSION_GOA;
391         source_extension = e_source_get_extension (source, extension_name);
392
393         g_object_bind_property (
394                 goa_account, "id",
395                 source_extension, "account-id",
396                 G_BINDING_SYNC_CREATE);
397
398         extension_name = E_SOURCE_EXTENSION_COLLECTION;
399         source_extension = e_source_get_extension (source, extension_name);
400
401         g_object_bind_property_full (
402                 goa_account, "provider-type",
403                 source_extension, "backend-name",
404                 G_BINDING_SYNC_CREATE,
405                 online_accounts_provider_type_to_backend_name,
406                 NULL,
407                 NULL, (GDestroyNotify) NULL);
408
409         g_object_bind_property (
410                 goa_account, "identity",
411                 source_extension, "identity",
412                 G_BINDING_SYNC_CREATE);
413
414         g_object_bind_property_full (
415                 goa_object, "calendar",
416                 source_extension, "calendar-enabled",
417                 G_BINDING_SYNC_CREATE,
418                 online_accounts_object_is_non_null,
419                 NULL,
420                 NULL, (GDestroyNotify) NULL);
421
422         g_object_bind_property_full (
423                 goa_object, "contacts",
424                 source_extension, "contacts-enabled",
425                 G_BINDING_SYNC_CREATE,
426                 online_accounts_object_is_non_null,
427                 NULL,
428                 NULL, (GDestroyNotify) NULL);
429
430         g_object_bind_property_full (
431                 goa_object, "mail",
432                 source_extension, "mail-enabled",
433                 G_BINDING_SYNC_CREATE,
434                 online_accounts_object_is_non_null,
435                 NULL,
436                 NULL, (GDestroyNotify) NULL);
437
438         g_object_unref (goa_account);
439
440         /* Handle optional GOA interfaces. */
441         online_accounts_config_exchange (extension, source, goa_object);
442         online_accounts_config_password (extension, source, goa_object);
443
444         /* The data source should not be removable by clients. */
445         e_server_side_source_set_removable (
446                 E_SERVER_SIDE_SOURCE (source), FALSE);
447 }
448
449 static void
450 online_accounts_config_mail_account (EOnlineAccounts *extension,
451                                      ESource *source,
452                                      GoaObject *goa_object)
453 {
454         EServerSideSource *server_side_source;
455
456         /* Only one or the other should be present, not both. */
457         online_accounts_config_oauth (extension, source, goa_object);
458         online_accounts_config_oauth2 (extension, source, goa_object);
459
460         /* XXX Need to defer the network security settings to the
461          *     provider-specific module since "imap-use-tls" tells
462          *     us neither the port number, nor whether to use IMAP
463          *     over SSL versus STARTTLS.  The module will know. */
464
465         /* Clients may change the source by may not remove it. */
466         server_side_source = E_SERVER_SIDE_SOURCE (source);
467         e_server_side_source_set_writable (server_side_source, TRUE);
468         e_server_side_source_set_removable (server_side_source, FALSE);
469 }
470
471 static void
472 online_accounts_config_mail_identity (EOnlineAccounts *extension,
473                                       ESource *source,
474                                       GoaObject *goa_object)
475 {
476         GoaMail *goa_mail;
477         ESourceExtension *source_extension;
478         EServerSideSource *server_side_source;
479         const gchar *extension_name;
480
481         goa_mail = goa_object_get_mail (goa_object);
482         g_return_if_fail (goa_mail != NULL);
483
484         extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
485         source_extension = e_source_get_extension (source, extension_name);
486
487         g_object_bind_property (
488                 goa_mail, "email-address",
489                 source_extension, "address",
490                 G_BINDING_SYNC_CREATE);
491
492         g_object_unref (goa_mail);
493
494         /* Clients may change the source by may not remove it. */
495         server_side_source = E_SERVER_SIDE_SOURCE (source);
496         e_server_side_source_set_writable (server_side_source, TRUE);
497         e_server_side_source_set_removable (server_side_source, FALSE);
498 }
499
500 static void
501 online_accounts_config_mail_transport (EOnlineAccounts *extension,
502                                        ESource *source,
503                                        GoaObject *goa_object)
504 {
505         EServerSideSource *server_side_source;
506
507         /* Only one or the other should be present, not both. */
508         online_accounts_config_oauth (extension, source, goa_object);
509         online_accounts_config_oauth2 (extension, source, goa_object);
510
511         /* XXX Need to defer the network security settings to the
512          *     provider-specific module since "smtp-use-tls" tells
513          *     us neither the port number, nor whether to use SMTP
514          *     over SSL versus STARTTLS.  The module will know. */
515
516         /* Clients may change the source by may not remove it. */
517         server_side_source = E_SERVER_SIDE_SOURCE (source);
518         e_server_side_source_set_writable (server_side_source, TRUE);
519         e_server_side_source_set_removable (server_side_source, FALSE);
520 }
521
522 static void
523 online_accounts_config_sources (EOnlineAccounts *extension,
524                                 ESource *source,
525                                 GoaObject *goa_object)
526 {
527         ESourceRegistryServer *server;
528         ECollectionBackend *backend;
529         GList *list, *link;
530
531         /* XXX This function was primarily intended to smooth the
532          *     transition of mail accounts from XOAUTH to XOAUTH2,
533          *     but it may be useful for other types of migration. */
534
535         online_accounts_config_collection (extension, source, goa_object);
536
537         server = online_accounts_get_server (extension);
538         backend = e_source_registry_server_ref_backend (server, source);
539         g_return_if_fail (backend != NULL);
540
541         list = e_collection_backend_list_mail_sources (backend);
542
543         for (link = list; link != NULL; link = g_list_next (link)) {
544                 const gchar *extension_name;
545
546                 source = E_SOURCE (link->data);
547
548                 extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
549                 if (e_source_has_extension (source, extension_name))
550                         online_accounts_config_mail_account (
551                                 extension, source, goa_object);
552
553                 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
554                 if (e_source_has_extension (source, extension_name))
555                         online_accounts_config_mail_identity (
556                                 extension, source, goa_object);
557
558                 extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
559                 if (e_source_has_extension (source, extension_name))
560                         online_accounts_config_mail_transport (
561                                 extension, source, goa_object);
562         }
563
564         g_list_free_full (list, (GDestroyNotify) g_object_unref);
565
566         g_object_unref (backend);
567 }
568
569 static void
570 online_accounts_create_collection (EOnlineAccounts *extension,
571                                    EBackendFactory *backend_factory,
572                                    GoaObject *goa_object)
573 {
574         GoaAccount *goa_account;
575         ESourceRegistryServer *server;
576         ESource *collection_source;
577         ESource *mail_account_source;
578         ESource *mail_identity_source;
579         ESource *mail_transport_source;
580         const gchar *account_id;
581         const gchar *parent_uid;
582
583         server = online_accounts_get_server (extension);
584
585         collection_source = online_accounts_new_source (extension);
586         g_return_if_fail (E_IS_SOURCE (collection_source));
587
588         mail_account_source = online_accounts_new_source (extension);
589         g_return_if_fail (E_IS_SOURCE (mail_account_source));
590
591         mail_identity_source = online_accounts_new_source (extension);
592         g_return_if_fail (E_IS_SOURCE (mail_identity_source));
593
594         mail_transport_source = online_accounts_new_source (extension);
595         g_return_if_fail (E_IS_SOURCE (mail_transport_source));
596
597         /* Configure parent/child relationships. */
598         parent_uid = e_source_get_uid (collection_source);
599         e_source_set_parent (mail_account_source, parent_uid);
600         e_source_set_parent (mail_identity_source, parent_uid);
601         e_source_set_parent (mail_transport_source, parent_uid);
602
603         /* Give the factory first crack at mail configuration. */
604         e_collection_backend_factory_prepare_mail (
605                 E_COLLECTION_BACKEND_FACTORY (backend_factory),
606                 mail_account_source,
607                 mail_identity_source,
608                 mail_transport_source);
609
610         /* Now it's our turn. */
611         online_accounts_config_collection (
612                 extension, collection_source, goa_object);
613         online_accounts_config_mail_account (
614                 extension, mail_account_source, goa_object);
615         online_accounts_config_mail_identity (
616                 extension, mail_identity_source, goa_object);
617         online_accounts_config_mail_transport (
618                 extension, mail_transport_source, goa_object);
619
620         /* Export the new source collection. */
621         e_source_registry_server_add_source (server, collection_source);
622         e_source_registry_server_add_source (server, mail_account_source);
623         e_source_registry_server_add_source (server, mail_identity_source);
624         e_source_registry_server_add_source (server, mail_transport_source);
625
626         goa_account = goa_object_get_account (goa_object);
627         account_id = goa_account_get_id (goa_account);
628
629         g_hash_table_insert (
630                 extension->goa_to_eds,
631                 g_strdup (account_id),
632                 g_strdup (parent_uid));
633
634         g_object_unref (goa_account);
635
636         g_object_unref (collection_source);
637         g_object_unref (mail_account_source);
638         g_object_unref (mail_identity_source);
639         g_object_unref (mail_transport_source);
640 }
641
642 static void
643 online_accounts_remove_collection (EOnlineAccounts *extension,
644                                    ESource *source)
645 {
646         GError *error = NULL;
647
648         /* This removes the entire subtree rooted at source.
649          * Deletes the corresponding on-disk key files too. */
650         e_source_remove_sync (source, NULL, &error);
651
652         if (error != NULL) {
653                 g_warning ("%s: %s", G_STRFUNC, error->message);
654                 g_error_free (error);
655         }
656 }
657
658 static void
659 online_accounts_account_added_cb (GoaClient *goa_client,
660                                   GoaObject *goa_object,
661                                   EOnlineAccounts *extension)
662 {
663         GoaAccount *goa_account;
664         ESourceRegistryServer *server;
665         EBackendFactory *backend_factory = NULL;
666         const gchar *provider_type;
667         const gchar *backend_name;
668         const gchar *account_id;
669         const gchar *source_uid;
670
671         server = online_accounts_get_server (extension);
672
673         goa_account = goa_object_get_account (goa_object);
674         provider_type = goa_account_get_provider_type (goa_account);
675         backend_name = online_accounts_get_backend_name (provider_type);
676
677         account_id = goa_account_get_id (goa_account);
678         source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
679
680         if (source_uid == NULL && backend_name != NULL)
681                 backend_factory = e_data_factory_ref_backend_factory (
682                         E_DATA_FACTORY (server), backend_name);
683
684         if (backend_factory != NULL) {
685                 online_accounts_create_collection (
686                         extension, backend_factory, goa_object);
687                 g_object_unref (backend_factory);
688         }
689
690         g_object_unref (goa_account);
691 }
692
693 static void
694 online_accounts_account_removed_cb (GoaClient *goa_client,
695                                     GoaObject *goa_object,
696                                     EOnlineAccounts *extension)
697 {
698         ESource *source = NULL;
699         ESourceRegistryServer *server;
700         GoaAccount *goa_account;
701         const gchar *account_id;
702         const gchar *source_uid;
703
704         server = online_accounts_get_server (extension);
705
706         goa_account = goa_object_get_account (goa_object);
707
708         account_id = goa_account_get_id (goa_account);
709         source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
710
711         if (source_uid != NULL)
712                 source = e_source_registry_server_ref_source (
713                         server, source_uid);
714
715         if (source != NULL) {
716                 online_accounts_remove_collection (extension, source);
717                 g_object_unref (source);
718         }
719
720         g_object_unref (goa_account);
721 }
722
723 static gint
724 online_accounts_compare_id (GoaObject *goa_object,
725                             const gchar *target_id)
726 {
727         GoaAccount *goa_account;
728         const gchar *account_id;
729         gint result;
730
731         goa_account = goa_object_get_account (goa_object);
732         account_id = goa_account_get_id (goa_account);
733         result = g_strcmp0 (account_id, target_id);
734         g_object_unref (goa_account);
735
736         return result;
737 }
738
739 static void
740 online_accounts_populate_accounts_table (EOnlineAccounts *extension,
741                                          GList *goa_objects)
742 {
743         ESourceRegistryServer *server;
744         GQueue trash = G_QUEUE_INIT;
745         GList *list, *link;
746         const gchar *extension_name;
747
748         server = online_accounts_get_server (extension);
749
750         extension_name = E_SOURCE_EXTENSION_GOA;
751         list = e_source_registry_server_list_sources (server, extension_name);
752
753         for (link = list; link != NULL; link = g_list_next (link)) {
754                 ESource *source;
755                 ESourceGoa *goa_ext;
756                 const gchar *account_id;
757                 const gchar *source_uid;
758                 GList *match;
759
760                 source = E_SOURCE (link->data);
761                 source_uid = e_source_get_uid (source);
762
763                 extension_name = E_SOURCE_EXTENSION_GOA;
764                 goa_ext = e_source_get_extension (source, extension_name);
765                 account_id = e_source_goa_get_account_id (goa_ext);
766
767                 if (account_id == NULL)
768                         continue;
769
770                 /* Verify the GOA account still exists. */
771                 match = g_list_find_custom (
772                         goa_objects, account_id,
773                         (GCompareFunc) online_accounts_compare_id);
774
775                 /* If a matching GoaObject was found, add its ID
776                  * to our accounts hash table.  Otherwise remove
777                  * the ESource after we finish looping. */
778                 if (match != NULL) {
779                         GoaObject *goa_object;
780
781                         g_hash_table_insert (
782                                 extension->goa_to_eds,
783                                 g_strdup (account_id),
784                                 g_strdup (source_uid));
785
786                         goa_object = GOA_OBJECT (match->data);
787                         online_accounts_config_sources (
788                                 extension, source, goa_object);
789                 } else {
790                         g_queue_push_tail (&trash, source);
791                 }
792         }
793
794         /* Empty the trash. */
795         while (!g_queue_is_empty (&trash)) {
796                 ESource *source = g_queue_pop_head (&trash);
797                 online_accounts_remove_collection (extension, source);
798         }
799
800         g_list_free_full (list, (GDestroyNotify) g_object_unref);
801 }
802
803 static void
804 online_accounts_create_client_cb (GObject *source_object,
805                                   GAsyncResult *result,
806                                   gpointer user_data)
807 {
808         EOnlineAccounts *extension;
809         GoaClient *goa_client;
810         GList *list, *link;
811         GError *error = NULL;
812
813         /* If we get back a G_IO_ERROR_CANCELLED then it means the
814          * EOnlineAccounts is already finalized, so be careful not
815          * to touch it until after we have a valid GoaClient. */
816
817         goa_client = goa_client_new_finish (result, &error);
818
819         if (error != NULL) {
820                 g_warn_if_fail (goa_client == NULL);
821                 g_warning (
822                         "Unable to connect to the GNOME Online "
823                         "Accounts service: %s", error->message);
824                 g_error_free (error);
825                 return;
826         }
827
828         g_return_if_fail (GOA_IS_CLIENT (goa_client));
829
830         /* Should be safe to dereference the EOnlineAccounts now. */
831
832         extension = E_ONLINE_ACCOUNTS (user_data);
833         extension->goa_client = goa_client;  /* takes ownership */
834
835         /* Don't need the GCancellable anymore. */
836         g_object_unref (extension->create_client);
837         extension->create_client = NULL;
838
839         list = goa_client_get_accounts (extension->goa_client);
840
841         /* This populates a hash table of GOA ID -> ESource UID strings by
842          * searching through available data sources for ones with a "GNOME
843          * Online Accounts" extension.  If such an extension is found, but
844          * no corresponding GoaAccount (presumably meaning the GOA account
845          * was somehow deleted between E-D-S sessions) then the ESource in
846          * which the extension was found gets deleted. */
847         online_accounts_populate_accounts_table (extension, list);
848
849         for (link = list; link != NULL; link = g_list_next (link))
850                 online_accounts_account_added_cb (
851                         extension->goa_client,
852                         GOA_OBJECT (link->data),
853                         extension);
854
855         g_list_free_full (list, (GDestroyNotify) g_object_unref);
856
857         /* Listen for Online Account changes. */
858         g_signal_connect (
859                 extension->goa_client, "account-added",
860                 G_CALLBACK (online_accounts_account_added_cb), extension);
861         g_signal_connect (
862                 extension->goa_client, "account-removed",
863                 G_CALLBACK (online_accounts_account_removed_cb), extension);
864 }
865
866 static void
867 online_accounts_bus_acquired_cb (EDBusServer *server,
868                                  GDBusConnection *connection,
869                                  EOnlineAccounts *extension)
870 {
871         /* Connect to the GNOME Online Accounts service. */
872
873         /* Note we don't reference the extension.  If the
874          * extension gets destroyed before this completes
875          * we cancel the operation from dispose(). */
876         goa_client_new (
877                 extension->create_client,
878                 online_accounts_create_client_cb,
879                 extension);
880 }
881
882 static void
883 online_accounts_dispose (GObject *object)
884 {
885         EOnlineAccounts *extension;
886
887         extension = E_ONLINE_ACCOUNTS (object);
888
889         if (extension->goa_client != NULL) {
890                 g_signal_handlers_disconnect_matched (
891                         extension->goa_client,
892                         G_SIGNAL_MATCH_DATA,
893                         0, 0, NULL, NULL, object);
894                 g_object_unref (extension->goa_client);
895                 extension->goa_client = NULL;
896         }
897
898         /* This cancels goa_client_new() in case it still
899          * hasn't completed.  We're no longer interested. */
900         if (extension->create_client != NULL) {
901                 g_cancellable_cancel (extension->create_client);
902                 g_object_unref (extension->create_client);
903                 extension->create_client = NULL;
904         }
905
906         /* Chain up to parent's dispose() method. */
907         G_OBJECT_CLASS (e_online_accounts_parent_class)->dispose (object);
908 }
909
910 static void
911 online_accounts_finalize (GObject *object)
912 {
913         EOnlineAccounts *extension;
914
915         extension = E_ONLINE_ACCOUNTS (object);
916
917         g_hash_table_destroy (extension->goa_to_eds);
918
919         /* Chain up to parent's finalize() method. */
920         G_OBJECT_CLASS (e_online_accounts_parent_class)->finalize (object);
921 }
922
923 static void
924 online_accounts_constructed (GObject *object)
925 {
926         EExtension *extension;
927         EExtensible *extensible;
928
929         extension = E_EXTENSION (object);
930         extensible = e_extension_get_extensible (extension);
931
932         /* Wait for the registry service to acquire its well-known
933          * bus name so we don't do anything destructive beforehand. */
934
935         g_signal_connect (
936                 extensible, "bus-acquired",
937                 G_CALLBACK (online_accounts_bus_acquired_cb), extension);
938
939         /* Chain up to parent's constructed() method. */
940         G_OBJECT_CLASS (e_online_accounts_parent_class)->constructed (object);
941 }
942
943 static void
944 e_online_accounts_class_init (EOnlineAccountsClass *class)
945 {
946         GObjectClass *object_class;
947         EExtensionClass *extension_class;
948
949         object_class = G_OBJECT_CLASS (class);
950         object_class->dispose = online_accounts_dispose;
951         object_class->finalize = online_accounts_finalize;
952         object_class->constructed = online_accounts_constructed;
953
954         extension_class = E_EXTENSION_CLASS (class);
955         extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
956 }
957
958 static void
959 e_online_accounts_class_finalize (EOnlineAccountsClass *class)
960 {
961 }
962
963 static void
964 e_online_accounts_init (EOnlineAccounts *extension)
965 {
966         /* Used to cancel unfinished goa_client_new(). */
967         extension->create_client = g_cancellable_new ();
968
969         extension->goa_to_eds = g_hash_table_new_full (
970                 (GHashFunc) g_str_hash,
971                 (GEqualFunc) g_str_equal,
972                 (GDestroyNotify) g_free,
973                 (GDestroyNotify) g_free);
974 }
975
976 G_MODULE_EXPORT void
977 e_module_load (GTypeModule *type_module)
978 {
979         e_online_accounts_register_type (type_module);
980 }
981
982 G_MODULE_EXPORT void
983 e_module_unload (GTypeModule *type_module)
984 {
985 }
986