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