Use e_backend_authenticate_sync() in backends.
[platform/upstream/evolution-data-server.git] / addressbook / backends / google / e-book-backend-google.c
1 /* e-book-backend-google.c - Google contact backendy.
2  *
3  * Copyright (C) 2008 Joergen Scheibengruber
4  * Copyright (C) 2010, 2011 Philip Withnall
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation; either version 2.1 of the License, or (at your
9  * option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  * Author: Joergen Scheibengruber <joergen.scheibengruber AT googlemail.com>
21  *         Philip Withnall <philip@tecnocode.co.uk>
22  */
23
24 #include <config.h>
25 #include <string.h>
26 #include <errno.h>
27
28 #include <glib/gi18n-lib.h>
29 #include <gdata/gdata.h>
30
31 #include "e-book-backend-google.h"
32 #include "e-book-google-utils.h"
33
34 #ifdef HAVE_GOA
35 #include "e-gdata-goa-authorizer.h"
36 #endif
37
38 #define E_BOOK_BACKEND_GOOGLE_GET_PRIVATE(obj) \
39         (G_TYPE_INSTANCE_GET_PRIVATE \
40         ((obj), E_TYPE_BOOK_BACKEND_GOOGLE, EBookBackendGooglePrivate))
41
42 #define CLIENT_ID "evolution-client-0.1.0"
43
44 #define URI_GET_CONTACTS "https://www.google.com/m8/feeds/contacts/default/full"
45
46 #define EDB_ERROR(_code) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, NULL)
47 #define EDB_ERROR_EX(_code, _msg) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, _msg)
48
49 /* Forward Declarations */
50 static void     e_book_backend_google_source_authenticator_init
51                                 (ESourceAuthenticatorInterface *interface);
52
53 G_DEFINE_TYPE_WITH_CODE (
54         EBookBackendGoogle,
55         e_book_backend_google,
56         E_TYPE_BOOK_BACKEND,
57         G_IMPLEMENT_INTERFACE (
58                 E_TYPE_SOURCE_AUTHENTICATOR,
59                 e_book_backend_google_source_authenticator_init))
60
61 struct _EBookBackendGooglePrivate {
62         GList *bookviews;
63
64         EBookBackendCache *cache;
65
66         /* Mapping from group ID to (human readable) group name */
67         GHashTable *groups_by_id;
68         /* Mapping from (human readable) group name to group ID */
69         GHashTable *groups_by_name;
70         /* Mapping system_group_id to entry ID */
71         GHashTable *system_groups_by_id;
72         /* Mapping entry ID to system_group_id */
73         GHashTable *system_groups_by_entry_id;
74         /* Time when the groups were last queried */
75         GTimeVal last_groups_update;
76
77         GDataAuthorizer *authorizer;
78         GDataService *service;
79         EProxy *proxy;
80         guint refresh_interval;
81
82         /* If views are open we will send out signals in an idle_handler */
83         guint idle_id;
84
85         guint refresh_id;
86
87         /* Map of active opids to GCancellables */
88         GHashTable *cancellables;
89 };
90
91 gboolean __e_book_backend_google_debug__;
92 #define __debug__(...) (__e_book_backend_google_debug__ ? g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, __VA_ARGS__) : (void) 0)
93
94 static void data_book_error_from_gdata_error (GError **dest_err, const GError *error);
95
96 static void
97 migrate_cache (EBookBackendCache *cache)
98 {
99         const gchar *version;
100         const gchar *version_key = "book-cache-version";
101
102         g_return_if_fail (cache != NULL);
103
104         version = e_file_cache_get_object (E_FILE_CACHE (cache), version_key);
105         if (!version || atoi (version) < 1) {
106                 /* not versioned yet, dump the cache and reload it from a server */
107                 e_file_cache_clean (E_FILE_CACHE (cache));
108                 e_file_cache_add_object (E_FILE_CACHE (cache), version_key, "1");
109         }
110 }
111
112 static void
113 cache_init (EBookBackend *backend)
114 {
115         EBookBackendGooglePrivate *priv;
116         const gchar *cache_dir;
117         gchar *filename;
118
119         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
120
121         cache_dir = e_book_backend_get_cache_dir (backend);
122         filename = g_build_filename (cache_dir, "cache.xml", NULL);
123         priv->cache = e_book_backend_cache_new (filename);
124         g_free (filename);
125
126         migrate_cache (priv->cache);
127 }
128
129 static EContact *
130 cache_add_contact (EBookBackend *backend,
131                    GDataEntry *entry)
132 {
133         EBookBackendGooglePrivate *priv;
134         EContact *contact;
135
136         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
137
138         contact = e_contact_new_from_gdata_entry (entry, priv->groups_by_id, priv->system_groups_by_entry_id);
139         e_contact_add_gdata_entry_xml (contact, entry);
140         e_book_backend_cache_add_contact (priv->cache, contact);
141         e_contact_remove_gdata_entry_xml (contact);
142
143         return contact;
144 }
145
146 static gboolean
147 cache_remove_contact (EBookBackend *backend,
148                       const gchar *uid)
149 {
150         EBookBackendGooglePrivate *priv;
151
152         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
153
154         return e_book_backend_cache_remove_contact (priv->cache, uid);
155 }
156
157 static gboolean
158 cache_has_contact (EBookBackend *backend,
159                    const gchar *uid)
160 {
161         EBookBackendGooglePrivate *priv;
162
163         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
164
165         return e_book_backend_cache_check_contact (priv->cache, uid);
166 }
167
168 static EContact *
169 cache_get_contact (EBookBackend *backend,
170                    const gchar *uid,
171                    GDataEntry **entry)
172 {
173         EBookBackendGooglePrivate *priv;
174         EContact *contact;
175
176         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
177
178         contact = e_book_backend_cache_get_contact (priv->cache, uid);
179         if (contact) {
180                 if (entry) {
181                         const gchar *entry_xml, *edit_uri = NULL;
182
183                         entry_xml = e_contact_get_gdata_entry_xml (contact, &edit_uri);
184                         *entry = GDATA_ENTRY (gdata_parsable_new_from_xml (GDATA_TYPE_CONTACTS_CONTACT, entry_xml, -1, NULL));
185
186                         if (*entry) {
187                                 GDataLink *edit_link = gdata_link_new (edit_uri, GDATA_LINK_EDIT);
188                                 gdata_entry_add_link (*entry, edit_link);
189                                 g_object_unref (edit_link);
190                         }
191                 }
192
193                 e_contact_remove_gdata_entry_xml (contact);
194         }
195
196         return contact;
197 }
198
199 static GList *
200 cache_get_contacts (EBookBackend *backend)
201 {
202         EBookBackendGooglePrivate *priv;
203         GList *contacts, *iter;
204
205         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
206
207         contacts = e_book_backend_cache_get_contacts (priv->cache, "(contains \"x-evolution-any-field\" \"\")");
208         for (iter = contacts; iter; iter = iter->next)
209                 e_contact_remove_gdata_entry_xml (iter->data);
210
211         return contacts;
212 }
213
214 static void
215 cache_freeze (EBookBackend *backend)
216 {
217         EBookBackendGooglePrivate *priv;
218
219         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
220
221         e_file_cache_freeze_changes (E_FILE_CACHE (priv->cache));
222 }
223
224 static void
225 cache_thaw (EBookBackend *backend)
226 {
227         EBookBackendGooglePrivate *priv;
228
229         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
230
231         e_file_cache_thaw_changes (E_FILE_CACHE (priv->cache));
232 }
233
234 static gchar *
235 cache_get_last_update (EBookBackend *backend)
236 {
237         EBookBackendGooglePrivate *priv;
238
239         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
240
241         return e_book_backend_cache_get_time (priv->cache);
242 }
243
244 static gboolean
245 cache_get_last_update_tv (EBookBackend *backend,
246                           GTimeVal *tv)
247 {
248         EBookBackendGooglePrivate *priv;
249         gchar *last_update;
250         gint rv;
251
252         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
253
254         last_update = e_book_backend_cache_get_time (priv->cache);
255         rv = last_update ? g_time_val_from_iso8601 (last_update, tv) : FALSE;
256         g_free (last_update);
257
258         return rv;
259 }
260
261 static void
262 cache_set_last_update (EBookBackend *backend,
263                        GTimeVal *tv)
264 {
265         EBookBackendGooglePrivate *priv;
266         gchar *_time;
267
268         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
269
270         _time = g_time_val_to_iso8601 (tv);
271         e_book_backend_cache_set_time (priv->cache, _time);
272         g_free (_time);
273 }
274
275 static gboolean
276 cache_needs_update (EBookBackend *backend,
277                     guint *remaining_secs)
278 {
279         EBookBackendGooglePrivate *priv;
280         GTimeVal last, current;
281         guint diff;
282         gboolean rv;
283
284         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
285
286         if (remaining_secs)
287                 *remaining_secs = G_MAXUINT;
288
289         /* We never want to update in offline mode */
290         if (!e_backend_get_online (E_BACKEND (backend)))
291                 return FALSE;
292
293         rv = cache_get_last_update_tv (backend, &last);
294
295         if (!rv)
296                 return TRUE;
297
298         g_get_current_time (&current);
299         if (last.tv_sec > current.tv_sec) {
300                 g_warning ("last update is in the feature?");
301
302                 /* Do an update so we can fix this */
303                 return TRUE;
304         }
305         diff = current.tv_sec - last.tv_sec;
306
307         if (diff >= priv->refresh_interval)
308                 return TRUE;
309
310         if (remaining_secs)
311                 *remaining_secs = priv->refresh_interval - diff;
312
313         __debug__ ("No update needed. Next update needed in %d secs", priv->refresh_interval - diff);
314
315         return FALSE;
316 }
317
318 static gboolean
319 backend_is_authorized (EBookBackend *backend)
320 {
321         EBookBackendGooglePrivate *priv;
322
323         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
324
325         if (priv->service == NULL)
326                 return FALSE;
327
328 #ifdef HAVE_GOA
329         /* If we're using OAuth tokens, then as far as the backend
330          * is concerned it's always authorized.  The GDataAuthorizer
331          * will take care of everything in the background without
332          * bothering clients with "auth-required" signals. */
333         if (E_IS_GDATA_GOA_AUTHORIZER (priv->authorizer))
334                 return TRUE;
335 #endif
336
337         return gdata_service_is_authorized (priv->service);
338 }
339
340 static void
341 on_contact_added (EBookBackend *backend,
342                   EContact *contact)
343 {
344         EBookBackendGooglePrivate *priv;
345         GList *iter;
346
347         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
348
349         for (iter = priv->bookviews; iter; iter = iter->next)
350                 e_data_book_view_notify_update (E_DATA_BOOK_VIEW (iter->data), g_object_ref (contact));
351 }
352
353 static void
354 on_contact_removed (EBookBackend *backend,
355                     const gchar *uid)
356 {
357         EBookBackendGooglePrivate *priv;
358         GList *iter;
359
360         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
361
362         for (iter = priv->bookviews; iter; iter = iter->next)
363                 e_data_book_view_notify_remove (E_DATA_BOOK_VIEW (iter->data), g_strdup (uid));
364 }
365
366 static void
367 on_contact_changed (EBookBackend *backend,
368                     EContact *contact)
369 {
370         EBookBackendGooglePrivate *priv;
371         GList *iter;
372
373         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
374
375         for (iter = priv->bookviews; iter; iter = iter->next)
376                 e_data_book_view_notify_update (E_DATA_BOOK_VIEW (iter->data), g_object_ref (contact));
377 }
378
379 static GCancellable *
380 start_operation (EBookBackend *backend,
381                  guint32 opid,
382                  GCancellable *cancellable,
383                  const gchar *message)
384 {
385         EBookBackendGooglePrivate *priv;
386         GList *iter;
387
388         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
389
390         /* Insert the operation into the set of active cancellable operations */
391         if (cancellable)
392                 g_object_ref (cancellable);
393         else
394                 cancellable = g_cancellable_new ();
395         g_hash_table_insert (priv->cancellables, GUINT_TO_POINTER (opid), g_object_ref (cancellable));
396
397         /* Send out a status message to each view */
398         for (iter = priv->bookviews; iter; iter = iter->next)
399                 e_data_book_view_notify_progress (E_DATA_BOOK_VIEW (iter->data), -1, message);
400
401         return cancellable;
402 }
403
404 static void
405 finish_operation (EBookBackend *backend,
406                   guint32 opid,
407                   const GError *gdata_error)
408 {
409         EBookBackendGooglePrivate *priv;
410         GError *book_error = NULL;
411
412         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
413
414         if (gdata_error != NULL) {
415                 data_book_error_from_gdata_error (&book_error, gdata_error);
416                 __debug__ ("Book view query failed: %s", book_error->message);
417         }
418
419         if (g_hash_table_remove (priv->cancellables, GUINT_TO_POINTER (opid))) {
420                 GList *iter;
421
422                 /* Send out a status message to each view */
423                 for (iter = priv->bookviews; iter; iter = iter->next)
424                         e_data_book_view_notify_complete (E_DATA_BOOK_VIEW (iter->data), book_error);
425         }
426
427         g_clear_error (&book_error);
428 }
429
430 static void
431 process_contact_finish (EBookBackend *backend,
432                         GDataEntry *entry)
433 {
434         EContact *new_contact;
435         gboolean was_cached;
436
437         __debug__ (G_STRFUNC);
438
439         was_cached = cache_has_contact (backend, gdata_entry_get_id (entry));
440         new_contact = cache_add_contact (backend, entry);
441
442         if (was_cached == TRUE) {
443                 on_contact_changed (backend, new_contact);
444         } else {
445                 on_contact_added (backend, new_contact);
446         }
447
448         g_object_unref (new_contact);
449 }
450
451 typedef struct {
452         EBookBackend *backend;
453         GCancellable *cancellable;
454         GError *gdata_error;
455
456         /* These two don't need locking; they're only accessed from the main thread. */
457         gboolean update_complete;
458         guint num_contacts_pending_photos;
459 } GetContactsData;
460
461 static void
462 check_get_new_contacts_finished (GetContactsData *data)
463 {
464         __debug__ (G_STRFUNC);
465
466         /* Are we finished yet? */
467         if (data->update_complete == FALSE || data->num_contacts_pending_photos > 0) {
468                 __debug__ ("Bailing from check_get_new_contacts_finished(): update_complete: %u, num_contacts_pending_photos: %u, data: %p",
469                            data->update_complete, data->num_contacts_pending_photos, data);
470                 return;
471         }
472
473         __debug__ ("Proceeding with check_get_new_contacts_finished() for data: %p.", data);
474
475         finish_operation (data->backend, -1, data->gdata_error);
476
477         /* Tidy up */
478         g_object_unref (data->cancellable);
479         g_object_unref (data->backend);
480         g_clear_error (&data->gdata_error);
481
482         g_slice_free (GetContactsData, data);
483 }
484
485 typedef struct {
486         GetContactsData *parent_data;
487
488         GCancellable *cancellable;
489         gulong cancelled_handle;
490 } PhotoData;
491
492 static void
493 process_contact_photo_cancelled_cb (GCancellable *parent_cancellable,
494                                     GCancellable *photo_cancellable)
495 {
496         __debug__ (G_STRFUNC);
497
498         g_cancellable_cancel (photo_cancellable);
499 }
500
501 static void
502 process_contact_photo_cb (GDataContactsContact *gdata_contact,
503                           GAsyncResult *async_result,
504                           PhotoData *data)
505 {
506         EBookBackend *backend = data->parent_data->backend;
507         guint8 *photo_data = NULL;
508         gsize photo_length;
509         gchar *photo_content_type = NULL;
510         GError *error = NULL;
511
512         __debug__ (G_STRFUNC);
513
514         /* Finish downloading the photo */
515         photo_data = gdata_contacts_contact_get_photo_finish (gdata_contact, async_result, &photo_length, &photo_content_type, &error);
516
517         if (error == NULL) {
518                 EContactPhoto *photo;
519
520                 /* Success! Create an EContactPhoto and store it on the final GDataContactsContact object so it makes it into the cache. */
521                 photo = e_contact_photo_new ();
522                 photo->type = E_CONTACT_PHOTO_TYPE_INLINED;
523                 photo->data.inlined.data = (guchar *) photo_data;
524                 photo->data.inlined.length = photo_length;
525                 photo->data.inlined.mime_type = photo_content_type;
526
527                 g_object_set_data_full (G_OBJECT (gdata_contact), "photo", photo, (GDestroyNotify) e_contact_photo_free);
528
529                 photo_data = NULL;
530                 photo_content_type = NULL;
531         } else {
532                 /* Error. */
533                 __debug__ ("Downloading contact photo for '%s' failed: %s", gdata_entry_get_id (GDATA_ENTRY (gdata_contact)), error->message);
534                 g_error_free (error);
535         }
536
537         process_contact_finish (backend, GDATA_ENTRY (gdata_contact));
538
539         g_free (photo_data);
540         g_free (photo_content_type);
541
542         /* Disconnect from the cancellable. */
543         g_cancellable_disconnect (data->parent_data->cancellable, data->cancelled_handle);
544         g_object_unref (data->cancellable);
545
546         data->parent_data->num_contacts_pending_photos--;
547         check_get_new_contacts_finished (data->parent_data);
548
549         g_slice_free (PhotoData, data);
550 }
551
552 static void
553 process_contact_cb (GDataEntry *entry,
554                     guint entry_key,
555                     guint entry_count,
556                     GetContactsData *data)
557 {
558         EBookBackendGooglePrivate *priv;
559         EBookBackend *backend = data->backend;
560         gboolean is_deleted, is_cached;
561         const gchar *uid;
562
563         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
564
565         __debug__ (G_STRFUNC);
566         uid = gdata_entry_get_id (entry);
567         is_deleted = gdata_contacts_contact_is_deleted (GDATA_CONTACTS_CONTACT (entry));
568
569         is_cached = cache_has_contact (backend, uid);
570         if (is_deleted) {
571                 /* Do we have this item in our cache? */
572                 if (is_cached) {
573                         cache_remove_contact (backend, uid);
574                         on_contact_removed (backend, uid);
575                 }
576         } else {
577                 gchar *old_photo_etag = NULL;
578                 const gchar *new_photo_etag;
579
580                 /* Download the contact's photo first, if the contact's uncached or if the photo's been updated. */
581                 if (is_cached == TRUE) {
582                         EContact *old_contact;
583                         EContactPhoto *photo;
584                         EVCardAttribute *old_attr;
585
586                         old_contact = cache_get_contact (backend, uid, NULL);
587
588                         /* Get the old ETag. */
589                         old_attr = e_vcard_get_attribute (E_VCARD (old_contact), GDATA_PHOTO_ETAG_ATTR);
590                         old_photo_etag = (old_attr != NULL) ? e_vcard_attribute_get_value (old_attr) : NULL;
591
592                         /* Attach the old photo to the new contact. */
593                         photo = e_contact_get (old_contact, E_CONTACT_PHOTO);
594
595                         if (photo != NULL && photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
596                                 g_object_set_data_full (G_OBJECT (entry), "photo", photo, (GDestroyNotify) e_contact_photo_free);
597                         } else if (photo != NULL) {
598                                 e_contact_photo_free (photo);
599                         }
600
601                         g_object_unref (old_contact);
602                 }
603
604                 new_photo_etag = gdata_contacts_contact_get_photo_etag (GDATA_CONTACTS_CONTACT (entry));
605
606                 if ((old_photo_etag == NULL && new_photo_etag != NULL) ||
607                     (old_photo_etag != NULL && new_photo_etag != NULL && strcmp (old_photo_etag, new_photo_etag) != 0)) {
608                         GCancellable *cancellable;
609                         PhotoData *photo_data;
610
611                         photo_data = g_slice_new (PhotoData);
612                         photo_data->parent_data = data;
613
614                         /* Increment the count of contacts whose photos we're waiting for. */
615                         data->num_contacts_pending_photos++;
616
617                         /* Cancel downloading if the get_new_contacts() operation is cancelled. */
618                         cancellable = g_cancellable_new ();
619
620                         photo_data->cancellable = g_object_ref (cancellable);
621                         photo_data->cancelled_handle = g_cancellable_connect (data->cancellable, (GCallback) process_contact_photo_cancelled_cb,
622                                                                               g_object_ref (cancellable), (GDestroyNotify) g_object_unref);
623
624                         /* Download the photo. */
625                         gdata_contacts_contact_get_photo_async (GDATA_CONTACTS_CONTACT (entry),
626                                                                 GDATA_CONTACTS_SERVICE (priv->service), cancellable,
627                                                                 (GAsyncReadyCallback) process_contact_photo_cb, photo_data);
628
629                         g_object_unref (cancellable);
630                         g_free (old_photo_etag);
631
632                         return;
633                 }
634
635                 g_free (old_photo_etag);
636
637                 /* Since we're not downloading a photo, add the contact to the cache now. */
638                 process_contact_finish (backend, entry);
639         }
640 }
641
642 static void
643 get_new_contacts_cb (GDataService *service,
644                      GAsyncResult *result,
645                      GetContactsData *data)
646 {
647         EBookBackend *backend = data->backend;
648         GDataFeed *feed;
649         GError *gdata_error = NULL;
650
651         __debug__ (G_STRFUNC);
652         feed = gdata_service_query_finish (service, result, &gdata_error);
653         if (__e_book_backend_google_debug__ && feed) {
654                 GList *entries = gdata_feed_get_entries (feed);
655                 __debug__ ("Feed has %d entries", g_list_length (entries));
656         }
657
658         if (feed != NULL)
659                 g_object_unref (feed);
660
661         if (!gdata_error) {
662                 /* Finish updating the cache */
663                 GTimeVal current_time;
664                 g_get_current_time (&current_time);
665                 cache_set_last_update (backend, &current_time);
666         }
667
668         /* Thaw the cache again */
669         cache_thaw (backend);
670
671         /* Note: The operation's only marked as finished when all the
672          * process_contact_photo_cb() callbacks have been called as well. */
673         data->update_complete = TRUE;
674         data->gdata_error = gdata_error;
675         check_get_new_contacts_finished (data);
676 }
677
678 static void
679 get_new_contacts (EBookBackend *backend)
680 {
681         EBookBackendGooglePrivate *priv;
682         gchar *last_updated;
683         GTimeVal updated;
684         GDataQuery *query;
685         GCancellable *cancellable;
686         GetContactsData *data;
687
688         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
689
690         __debug__ (G_STRFUNC);
691         g_return_if_fail (backend_is_authorized (backend));
692
693         /* Sort out update times */
694         last_updated = cache_get_last_update (backend);
695         g_assert (last_updated == NULL || g_time_val_from_iso8601 (last_updated, &updated) == TRUE);
696         g_free (last_updated);
697
698         /* Prevent the cache writing each change to disk individually (thawed in get_new_contacts_cb()) */
699         cache_freeze (backend);
700
701         /* Build our query */
702         query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
703         if (last_updated) {
704                 gdata_query_set_updated_min (query, updated.tv_sec);
705                 gdata_contacts_query_set_show_deleted (GDATA_CONTACTS_QUERY (query), TRUE);
706         }
707
708         /* Query for new contacts asynchronously */
709         cancellable = start_operation (backend, -1, NULL, _("Querying for updated contacts…"));
710
711         data = g_slice_new (GetContactsData);
712         data->backend = g_object_ref (backend);
713         data->cancellable = g_object_ref (cancellable);
714         data->gdata_error = NULL;
715         data->num_contacts_pending_photos = 0;
716         data->update_complete = FALSE;
717
718         gdata_contacts_service_query_contacts_async (
719                 GDATA_CONTACTS_SERVICE (priv->service),
720                 query,
721                 cancellable,
722                 (GDataQueryProgressCallback) process_contact_cb,
723                 data,
724                 (GDestroyNotify) NULL,
725                 (GAsyncReadyCallback) get_new_contacts_cb,
726                 data);
727
728         g_object_unref (cancellable);
729         g_object_unref (query);
730 }
731
732 static void
733 process_group (GDataEntry *entry,
734                guint entry_key,
735                guint entry_count,
736                EBookBackend *backend)
737 {
738         EBookBackendGooglePrivate *priv;
739         const gchar *uid, *system_group_id;
740         gchar *name;
741         gboolean is_deleted;
742
743         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
744
745         __debug__ (G_STRFUNC);
746         uid = gdata_entry_get_id (entry);
747         name = e_contact_sanitise_google_group_name (entry);
748
749         system_group_id = gdata_contacts_group_get_system_group_id (GDATA_CONTACTS_GROUP (entry));
750         is_deleted = gdata_contacts_group_is_deleted (GDATA_CONTACTS_GROUP (entry));
751
752         if (system_group_id) {
753                 __debug__ ("Processing %ssystem group %s, %s", is_deleted ? "(deleted) " : "", system_group_id, uid);
754
755                 if (is_deleted) {
756                         gchar *entry_id = g_hash_table_lookup (priv->system_groups_by_id, system_group_id);
757                         g_hash_table_remove (priv->system_groups_by_entry_id, entry_id);
758                         g_hash_table_remove (priv->system_groups_by_id, system_group_id);
759                 } else {
760                         gchar *entry_id, *system_group_id_dup;
761
762                         entry_id = e_contact_sanitise_google_group_id (uid);
763                         system_group_id_dup = g_strdup (system_group_id);
764
765                         g_hash_table_replace (priv->system_groups_by_entry_id, entry_id, system_group_id_dup);
766                         g_hash_table_replace (priv->system_groups_by_id, system_group_id_dup, entry_id);
767                 }
768
769                 g_free (name);
770
771                 /* use evolution's names for google's system groups */
772                 name = g_strdup (e_contact_map_google_with_evo_group (system_group_id, TRUE));
773
774                 g_warn_if_fail (name != NULL);
775                 if (!name)
776                         name = g_strdup (system_group_id);
777         }
778
779         if (is_deleted) {
780                 __debug__ ("Processing (deleting) group %s, %s", uid, name);
781                 g_hash_table_remove (priv->groups_by_id, uid);
782                 g_hash_table_remove (priv->groups_by_name, name);
783         } else {
784                 __debug__ ("Processing group %s, %s", uid, name);
785                 g_hash_table_replace (priv->groups_by_id, e_contact_sanitise_google_group_id (uid), g_strdup (name));
786                 g_hash_table_replace (priv->groups_by_name, g_strdup (name), e_contact_sanitise_google_group_id (uid));
787         }
788
789         g_free (name);
790 }
791
792 static void
793 get_groups_cb (GDataService *service,
794                GAsyncResult *result,
795                EBookBackend *backend)
796 {
797         EBookBackendGooglePrivate *priv;
798         GDataFeed *feed;
799         GError *gdata_error = NULL;
800
801         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
802
803         __debug__ (G_STRFUNC);
804         feed = gdata_service_query_finish (service, result, &gdata_error);
805         if (__e_book_backend_google_debug__ && feed) {
806                 GList *entries = gdata_feed_get_entries (feed);
807                 __debug__ ("Group feed has %d entries", g_list_length (entries));
808         }
809
810         if (feed != NULL)
811                 g_object_unref (feed);
812
813         if (!gdata_error) {
814                 /* Update the update time */
815                 g_get_current_time (&(priv->last_groups_update));
816         }
817
818         finish_operation (backend, -2, gdata_error);
819         g_object_unref (backend);
820
821         g_clear_error (&gdata_error);
822 }
823
824 static void
825 get_groups (EBookBackend *backend)
826 {
827         EBookBackendGooglePrivate *priv;
828         GDataQuery *query;
829         GCancellable *cancellable;
830
831         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
832
833         __debug__ (G_STRFUNC);
834         g_return_if_fail (backend_is_authorized (backend));
835
836         /* Build our query */
837         query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
838         if (priv->last_groups_update.tv_sec != 0 || priv->last_groups_update.tv_usec != 0) {
839                 gdata_query_set_updated_min (query, priv->last_groups_update.tv_sec);
840                 gdata_contacts_query_set_show_deleted (GDATA_CONTACTS_QUERY (query), TRUE);
841         }
842
843         g_object_ref (backend);
844
845         /* Run the query asynchronously */
846         cancellable = start_operation (backend, -2, NULL, _("Querying for updated groups…"));
847         gdata_contacts_service_query_groups_async (
848                 GDATA_CONTACTS_SERVICE (priv->service),
849                 query,
850                 cancellable,
851                 (GDataQueryProgressCallback) process_group,
852                 backend,
853                 (GDestroyNotify) NULL,
854                 (GAsyncReadyCallback) get_groups_cb,
855                 backend);
856
857         g_object_unref (cancellable);
858         g_object_unref (query);
859 }
860
861 static void
862 get_groups_sync (EBookBackend *backend,
863                  GCancellable *cancellable)
864 {
865         EBookBackendGooglePrivate *priv;
866         GDataQuery *query;
867         GDataFeed *feed;
868
869         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
870
871         __debug__ (G_STRFUNC);
872         g_return_if_fail (backend_is_authorized (backend));
873
874         /* Build our query, always fetch all of them */
875         query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
876
877         /* Run the query synchronously */
878         feed = gdata_contacts_service_query_groups (
879                 GDATA_CONTACTS_SERVICE (priv->service),
880                 query,
881                 cancellable,
882                 (GDataQueryProgressCallback) process_group,
883                 backend,
884                 NULL);
885
886         if (feed)
887                 g_object_unref (feed);
888
889         g_object_unref (query);
890 }
891
892 static gchar *
893 create_group (EBookBackend *backend,
894               const gchar *category_name,
895               GError **error)
896 {
897         EBookBackendGooglePrivate *priv;
898         GDataEntry *group, *new_group;
899         gchar *uid;
900         const gchar *system_group_id;
901
902         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
903
904         system_group_id = e_contact_map_google_with_evo_group (category_name, FALSE);
905         if (system_group_id) {
906                 const gchar *group_entry_id = g_hash_table_lookup (priv->system_groups_by_id, system_group_id);
907
908                 g_return_val_if_fail (group_entry_id != NULL, NULL);
909
910                 return g_strdup (group_entry_id);
911         }
912
913         group = GDATA_ENTRY (gdata_contacts_group_new (NULL));
914
915         gdata_entry_set_title (group, category_name);
916         __debug__ ("Creating group %s", category_name);
917
918         /* Insert the new group */
919         new_group = GDATA_ENTRY (gdata_contacts_service_insert_group (GDATA_CONTACTS_SERVICE (priv->service), GDATA_CONTACTS_GROUP (group),
920                                                                       NULL, error));
921         g_object_unref (group);
922
923         if (new_group == NULL)
924                 return NULL;
925
926         /* Add the new group to the group mappings */
927         uid = g_strdup (gdata_entry_get_id (new_group));
928         g_hash_table_replace (priv->groups_by_id, e_contact_sanitise_google_group_id (uid), g_strdup (category_name));
929         g_hash_table_replace (priv->groups_by_name, g_strdup (category_name), e_contact_sanitise_google_group_id (uid));
930         g_object_unref (new_group);
931
932         __debug__ ("...got UID %s", uid);
933
934         return uid;
935 }
936
937 static gchar *
938 _create_group (const gchar *category_name,
939                gpointer user_data,
940                GError **error)
941 {
942         return create_group (E_BOOK_BACKEND (user_data), category_name, error);
943 }
944
945 static gboolean cache_refresh_if_needed (EBookBackend *backend);
946
947 static gboolean
948 on_refresh_timeout (EBookBackend *backend)
949 {
950         EBookBackendGooglePrivate *priv;
951
952         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
953
954         __debug__ (G_STRFUNC);
955
956         priv->refresh_id = 0;
957         if (priv->bookviews != NULL)
958                 cache_refresh_if_needed (backend);
959
960         return FALSE;
961 }
962
963 static gboolean
964 cache_refresh_if_needed (EBookBackend *backend)
965 {
966         EBookBackendGooglePrivate *priv;
967         guint remaining_secs;
968         gboolean install_timeout;
969         gboolean is_online;
970
971         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
972
973         __debug__ (G_STRFUNC);
974
975         is_online = e_backend_get_online (E_BACKEND (backend));
976
977         if (!is_online || !backend_is_authorized (backend)) {
978                 __debug__ ("We are not connected to Google%s.", (!is_online) ? " (offline mode)" : "");
979                 return TRUE;
980         }
981
982         install_timeout = (priv->bookviews != NULL && priv->refresh_interval > 0 && 0 == priv->refresh_id);
983
984         if (cache_needs_update (backend, &remaining_secs)) {
985                 /* Update the cache asynchronously and schedule a new timeout */
986                 get_groups (backend);
987                 get_new_contacts (backend);
988                 remaining_secs = priv->refresh_interval;
989         } else if (g_hash_table_size (priv->system_groups_by_id) == 0)
990                 get_groups (backend);
991
992         if (install_timeout) {
993                 __debug__ ("Installing timeout with %d seconds", remaining_secs);
994                 priv->refresh_id = g_timeout_add_seconds (remaining_secs, (GSourceFunc) on_refresh_timeout, backend);
995         }
996
997         return TRUE;
998 }
999
1000 static void
1001 proxy_settings_changed (EProxy *proxy,
1002                         EBookBackend *backend)
1003 {
1004         EBookBackendGooglePrivate *priv;
1005         SoupURI *proxy_uri = NULL;
1006
1007         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1008
1009         if (!priv || !priv->service)
1010                 return;
1011
1012         /* use proxy if necessary */
1013         if (e_proxy_require_proxy_for_uri (proxy, URI_GET_CONTACTS))
1014                 proxy_uri = e_proxy_peek_uri_for (proxy, URI_GET_CONTACTS);
1015         gdata_service_set_proxy_uri (priv->service, proxy_uri);
1016 }
1017
1018 static gboolean
1019 request_authorization (EBookBackend *backend,
1020                        GCancellable *cancellable,
1021                        GError **error)
1022 {
1023         EBookBackendGooglePrivate *priv;
1024
1025         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1026
1027         /* Make sure we have the GDataService configured
1028          * before requesting authorization. */
1029
1030 #ifdef HAVE_GOA
1031         /* If this is associated with a GNOME Online Account,
1032          * use OAuth authentication instead of ClientLogin. */
1033         if (priv->authorizer == NULL) {
1034                 EGDataGoaAuthorizer *authorizer;
1035                 GoaObject *goa_object;
1036
1037                 goa_object = g_object_get_data (
1038                         G_OBJECT (backend), "GNOME Online Account");
1039                 if (GOA_IS_OBJECT (goa_object)) {
1040                         authorizer = e_gdata_goa_authorizer_new (goa_object);
1041                         priv->authorizer = GDATA_AUTHORIZER (authorizer);
1042                 }
1043         }
1044 #endif
1045
1046         if (priv->authorizer == NULL) {
1047                 GDataClientLoginAuthorizer *authorizer;
1048
1049                 authorizer = gdata_client_login_authorizer_new (
1050                         CLIENT_ID, GDATA_TYPE_CONTACTS_SERVICE);
1051                 priv->authorizer = GDATA_AUTHORIZER (authorizer);
1052         }
1053
1054         if (priv->service == NULL) {
1055                 GDataContactsService *contacts_service;
1056
1057                 contacts_service =
1058                         gdata_contacts_service_new (priv->authorizer);
1059                 priv->service = GDATA_SERVICE (contacts_service);
1060                 proxy_settings_changed (priv->proxy, backend);
1061         }
1062
1063 #ifdef HAVE_GOA
1064         /* If we're using OAuth tokens, then as far as the backend
1065          * is concerned it's always authorized.  The GDataAuthorizer
1066          * will take care of everything in the background. */
1067         if (E_IS_GDATA_GOA_AUTHORIZER (priv->authorizer))
1068                 return TRUE;
1069 #endif
1070
1071         /* Otherwise it's up to us to obtain a login secret. */
1072         return e_backend_authenticate_sync (
1073                 E_BACKEND (backend),
1074                 E_SOURCE_AUTHENTICATOR (backend),
1075                 cancellable, error);
1076 }
1077
1078 typedef struct {
1079         EBookBackend *backend;
1080         EDataBook *book;
1081         guint32 opid;
1082         GCancellable *cancellable;
1083         GDataContactsContact *new_contact;
1084         EContactPhoto *photo;
1085 } CreateContactData;
1086
1087 static void
1088 create_contact_finish (CreateContactData *data,
1089                        GDataContactsContact *new_contact,
1090                        const GError *gdata_error)
1091 {
1092         __debug__ (G_STRFUNC);
1093
1094         if (gdata_error == NULL) {
1095                 /* Add the new contact to the cache. If uploading the photo was successful, the photo's data is stored on the contact as the "photo"
1096                  * key, which the cache will pick up and store. */
1097                 EContact *e_contact;
1098                 GSList added_contacts = {NULL,};
1099                 e_contact = cache_add_contact (data->backend, GDATA_ENTRY (new_contact));
1100
1101                 added_contacts.data = e_contact;
1102                 e_data_book_respond_create_contacts (data->book, data->opid, NULL, &added_contacts);
1103                 g_object_unref (e_contact);
1104         } else {
1105                 GError *book_error = NULL;
1106
1107                 /* Report the error. */
1108                 data_book_error_from_gdata_error (&book_error, gdata_error);
1109                 e_data_book_respond_create_contacts (data->book, data->opid, book_error, NULL);
1110         }
1111
1112         finish_operation (data->backend, data->opid, gdata_error);
1113
1114         if (data->photo != NULL) {
1115                 e_contact_photo_free (data->photo);
1116         }
1117
1118         if (data->new_contact != NULL) {
1119                 g_object_unref (data->new_contact);
1120         }
1121
1122         g_object_unref (data->cancellable);
1123         g_object_unref (data->book);
1124         g_object_unref (data->backend);
1125         g_slice_free (CreateContactData, data);
1126 }
1127
1128 static void
1129 create_contact_photo_query_cb (GDataService *service,
1130                                GAsyncResult *async_result,
1131                                CreateContactData *data)
1132 {
1133         GDataEntry *queried_contact;
1134         EContactPhoto *photo;
1135         GError *gdata_error = NULL;
1136
1137         __debug__ (G_STRFUNC);
1138
1139         queried_contact = gdata_service_query_single_entry_finish (service, async_result, &gdata_error);
1140
1141         if (gdata_error != NULL) {
1142                 __debug__ ("Querying for created contact failed: %s", gdata_error->message);
1143                 goto finish;
1144         }
1145
1146         /* Output debug XML */
1147         if (__e_book_backend_google_debug__) {
1148                 gchar *xml = gdata_parsable_get_xml (GDATA_PARSABLE (queried_contact));
1149                 __debug__ ("After re-querying:\n%s", xml);
1150                 g_free (xml);
1151         }
1152
1153         /* Copy the photo from the previous contact to the new one so that it makes it into the cache. */
1154         photo = g_object_steal_data (G_OBJECT (data->new_contact), "photo");
1155
1156         if (photo != NULL) {
1157                 g_object_set_data_full (G_OBJECT (queried_contact), "photo", photo, (GDestroyNotify) e_contact_photo_free);
1158         }
1159
1160 finish:
1161         create_contact_finish (data, GDATA_CONTACTS_CONTACT (queried_contact), gdata_error);
1162
1163         g_clear_error (&gdata_error);
1164
1165         if (queried_contact != NULL) {
1166                 g_object_unref (queried_contact);
1167         }
1168 }
1169
1170 static void
1171 create_contact_photo_cb (GDataContactsContact *contact,
1172                          GAsyncResult *async_result,
1173                          CreateContactData *data)
1174 {
1175         EBookBackendGooglePrivate *priv;
1176         GError *gdata_error = NULL;
1177
1178         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (data->backend);
1179
1180         __debug__ (G_STRFUNC);
1181
1182         gdata_contacts_contact_set_photo_finish (contact, async_result, &gdata_error);
1183
1184         if (gdata_error == NULL) {
1185                 /* Success! Store the photo on the final GDataContactsContact object so it makes it into the cache. */
1186                 g_object_set_data_full (G_OBJECT (contact), "photo", data->photo, (GDestroyNotify) e_contact_photo_free);
1187                 data->photo = NULL;
1188
1189                 /* We now have to re-query for the contact, since setting its photo changes the contact's ETag. */
1190                 gdata_service_query_single_entry_async (priv->service,
1191                                                         gdata_contacts_service_get_primary_authorization_domain (),
1192                                                         gdata_entry_get_id (GDATA_ENTRY (contact)), NULL, GDATA_TYPE_CONTACTS_CONTACT,
1193                                                         data->cancellable, (GAsyncReadyCallback) create_contact_photo_query_cb, data);
1194                 return;
1195         } else {
1196                 /* Error. */
1197                 __debug__ ("Uploading initial contact photo for '%s' failed: %s", gdata_entry_get_id (GDATA_ENTRY (contact)), gdata_error->message);
1198         }
1199
1200         /* Respond to the initial create contact operation. */
1201         create_contact_finish (data, contact, gdata_error);
1202
1203         g_clear_error (&gdata_error);
1204 }
1205
1206 static void
1207 create_contact_cb (GDataService *service,
1208                    GAsyncResult *result,
1209                    CreateContactData *data)
1210 {
1211         GError *gdata_error = NULL;
1212         GDataEntry *new_contact;
1213
1214         __debug__ (G_STRFUNC);
1215
1216         new_contact = gdata_service_insert_entry_finish (service, result, &gdata_error);
1217
1218         if (!new_contact) {
1219                 __debug__ ("Creating contact failed: %s", gdata_error->message);
1220                 goto finish;
1221         }
1222
1223         data->new_contact = g_object_ref (new_contact);
1224
1225         /* Add a photo for the new contact, if appropriate. This has to be done before we respond to the contact creation operation so that
1226          * we can update the EContact with the photo data and ETag. */
1227         if (data->photo != NULL) {
1228                 gdata_contacts_contact_set_photo_async (GDATA_CONTACTS_CONTACT (new_contact), GDATA_CONTACTS_SERVICE (service),
1229                                                         (const guint8 *) data->photo->data.inlined.data, data->photo->data.inlined.length,
1230                                                         data->photo->data.inlined.mime_type, data->cancellable,
1231                                                         (GAsyncReadyCallback) create_contact_photo_cb, data);
1232                 return;
1233         }
1234
1235 finish:
1236         create_contact_finish (data, GDATA_CONTACTS_CONTACT (new_contact), gdata_error);
1237
1238         g_clear_error (&gdata_error);
1239
1240         if (new_contact != NULL) {
1241                 g_object_unref (new_contact);
1242         }
1243 }
1244
1245 /*
1246  * Creating a contact happens in either one request or three, depending on whether the contact's photo needs to be set. If the photo doesn't
1247  * need to be set, a single request is made to insert the contact's other data, and finished and responded to in create_contact_cb().
1248  *
1249  * If the photo does need to be set, one request is made to insert the contact's other data, which is finished in create_contact_cb(). This then
1250  * makes another request to upload the photo, which is finished in create_contact_photo_cb(). This then makes another request to re-query
1251  * the contact so that we have the latest version of its ETag (which changes when the contact's photo is set); this is finished and the creation
1252  * operation responded to in create_contact_photo_query_cb().
1253  */
1254 static void
1255 e_book_backend_google_create_contacts (EBookBackend *backend,
1256                                        EDataBook *book,
1257                                        guint32 opid,
1258                                        GCancellable *cancellable,
1259                                        const GSList *vcards)
1260 {
1261         EBookBackendGooglePrivate *priv;
1262         EContact *contact;
1263         GDataEntry *entry;
1264         gchar *xml;
1265         CreateContactData *data;
1266         const gchar *vcard_str = (const gchar *) vcards->data;
1267
1268         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1269
1270         /* We make the assumption that the vCard list we're passed is always exactly one element long, since we haven't specified "bulk-adds"
1271          * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
1272         if (vcards->next != NULL) {
1273                 e_data_book_respond_create_contacts (book, opid,
1274                                                      EDB_ERROR_EX (NOT_SUPPORTED,
1275                                                      _("The backend does not support bulk additions")),
1276                                                      NULL);
1277                 return;
1278         }
1279
1280         __debug__ (G_STRFUNC);
1281
1282         __debug__ ("Creating: %s", vcard_str);
1283
1284         if (!e_backend_get_online (E_BACKEND (backend))) {
1285                 e_data_book_respond_create_contacts (book, opid, EDB_ERROR (OFFLINE_UNAVAILABLE), NULL);
1286                 return;
1287         }
1288
1289         g_return_if_fail (backend_is_authorized (backend));
1290
1291         /* Ensure the system groups have been fetched. */
1292         if (g_hash_table_size (priv->system_groups_by_id) == 0) {
1293                 get_groups_sync (backend, cancellable);
1294         }
1295
1296         /* Build the GDataEntry from the vCard */
1297         contact = e_contact_new_from_vcard (vcard_str);
1298         entry = gdata_entry_new_from_e_contact (contact, priv->groups_by_name, priv->system_groups_by_id, _create_group, backend);
1299         g_object_unref (contact);
1300
1301         /* Debug XML output */
1302         xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
1303         __debug__ ("new entry with xml: %s", xml);
1304         g_free (xml);
1305
1306         /* Insert the entry on the server asynchronously */
1307         cancellable = start_operation (backend, opid, cancellable, _("Creating new contact…"));
1308
1309         data = g_slice_new (CreateContactData);
1310         data->backend = g_object_ref (backend);
1311         data->book = g_object_ref (book);
1312         data->opid = opid;
1313         data->cancellable = g_object_ref (cancellable);
1314         data->new_contact = NULL;
1315         data->photo = g_object_steal_data (G_OBJECT (entry), "photo");
1316
1317         gdata_contacts_service_insert_contact_async (GDATA_CONTACTS_SERVICE (priv->service), GDATA_CONTACTS_CONTACT (entry), cancellable,
1318                                                      (GAsyncReadyCallback) create_contact_cb, data);
1319
1320         g_object_unref (cancellable);
1321         g_object_unref (entry);
1322 }
1323
1324 typedef struct {
1325         EBookBackend *backend;
1326         EDataBook *book;
1327         guint32 opid;
1328         gchar *uid;
1329 } RemoveContactData;
1330
1331 static void
1332 remove_contact_cb (GDataService *service,
1333                    GAsyncResult *result,
1334                    RemoveContactData *data)
1335 {
1336         GError *gdata_error = NULL;
1337         gboolean success;
1338         GSList *ids;
1339
1340         __debug__ (G_STRFUNC);
1341
1342         success = gdata_service_delete_entry_finish (service, result, &gdata_error);
1343         finish_operation (data->backend, data->opid, gdata_error);
1344
1345         if (!success) {
1346                 GError *book_error = NULL;
1347                 data_book_error_from_gdata_error (&book_error, gdata_error);
1348                 __debug__ ("Deleting contact %s failed: %s", data->uid, gdata_error->message);
1349                 g_error_free (gdata_error);
1350
1351                 e_data_book_respond_remove_contacts (data->book, data->opid, book_error, NULL);
1352                 goto finish;
1353         }
1354
1355         /* List the entry's ID in the success list */
1356         ids = g_slist_prepend (NULL, data->uid);
1357         e_data_book_respond_remove_contacts (data->book, data->opid, NULL, ids);
1358         g_slist_free (ids);
1359
1360 finish:
1361         g_free (data->uid);
1362         g_object_unref (data->book);
1363         g_object_unref (data->backend);
1364         g_slice_free (RemoveContactData, data);
1365 }
1366
1367 static void
1368 e_book_backend_google_remove_contacts (EBookBackend *backend,
1369                                        EDataBook *book,
1370                                        guint32 opid,
1371                                        GCancellable *cancellable,
1372                                        const GSList *id_list)
1373 {
1374         EBookBackendGooglePrivate *priv;
1375         const gchar *uid = id_list->data;
1376         GDataEntry *entry = NULL;
1377         EContact *cached_contact;
1378         RemoveContactData *data;
1379
1380         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1381
1382         __debug__ (G_STRFUNC);
1383
1384         if (!e_backend_get_online (E_BACKEND (backend))) {
1385                 e_data_book_respond_remove_contacts (book, opid, EDB_ERROR (OFFLINE_UNAVAILABLE), NULL);
1386                 return;
1387         }
1388
1389         g_return_if_fail (backend_is_authorized (backend));
1390
1391         /* We make the assumption that the ID list we're passed is always exactly one element long, since we haven't specified "bulk-removes"
1392          * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
1393         if (id_list->next != NULL) {
1394                 e_data_book_respond_remove_contacts (book, opid,
1395                                                      EDB_ERROR_EX (NOT_SUPPORTED,
1396                                                      _("The backend does not support bulk removals")),
1397                                                      NULL);
1398                 return;
1399         }
1400         g_return_if_fail (!id_list->next);
1401
1402         /* Get the contact and associated GDataEntry from the cache */
1403         cached_contact = cache_get_contact (backend, uid, &entry);
1404
1405         if (!cached_contact) {
1406                 __debug__ ("Deleting contact %s failed: Contact not found in cache.", uid);
1407
1408                 e_data_book_respond_remove_contacts (book, opid, EDB_ERROR (CONTACT_NOT_FOUND), NULL);
1409                 return;
1410         }
1411
1412         g_object_unref (cached_contact);
1413
1414         /* Remove the contact from the cache */
1415         cache_remove_contact (backend, uid);
1416
1417         /* Delete the contact from the server asynchronously */
1418         data = g_slice_new (RemoveContactData);
1419         data->backend = g_object_ref (backend);
1420         data->book = g_object_ref (book);
1421         data->opid = opid;
1422         data->uid = g_strdup (uid);
1423
1424         cancellable = start_operation (backend, opid, cancellable, _("Deleting contact…"));
1425         gdata_service_delete_entry_async (GDATA_SERVICE (priv->service), gdata_contacts_service_get_primary_authorization_domain (),
1426                                           entry, cancellable, (GAsyncReadyCallback) remove_contact_cb, data);
1427         g_object_unref (cancellable);
1428         g_object_unref (entry);
1429 }
1430
1431 typedef enum {
1432         LEAVE_PHOTO,
1433         ADD_PHOTO,
1434         REMOVE_PHOTO,
1435         UPDATE_PHOTO,
1436 } PhotoOperation;
1437
1438 typedef struct {
1439         EBookBackend *backend;
1440         EDataBook *book;
1441         guint32 opid;
1442         GCancellable *cancellable;
1443         GDataContactsContact *new_contact;
1444         EContactPhoto *photo;
1445         PhotoOperation photo_operation;
1446 } ModifyContactData;
1447
1448 static void
1449 modify_contact_finish (ModifyContactData *data,
1450                        GDataContactsContact *new_contact,
1451                        const GError *gdata_error)
1452 {
1453         EContact *e_contact;
1454
1455         __debug__ (G_STRFUNC);
1456
1457         if (gdata_error == NULL) {
1458                 GSList modified_contacts = {NULL,};
1459                 /* Add the new entry to the cache */
1460                 e_contact = cache_add_contact (data->backend, GDATA_ENTRY (new_contact));
1461                 modified_contacts.data = e_contact;
1462                 e_data_book_respond_modify_contacts (data->book, data->opid, NULL, &modified_contacts);
1463                 g_object_unref (e_contact);
1464         } else {
1465                 GError *book_error = NULL;
1466
1467                 /* Report the error. */
1468                 data_book_error_from_gdata_error (&book_error, gdata_error);
1469                 e_data_book_respond_modify_contacts (data->book, data->opid, book_error, NULL);
1470         }
1471
1472         finish_operation (data->backend, data->opid, gdata_error);
1473
1474         if (data->photo != NULL) {
1475                 e_contact_photo_free (data->photo);
1476         }
1477
1478         if (data->new_contact != NULL) {
1479                 g_object_unref (data->new_contact);
1480         }
1481
1482         g_object_unref (data->cancellable);
1483         g_object_unref (data->book);
1484         g_object_unref (data->backend);
1485         g_slice_free (ModifyContactData, data);
1486 }
1487
1488 static void
1489 modify_contact_photo_query_cb (GDataService *service,
1490                                GAsyncResult *async_result,
1491                                ModifyContactData *data)
1492 {
1493         GDataEntry *queried_contact;
1494         EContactPhoto *photo;
1495         GError *gdata_error = NULL;
1496
1497         __debug__ (G_STRFUNC);
1498
1499         queried_contact = gdata_service_query_single_entry_finish (service, async_result, &gdata_error);
1500
1501         if (gdata_error != NULL) {
1502                 __debug__ ("Querying for modified contact failed: %s", gdata_error->message);
1503                 goto finish;
1504         }
1505
1506         /* Output debug XML */
1507         if (__e_book_backend_google_debug__) {
1508                 gchar *xml = gdata_parsable_get_xml (GDATA_PARSABLE (queried_contact));
1509                 __debug__ ("After re-querying:\n%s", xml);
1510                 g_free (xml);
1511         }
1512
1513         /* Copy the photo from the previous contact to the new one so that it makes it into the cache. */
1514         photo = g_object_steal_data (G_OBJECT (data->new_contact), "photo");
1515
1516         if (photo != NULL) {
1517                 g_object_set_data_full (G_OBJECT (queried_contact), "photo", photo, (GDestroyNotify) e_contact_photo_free);
1518         }
1519
1520 finish:
1521         modify_contact_finish (data, GDATA_CONTACTS_CONTACT (queried_contact), gdata_error);
1522
1523         g_clear_error (&gdata_error);
1524
1525         if (queried_contact != NULL) {
1526                 g_object_unref (queried_contact);
1527         }
1528 }
1529
1530 static void
1531 modify_contact_photo_cb (GDataContactsContact *contact,
1532                          GAsyncResult *async_result,
1533                          ModifyContactData *data)
1534 {
1535         EBookBackendGooglePrivate *priv;
1536         GError *gdata_error = NULL;
1537
1538         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (data->backend);
1539
1540         __debug__ (G_STRFUNC);
1541
1542         gdata_contacts_contact_set_photo_finish (contact, async_result, &gdata_error);
1543
1544         if (gdata_error == NULL) {
1545                 /* Success! Store the photo on the final GDataContactsContact object so it makes it into the cache. */
1546                 if (data->photo != NULL) {
1547                         g_object_set_data_full (G_OBJECT (contact), "photo", data->photo, (GDestroyNotify) e_contact_photo_free);
1548                         data->photo = NULL;
1549                 } else {
1550                         g_object_set_data (G_OBJECT (contact), "photo", NULL);
1551                 }
1552
1553                 /* We now have to re-query for the contact, since setting its photo changes the contact's ETag. */
1554                 gdata_service_query_single_entry_async (priv->service,
1555                                                         gdata_contacts_service_get_primary_authorization_domain (),
1556                                                         gdata_entry_get_id (GDATA_ENTRY (contact)), NULL, GDATA_TYPE_CONTACTS_CONTACT,
1557                                                         data->cancellable, (GAsyncReadyCallback) modify_contact_photo_query_cb, data);
1558                 return;
1559         } else {
1560                 /* Error. */
1561                 __debug__ ("Uploading modified contact photo for '%s' failed: %s", gdata_entry_get_id (GDATA_ENTRY (contact)), gdata_error->message);
1562         }
1563
1564         /* Respond to the initial modify contact operation. */
1565         modify_contact_finish (data, contact, gdata_error);
1566
1567         g_clear_error (&gdata_error);
1568 }
1569
1570 static void
1571 modify_contact_cb (GDataService *service,
1572                    GAsyncResult *result,
1573                    ModifyContactData *data)
1574 {
1575         GDataEntry *new_contact;
1576         GError *gdata_error = NULL;
1577
1578         __debug__ (G_STRFUNC);
1579
1580         new_contact = gdata_service_update_entry_finish (service, result, &gdata_error);
1581
1582         if (!new_contact) {
1583                 __debug__ ("Modifying contact failed: %s", gdata_error->message);
1584                 goto finish;
1585         }
1586
1587         /* Output debug XML */
1588         if (__e_book_backend_google_debug__) {
1589                 gchar *xml = gdata_parsable_get_xml (GDATA_PARSABLE (new_contact));
1590                 __debug__ ("After:\n%s", xml);
1591                 g_free (xml);
1592         }
1593
1594         data->new_contact = g_object_ref (new_contact);
1595
1596         /* Add a photo for the new contact, if appropriate. This has to be done before we respond to the contact creation operation so that
1597          * we can update the EContact with the photo data and ETag. */
1598         switch (data->photo_operation) {
1599                 case LEAVE_PHOTO:
1600                         /* Do nothing apart from copy the photo stolen from the old GDataContactsContact to the updated one we've just received from
1601                          * Google. */
1602                         g_object_set_data_full (G_OBJECT (new_contact), "photo", data->photo, (GDestroyNotify) e_contact_photo_free);
1603                         data->photo = NULL;
1604                         break;
1605                 case ADD_PHOTO:
1606                 case UPDATE_PHOTO:
1607                         /* Set the photo. */
1608                         g_return_if_fail (data->photo != NULL);
1609                         gdata_contacts_contact_set_photo_async (GDATA_CONTACTS_CONTACT (new_contact), GDATA_CONTACTS_SERVICE (service),
1610                                                                 (const guint8 *) data->photo->data.inlined.data, data->photo->data.inlined.length,
1611                                                                 data->photo->data.inlined.mime_type, data->cancellable,
1612                                                                 (GAsyncReadyCallback) modify_contact_photo_cb, data);
1613                         return;
1614                 case REMOVE_PHOTO:
1615                         /* Unset the photo. */
1616                         g_return_if_fail (data->photo == NULL);
1617                         gdata_contacts_contact_set_photo_async (GDATA_CONTACTS_CONTACT (new_contact), GDATA_CONTACTS_SERVICE (service),
1618                                                                 NULL, 0, NULL, data->cancellable, (GAsyncReadyCallback) modify_contact_photo_cb, data);
1619                         return;
1620                 default:
1621                         g_assert_not_reached ();
1622         }
1623
1624 finish:
1625         modify_contact_finish (data, GDATA_CONTACTS_CONTACT (new_contact), gdata_error);
1626
1627         g_clear_error (&gdata_error);
1628
1629         if (new_contact != NULL) {
1630                 g_object_unref (new_contact);
1631         }
1632 }
1633
1634 /*
1635  * Modifying a contact happens in either one request or three, depending on whether the contact's photo needs to be updated. If the photo doesn't
1636  * need to be updated, a single request is made to update the contact's other data, and finished and responded to in modify_contact_cb().
1637  *
1638  * If the photo does need to be updated, one request is made to update the contact's other data, which is finished in modify_contact_cb(). This then
1639  * makes another request to upload the updated photo, which is finished in modify_contact_photo_cb(). This then makes another request to re-query
1640  * the contact so that we have the latest version of its ETag (which changes when the contact's photo is set); this is finished and the modification
1641  * operation responded to in modify_contact_photo_query_cb().
1642  */
1643 static void
1644 e_book_backend_google_modify_contacts (EBookBackend *backend,
1645                                       EDataBook *book,
1646                                       guint32 opid,
1647                                       GCancellable *cancellable,
1648                                       const GSList *vcards)
1649 {
1650         EBookBackendGooglePrivate *priv;
1651         EContact *contact, *cached_contact;
1652         EContactPhoto *old_photo, *new_photo;
1653         GDataEntry *entry = NULL;
1654         const gchar *uid;
1655         ModifyContactData *data;
1656         const gchar *vcard_str = vcards->data;
1657
1658         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1659
1660         __debug__ (G_STRFUNC);
1661
1662         __debug__ ("Updating: %s", vcard_str);
1663
1664         if (!e_backend_get_online (E_BACKEND (backend))) {
1665                 e_data_book_respond_modify_contacts (book, opid, EDB_ERROR (OFFLINE_UNAVAILABLE), NULL);
1666                 return;
1667         }
1668
1669         /* We make the assumption that the vCard list we're passed is always exactly one element long, since we haven't specified "bulk-modifies"
1670          * in our static capability list. This is because there is no clean way to roll back changes in case of an error. */
1671         if (vcards->next != NULL) {
1672                 e_data_book_respond_modify_contacts (book, opid,
1673                                                      EDB_ERROR_EX (NOT_SUPPORTED,
1674                                                      _("The backend does not support bulk modifications")),
1675                                                      NULL);
1676                 return;
1677         }
1678
1679         g_return_if_fail (backend_is_authorized (backend));
1680
1681         /* Get the new contact and its UID */
1682         contact = e_contact_new_from_vcard (vcard_str);
1683         uid = e_contact_get (contact, E_CONTACT_UID);
1684
1685         /* Get the old cached contact with the same UID and its associated GDataEntry */
1686         cached_contact = cache_get_contact (backend, uid, &entry);
1687
1688         if (!cached_contact) {
1689                 __debug__ ("Modifying contact failed: Contact with uid %s not found in cache.", uid);
1690                 g_object_unref (contact);
1691
1692                 e_data_book_respond_modify_contacts (book, opid, EDB_ERROR (CONTACT_NOT_FOUND), NULL);
1693                 return;
1694         }
1695
1696         /* Ensure the system groups have been fetched. */
1697         if (g_hash_table_size (priv->system_groups_by_id) == 0) {
1698                 get_groups_sync (backend, cancellable);
1699         }
1700
1701         /* Update the old GDataEntry from the new contact */
1702         gdata_entry_update_from_e_contact (entry, contact, FALSE, priv->groups_by_name, priv->system_groups_by_id, _create_group, backend);
1703
1704         /* Output debug XML */
1705         if (__e_book_backend_google_debug__) {
1706                 gchar *xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
1707                 __debug__ ("Before:\n%s", xml);
1708                 g_free (xml);
1709         }
1710
1711         /* Update the contact on the server asynchronously */
1712         cancellable = start_operation (backend, opid, cancellable, _("Modifying contact…"));
1713
1714         data = g_slice_new (ModifyContactData);
1715         data->backend = g_object_ref (backend);
1716         data->book = g_object_ref (book);
1717         data->opid = opid;
1718
1719         data->cancellable = g_object_ref (cancellable);
1720         data->new_contact = NULL;
1721         data->photo = g_object_steal_data (G_OBJECT (entry), "photo");
1722
1723         /* Update the contact's photo. We can't rely on the ETags at this point, as the ETag in @ontact may be out of sync with the photo in the
1724          * EContact (since the photo may have been updated). Consequently, after updating @entry its ETag may also be out of sync with its attached
1725          * photo data. This means that we have to detect whether the photo has changed by comparing the photo data itself, which is guaranteed to
1726          * be in sync between @contact and @entry. */
1727         old_photo = e_contact_get (cached_contact, E_CONTACT_PHOTO);
1728         new_photo = e_contact_get (contact, E_CONTACT_PHOTO);
1729
1730         if ((old_photo == NULL || old_photo->type != E_CONTACT_PHOTO_TYPE_INLINED) && new_photo != NULL) {
1731                 /* Adding a photo */
1732                 data->photo_operation = ADD_PHOTO;
1733         } else if (old_photo != NULL && (new_photo == NULL || new_photo->type != E_CONTACT_PHOTO_TYPE_INLINED)) {
1734                 /* Removing a photo */
1735                 data->photo_operation = REMOVE_PHOTO;
1736         } else if (old_photo != NULL && new_photo != NULL &&
1737                    (old_photo->data.inlined.length != new_photo->data.inlined.length ||
1738                     memcmp (old_photo->data.inlined.data, new_photo->data.inlined.data, old_photo->data.inlined.length) != 0)) {
1739                 /* Modifying the photo */
1740                 data->photo_operation = UPDATE_PHOTO;
1741         } else {
1742                 /* Do nothing. */
1743                 data->photo_operation = LEAVE_PHOTO;
1744         }
1745
1746         if (new_photo != NULL) {
1747                 e_contact_photo_free (new_photo);
1748         }
1749
1750         if (old_photo != NULL) {
1751                 e_contact_photo_free (old_photo);
1752         }
1753
1754         gdata_service_update_entry_async (GDATA_SERVICE (priv->service), gdata_contacts_service_get_primary_authorization_domain (),
1755                                           entry, cancellable, (GAsyncReadyCallback) modify_contact_cb, data);
1756         g_object_unref (cancellable);
1757
1758         g_object_unref (cached_contact);
1759         g_object_unref (contact);
1760         g_object_unref (entry);
1761 }
1762
1763 static void
1764 e_book_backend_google_get_contact (EBookBackend *backend,
1765                                    EDataBook *book,
1766                                    guint32 opid,
1767                                    GCancellable *cancellable,
1768                                    const gchar *uid)
1769 {
1770         EContact *contact;
1771         gchar *vcard_str;
1772
1773         __debug__ (G_STRFUNC);
1774
1775         /* Get the contact */
1776         contact = cache_get_contact (backend, uid, NULL);
1777         if (!contact) {
1778                 __debug__ ("Getting contact with uid %s failed: Contact not found in cache.", uid);
1779
1780                 e_data_book_respond_get_contact (book, opid, EDB_ERROR (CONTACT_NOT_FOUND), NULL);
1781                 return;
1782         }
1783
1784         /* Success! Build and return a vCard of the contacts */
1785         vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1786         e_data_book_respond_get_contact (book, opid, NULL, vcard_str);
1787         g_free (vcard_str);
1788         g_object_unref (contact);
1789 }
1790
1791 static void
1792 e_book_backend_google_get_contact_list (EBookBackend *backend,
1793                                         EDataBook *book,
1794                                         guint32 opid,
1795                                         GCancellable *cancellable,
1796                                         const gchar *query)
1797 {
1798         EBookBackendSExp *sexp;
1799         GList *all_contacts;
1800         GSList *filtered_contacts = NULL;
1801
1802         __debug__ (G_STRFUNC);
1803
1804         /* Get all contacts */
1805         sexp = e_book_backend_sexp_new (query);
1806         all_contacts = cache_get_contacts (backend);
1807
1808         for (; all_contacts; all_contacts = g_list_delete_link (all_contacts, all_contacts)) {
1809                 EContact *contact = all_contacts->data;
1810
1811                 /* If the search expression matches the contact, include it in the search results */
1812                 if (e_book_backend_sexp_match_contact (sexp, contact)) {
1813                         gchar *vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1814                         filtered_contacts = g_slist_append (filtered_contacts, vcard_str);
1815                 }
1816
1817                 g_object_unref (contact);
1818         }
1819
1820         g_object_unref (sexp);
1821
1822         e_data_book_respond_get_contact_list (book, opid, NULL, filtered_contacts);
1823
1824         g_slist_foreach (filtered_contacts, (GFunc) g_free, NULL);
1825         g_slist_free (filtered_contacts);
1826 }
1827
1828 static void
1829 e_book_backend_google_get_contact_list_uids (EBookBackend *backend,
1830                                              EDataBook *book,
1831                                              guint32 opid,
1832                                              GCancellable *cancellable,
1833                                              const gchar *query)
1834 {
1835         EBookBackendSExp *sexp;
1836         GList *all_contacts;
1837         GSList *filtered_uids = NULL;
1838
1839         __debug__ (G_STRFUNC);
1840
1841         /* Get all contacts */
1842         sexp = e_book_backend_sexp_new (query);
1843         all_contacts = cache_get_contacts (backend);
1844
1845         for (; all_contacts; all_contacts = g_list_delete_link (all_contacts, all_contacts)) {
1846                 EContact *contact = all_contacts->data;
1847
1848                 /* If the search expression matches the contact, include it in the search results */
1849                 if (e_book_backend_sexp_match_contact (sexp, contact)) {
1850                         filtered_uids = g_slist_append (filtered_uids, e_contact_get (contact, E_CONTACT_UID));
1851                 }
1852
1853                 g_object_unref (contact);
1854         }
1855
1856         g_object_unref (sexp);
1857
1858         e_data_book_respond_get_contact_list_uids (book, opid, NULL, filtered_uids);
1859
1860         g_slist_foreach (filtered_uids, (GFunc) g_free, NULL);
1861         g_slist_free (filtered_uids);
1862 }
1863
1864 static gboolean
1865 on_refresh_idle (EBookBackend *backend)
1866 {
1867         EBookBackendGooglePrivate *priv;
1868
1869         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1870
1871         priv->idle_id = 0;
1872         cache_refresh_if_needed (backend);
1873
1874         return FALSE;
1875 }
1876
1877 static void
1878 e_book_backend_google_start_book_view (EBookBackend *backend,
1879                                        EDataBookView *bookview)
1880 {
1881         EBookBackendGooglePrivate *priv;
1882         GList *cached_contacts;
1883         GError *error = NULL;
1884
1885         g_return_if_fail (E_IS_BOOK_BACKEND_GOOGLE (backend));
1886         g_return_if_fail (E_IS_DATA_BOOK_VIEW (bookview));
1887
1888         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1889
1890         __debug__ (G_STRFUNC);
1891
1892         priv->bookviews = g_list_append (priv->bookviews, bookview);
1893
1894         e_data_book_view_ref (bookview);
1895         e_data_book_view_notify_progress (bookview, -1, _("Loading…"));
1896
1897         /* Ensure that we're ready to support a view */
1898         cache_refresh_if_needed (backend);
1899
1900         /* Update the cache if necessary */
1901         if (cache_needs_update (backend, NULL)) {
1902                 /* XXX We ought to be authorized by now, I would think.
1903                  *     Not sure when we wouldn't be or how to handle it. */
1904                 if (!backend_is_authorized (backend)) {
1905                         error = EDB_ERROR (AUTHENTICATION_REQUIRED);
1906                         goto exit;
1907                 } else {
1908                         /* Update in an idle function, so that this call doesn't block */
1909                         priv->idle_id = g_idle_add ((GSourceFunc) on_refresh_idle, backend);
1910                 }
1911         }
1912
1913         /* Get the contacts */
1914         cached_contacts = cache_get_contacts (backend);
1915         __debug__ ("%d contacts found in cache", g_list_length (cached_contacts));
1916
1917         /* Notify the view that all the contacts have changed (i.e. been added) */
1918         for (; cached_contacts; cached_contacts = g_list_delete_link (cached_contacts, cached_contacts)) {
1919                 EContact *contact = cached_contacts->data;
1920                 e_data_book_view_notify_update (bookview, contact);
1921                 g_object_unref (contact);
1922         }
1923
1924 exit:
1925         /* This function frees the GError passed to it. */
1926         e_data_book_view_notify_complete (bookview, error);
1927 }
1928
1929 static void
1930 e_book_backend_google_stop_book_view (EBookBackend *backend,
1931                                       EDataBookView *bookview)
1932 {
1933         EBookBackendGooglePrivate *priv;
1934         GList *view;
1935
1936         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1937
1938         __debug__ (G_STRFUNC);
1939
1940         /* Remove the view from the list of active views */
1941         if ((view = g_list_find (priv->bookviews, bookview)) != NULL) {
1942                 priv->bookviews = g_list_delete_link (priv->bookviews, view);
1943                 e_data_book_view_unref (bookview);
1944         }
1945
1946         /* If there are no book views left, we can stop doing certain things, like refreshes */
1947         if (!priv->bookviews && priv->refresh_id != 0) {
1948                 g_source_remove (priv->refresh_id);
1949                 priv->refresh_id = 0;
1950         }
1951 }
1952
1953 static void
1954 e_book_backend_google_remove (EBookBackend *backend,
1955                               EDataBook *book,
1956                               guint32 opid,
1957                               GCancellable *cancellable)
1958 {
1959         __debug__ (G_STRFUNC);
1960         e_data_book_respond_remove (book, opid, NULL);
1961 }
1962
1963 static void
1964 e_book_backend_google_open (EBookBackend *backend,
1965                             EDataBook *book,
1966                             guint opid,
1967                             GCancellable *cancellable,
1968                             gboolean only_if_exists)
1969 {
1970         EBookBackendGooglePrivate *priv;
1971         ESourceRefresh *refresh_extension;
1972         ESource *source;
1973         guint interval_in_minutes;
1974         const gchar *extension_name;
1975         gboolean is_online;
1976         GError *error = NULL;
1977
1978         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1979
1980         __debug__ (G_STRFUNC);
1981
1982         if (priv->cancellables && backend_is_authorized (backend)) {
1983                 e_book_backend_respond_opened (backend, book, opid, NULL);
1984                 return;
1985         }
1986
1987         source = e_backend_get_source (E_BACKEND (backend));
1988
1989         extension_name = E_SOURCE_EXTENSION_REFRESH;
1990         refresh_extension = e_source_get_extension (source, extension_name);
1991
1992         interval_in_minutes =
1993                 e_source_refresh_get_enabled (refresh_extension) ?
1994                 e_source_refresh_get_interval_minutes (refresh_extension) : 0;
1995
1996         /* Set up our object */
1997         if (!priv->cancellables) {
1998                 priv->groups_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1999                 priv->groups_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
2000                 priv->system_groups_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
2001                 priv->system_groups_by_entry_id = g_hash_table_new (g_str_hash, g_str_equal); /* shares keys and values with system_groups_by_id */
2002                 priv->cancellables = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
2003         }
2004
2005         cache_init (backend);
2006         priv->refresh_interval = interval_in_minutes * 60;
2007
2008         /* Remove and re-add the timeout */
2009         if (priv->refresh_id != 0 && priv->refresh_interval > 0) {
2010                 g_source_remove (priv->refresh_id);
2011                 priv->refresh_id = g_timeout_add_seconds (priv->refresh_interval, (GSourceFunc) on_refresh_timeout, backend);
2012         }
2013
2014         /* Set up ready to be interacted with */
2015         is_online = e_backend_get_online (E_BACKEND (backend));
2016         e_book_backend_notify_online (backend, is_online);
2017         e_book_backend_notify_readonly (backend, TRUE);
2018
2019         if (is_online) {
2020                 if (request_authorization (backend, cancellable, &error)) {
2021                         /* Refresh the authorizer.  This may block. */
2022                         gdata_authorizer_refresh_authorization (
2023                                 priv->authorizer, cancellable, &error);
2024                 }
2025         }
2026
2027         if (!is_online || backend_is_authorized (backend)) {
2028                 if (is_online)
2029                         e_book_backend_notify_readonly (backend, FALSE);
2030                 e_book_backend_notify_opened (backend, NULL /* Success */);
2031         }
2032
2033         /* This function frees the GError passed to it. */
2034         e_data_book_respond_open (book, opid, error);
2035 }
2036
2037 static void
2038 e_book_backend_google_get_backend_property (EBookBackend *backend,
2039                                             EDataBook *book,
2040                                             guint32 opid,
2041                                             GCancellable *cancellable,
2042                                             const gchar *prop_name)
2043 {
2044         __debug__ (G_STRFUNC);
2045
2046         g_return_if_fail (prop_name != NULL);
2047
2048         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
2049                 e_data_book_respond_get_backend_property (book, opid, NULL, "net,do-initial-query,contact-lists");
2050         } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
2051                 e_data_book_respond_get_backend_property (book, opid, NULL, "");
2052         } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
2053                 GSList *fields = NULL;
2054                 gchar *fields_str;
2055                 guint i;
2056                 const gint supported_fields[] = {
2057                         E_CONTACT_FULL_NAME,
2058                         E_CONTACT_EMAIL_1,
2059                         E_CONTACT_EMAIL_2,
2060                         E_CONTACT_EMAIL_3,
2061                         E_CONTACT_EMAIL_4,
2062                         E_CONTACT_ADDRESS_LABEL_HOME,
2063                         E_CONTACT_ADDRESS_LABEL_WORK,
2064                         E_CONTACT_ADDRESS_LABEL_OTHER,
2065                         E_CONTACT_PHONE_HOME,
2066                         E_CONTACT_PHONE_HOME_FAX,
2067                         E_CONTACT_PHONE_BUSINESS,
2068                         E_CONTACT_PHONE_BUSINESS_FAX,
2069                         E_CONTACT_PHONE_MOBILE,
2070                         E_CONTACT_PHONE_PAGER,
2071                         E_CONTACT_IM_AIM,
2072                         E_CONTACT_IM_JABBER,
2073                         E_CONTACT_IM_YAHOO,
2074                         E_CONTACT_IM_MSN,
2075                         E_CONTACT_IM_ICQ,
2076                         E_CONTACT_IM_SKYPE,
2077                         E_CONTACT_IM_GOOGLE_TALK,
2078                         E_CONTACT_IM_GADUGADU,
2079                         E_CONTACT_IM_GROUPWISE,
2080                         E_CONTACT_ADDRESS,
2081                         E_CONTACT_ADDRESS_HOME,
2082                         E_CONTACT_ADDRESS_WORK,
2083                         E_CONTACT_ADDRESS_OTHER,
2084                         E_CONTACT_NAME,
2085                         E_CONTACT_GIVEN_NAME,
2086                         E_CONTACT_FAMILY_NAME,
2087                         E_CONTACT_PHONE_ASSISTANT,
2088                         E_CONTACT_PHONE_BUSINESS_2,
2089                         E_CONTACT_PHONE_CALLBACK,
2090                         E_CONTACT_PHONE_CAR,
2091                         E_CONTACT_PHONE_COMPANY,
2092                         E_CONTACT_PHONE_HOME_2,
2093                         E_CONTACT_PHONE_ISDN,
2094                         E_CONTACT_PHONE_OTHER,
2095                         E_CONTACT_PHONE_OTHER_FAX,
2096                         E_CONTACT_PHONE_PRIMARY,
2097                         E_CONTACT_PHONE_RADIO,
2098                         E_CONTACT_PHONE_TELEX,
2099                         E_CONTACT_PHONE_TTYTDD,
2100                         E_CONTACT_IM_AIM_HOME_1,
2101                         E_CONTACT_IM_AIM_HOME_2,
2102                         E_CONTACT_IM_AIM_HOME_3,
2103                         E_CONTACT_IM_AIM_WORK_1,
2104                         E_CONTACT_IM_AIM_WORK_2,
2105                         E_CONTACT_IM_AIM_WORK_3,
2106                         E_CONTACT_IM_GROUPWISE_HOME_1,
2107                         E_CONTACT_IM_GROUPWISE_HOME_2,
2108                         E_CONTACT_IM_GROUPWISE_HOME_3,
2109                         E_CONTACT_IM_GROUPWISE_WORK_1,
2110                         E_CONTACT_IM_GROUPWISE_WORK_2,
2111                         E_CONTACT_IM_GROUPWISE_WORK_3,
2112                         E_CONTACT_IM_JABBER_HOME_1,
2113                         E_CONTACT_IM_JABBER_HOME_2,
2114                         E_CONTACT_IM_JABBER_HOME_3,
2115                         E_CONTACT_IM_JABBER_WORK_1,
2116                         E_CONTACT_IM_JABBER_WORK_2,
2117                         E_CONTACT_IM_JABBER_WORK_3,
2118                         E_CONTACT_IM_YAHOO_HOME_1,
2119                         E_CONTACT_IM_YAHOO_HOME_2,
2120                         E_CONTACT_IM_YAHOO_HOME_3,
2121                         E_CONTACT_IM_YAHOO_WORK_1,
2122                         E_CONTACT_IM_YAHOO_WORK_2,
2123                         E_CONTACT_IM_YAHOO_WORK_3,
2124                         E_CONTACT_IM_MSN_HOME_1,
2125                         E_CONTACT_IM_MSN_HOME_2,
2126                         E_CONTACT_IM_MSN_HOME_3,
2127                         E_CONTACT_IM_MSN_WORK_1,
2128                         E_CONTACT_IM_MSN_WORK_2,
2129                         E_CONTACT_IM_MSN_WORK_3,
2130                         E_CONTACT_IM_ICQ_HOME_1,
2131                         E_CONTACT_IM_ICQ_HOME_2,
2132                         E_CONTACT_IM_ICQ_HOME_3,
2133                         E_CONTACT_IM_ICQ_WORK_1,
2134                         E_CONTACT_IM_ICQ_WORK_2,
2135                         E_CONTACT_IM_ICQ_WORK_3,
2136                         E_CONTACT_EMAIL,
2137                         E_CONTACT_IM_GADUGADU_HOME_1,
2138                         E_CONTACT_IM_GADUGADU_HOME_2,
2139                         E_CONTACT_IM_GADUGADU_HOME_3,
2140                         E_CONTACT_IM_GADUGADU_WORK_1,
2141                         E_CONTACT_IM_GADUGADU_WORK_2,
2142                         E_CONTACT_IM_GADUGADU_WORK_3,
2143                         E_CONTACT_TEL,
2144                         E_CONTACT_IM_SKYPE_HOME_1,
2145                         E_CONTACT_IM_SKYPE_HOME_2,
2146                         E_CONTACT_IM_SKYPE_HOME_3,
2147                         E_CONTACT_IM_SKYPE_WORK_1,
2148                         E_CONTACT_IM_SKYPE_WORK_2,
2149                         E_CONTACT_IM_SKYPE_WORK_3,
2150                         E_CONTACT_IM_GOOGLE_TALK_HOME_1,
2151                         E_CONTACT_IM_GOOGLE_TALK_HOME_2,
2152                         E_CONTACT_IM_GOOGLE_TALK_HOME_3,
2153                         E_CONTACT_IM_GOOGLE_TALK_WORK_1,
2154                         E_CONTACT_IM_GOOGLE_TALK_WORK_2,
2155                         E_CONTACT_IM_GOOGLE_TALK_WORK_3,
2156                         E_CONTACT_SIP,
2157                         E_CONTACT_ORG,
2158                         E_CONTACT_ORG_UNIT,
2159                         E_CONTACT_TITLE,
2160                         E_CONTACT_ROLE,
2161                         E_CONTACT_HOMEPAGE_URL,
2162                         E_CONTACT_BLOG_URL,
2163                         E_CONTACT_BIRTH_DATE,
2164                         E_CONTACT_ANNIVERSARY,
2165                         E_CONTACT_NOTE,
2166                         E_CONTACT_PHOTO,
2167                         E_CONTACT_CATEGORIES,
2168 #if defined(GDATA_CHECK_VERSION)
2169 #if GDATA_CHECK_VERSION(0, 11, 0)
2170                         E_CONTACT_CATEGORY_LIST,
2171                         E_CONTACT_FILE_AS
2172 #else
2173                         E_CONTACT_CATEGORY_LIST
2174 #endif
2175 #else
2176                         E_CONTACT_CATEGORY_LIST
2177 #endif
2178                 };
2179
2180                 /* Add all the fields above to the list */
2181                 for (i = 0; i < G_N_ELEMENTS (supported_fields); i++) {
2182                         const gchar *field_name = e_contact_field_name (supported_fields[i]);
2183                         fields = g_slist_prepend (fields, (gpointer) field_name);
2184                 }
2185
2186                 fields_str = e_data_book_string_slist_to_comma_string (fields);
2187
2188                 e_data_book_respond_get_backend_property (book, opid, NULL, fields_str);
2189
2190                 g_slist_free (fields);
2191                 g_free (fields_str);
2192         } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_AUTH_METHODS)) {
2193                 e_data_book_respond_get_backend_property (book, opid, NULL, "plain/password");
2194         } else {
2195                 E_BOOK_BACKEND_CLASS (e_book_backend_google_parent_class)->get_backend_property (backend, book, opid, cancellable, prop_name);
2196         }
2197 }
2198
2199 static void
2200 google_cancel_all_operations (EBookBackend *backend)
2201 {
2202         EBookBackendGooglePrivate *priv;
2203         GHashTableIter iter;
2204         gpointer opid_ptr;
2205         GCancellable *cancellable;
2206
2207         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
2208
2209         __debug__ (G_STRFUNC);
2210
2211         if (!priv->cancellables)
2212                 return;
2213
2214         /* Cancel all active operations */
2215         g_hash_table_iter_init (&iter, priv->cancellables);
2216         while (g_hash_table_iter_next (&iter, &opid_ptr, (gpointer *) &cancellable)) {
2217                 g_cancellable_cancel (cancellable);
2218         }
2219 }
2220
2221 static void
2222 e_book_backend_google_notify_online_cb (EBookBackend *backend,
2223                                         GParamSpec *pspec)
2224 {
2225         EBookBackendGooglePrivate *priv;
2226         gboolean is_online;
2227
2228         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
2229
2230         __debug__ (G_STRFUNC);
2231
2232         is_online = e_backend_get_online (E_BACKEND (backend));
2233         e_book_backend_notify_online (backend, is_online);
2234
2235         if (is_online && e_book_backend_is_opened (backend)) {
2236                 request_authorization (backend, NULL, NULL);
2237                 if (backend_is_authorized (backend))
2238                         e_book_backend_notify_readonly (backend, FALSE);
2239         } else {
2240                 /* Going offline, so cancel all running operations */
2241                 google_cancel_all_operations (backend);
2242
2243                 /* Mark the book as unwriteable if we're going offline, but don't do the inverse when we go online;
2244                  * e_book_backend_google_authenticate_user() will mark us as writeable again once the user's authenticated again. */
2245                 e_book_backend_notify_readonly (backend, TRUE);
2246
2247                 /* We can free our service. */
2248                 if (priv->service)
2249                         g_object_unref (priv->service);
2250                 priv->service = NULL;
2251         }
2252 }
2253
2254 static void
2255 e_book_backend_google_dispose (GObject *object)
2256 {
2257         EBookBackendGooglePrivate *priv;
2258
2259         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (object);
2260
2261         __debug__ (G_STRFUNC);
2262
2263         /* Cancel all outstanding operations */
2264         google_cancel_all_operations (E_BOOK_BACKEND (object));
2265
2266         while (priv->bookviews) {
2267                 e_data_book_view_unref (priv->bookviews->data);
2268                 priv->bookviews = g_list_delete_link (priv->bookviews, priv->bookviews);
2269         }
2270
2271         if (priv->idle_id) {
2272                 g_source_remove (priv->idle_id);
2273                 priv->idle_id = 0;
2274         }
2275
2276         if (priv->service)
2277                 g_object_unref (priv->service);
2278         priv->service = NULL;
2279
2280         if (priv->authorizer != NULL)
2281                 g_object_unref (priv->authorizer);
2282         priv->authorizer = NULL;
2283
2284         if (priv->proxy)
2285                 g_object_unref (priv->proxy);
2286         priv->proxy = NULL;
2287
2288         g_clear_object (&priv->cache);
2289
2290         G_OBJECT_CLASS (e_book_backend_google_parent_class)->dispose (object);
2291 }
2292
2293 static void
2294 e_book_backend_google_finalize (GObject *object)
2295 {
2296         EBookBackendGooglePrivate *priv;
2297
2298         priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (object);
2299
2300         __debug__ (G_STRFUNC);
2301
2302         if (priv->cancellables) {
2303                 g_hash_table_destroy (priv->groups_by_id);
2304                 g_hash_table_destroy (priv->groups_by_name);
2305                 g_hash_table_destroy (priv->system_groups_by_entry_id);
2306                 g_hash_table_destroy (priv->system_groups_by_id);
2307                 g_hash_table_destroy (priv->cancellables);
2308         }
2309
2310         G_OBJECT_CLASS (e_book_backend_google_parent_class)->finalize (object);
2311 }
2312
2313 static ESourceAuthenticationResult
2314 book_backend_google_try_password_sync (ESourceAuthenticator *authenticator,
2315                                        const GString *password,
2316                                        GCancellable *cancellable,
2317                                        GError **error)
2318 {
2319         EBookBackendGooglePrivate *priv;
2320         ESourceAuthentication *auth_extension;
2321         ESourceAuthenticationResult result;
2322         ESource *source;
2323         const gchar *extension_name;
2324         gchar *user;
2325         GError *local_error = NULL;
2326
2327         __debug__ (G_STRFUNC);
2328
2329         /* We should not have gotten here if we're offline. */
2330         g_return_val_if_fail (
2331                 e_backend_get_online (E_BACKEND (authenticator)),
2332                 E_SOURCE_AUTHENTICATION_ERROR);
2333
2334         /* Nor should we have gotten here if we're already authorized. */
2335         g_return_val_if_fail (
2336                 !backend_is_authorized (E_BOOK_BACKEND (authenticator)),
2337                 E_SOURCE_AUTHENTICATION_ERROR);
2338
2339         priv = E_BOOK_BACKEND_GOOGLE (authenticator)->priv;
2340
2341         source = e_backend_get_source (E_BACKEND (authenticator));
2342         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2343         auth_extension = e_source_get_extension (source, extension_name);
2344         user = e_source_authentication_dup_user (auth_extension);
2345
2346         gdata_client_login_authorizer_authenticate (
2347                 GDATA_CLIENT_LOGIN_AUTHORIZER (priv->authorizer),
2348                 user, password->str, cancellable, &local_error);
2349
2350         g_free (user);
2351
2352         if (local_error == NULL) {
2353                 result = E_SOURCE_AUTHENTICATION_ACCEPTED;
2354
2355         } else if (g_error_matches (
2356                 local_error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR,
2357                 GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_BAD_AUTHENTICATION)) {
2358
2359                 g_clear_error (&local_error);
2360                 result = E_SOURCE_AUTHENTICATION_REJECTED;
2361
2362         } else {
2363                 g_propagate_error (error, local_error);
2364                 result = E_SOURCE_AUTHENTICATION_ERROR;
2365         }
2366
2367         return result;
2368 }
2369
2370 static void
2371 e_book_backend_google_class_init (EBookBackendGoogleClass *class)
2372 {
2373         GObjectClass *object_class = G_OBJECT_CLASS (class);
2374         EBookBackendClass *backend_class = E_BOOK_BACKEND_CLASS (class);
2375
2376         g_type_class_add_private (class, sizeof (EBookBackendGooglePrivate));
2377
2378         /* Set the virtual methods. */
2379         backend_class->open                     = e_book_backend_google_open;
2380         backend_class->get_backend_property     = e_book_backend_google_get_backend_property;
2381         backend_class->start_book_view          = e_book_backend_google_start_book_view;
2382         backend_class->stop_book_view           = e_book_backend_google_stop_book_view;
2383         backend_class->remove                   = e_book_backend_google_remove;
2384         backend_class->create_contacts          = e_book_backend_google_create_contacts;
2385         backend_class->remove_contacts          = e_book_backend_google_remove_contacts;
2386         backend_class->modify_contacts          = e_book_backend_google_modify_contacts;
2387         backend_class->get_contact              = e_book_backend_google_get_contact;
2388         backend_class->get_contact_list         = e_book_backend_google_get_contact_list;
2389         backend_class->get_contact_list_uids    = e_book_backend_google_get_contact_list_uids;
2390
2391         object_class->dispose  = e_book_backend_google_dispose;
2392         object_class->finalize = e_book_backend_google_finalize;
2393
2394         __e_book_backend_google_debug__ = g_getenv ("GOOGLE_BACKEND_DEBUG") ? TRUE : FALSE;
2395 }
2396
2397 static void
2398 e_book_backend_google_source_authenticator_init (ESourceAuthenticatorInterface *interface)
2399 {
2400         interface->try_password_sync = book_backend_google_try_password_sync;
2401 }
2402
2403 static void
2404 e_book_backend_google_init (EBookBackendGoogle *backend)
2405 {
2406         __debug__ (G_STRFUNC);
2407
2408         backend->priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
2409
2410         g_signal_connect (
2411                 backend, "notify::online",
2412                 G_CALLBACK (e_book_backend_google_notify_online_cb), NULL);
2413
2414         /* Set up our EProxy. */
2415         backend->priv->proxy = e_proxy_new ();
2416         e_proxy_setup_proxy (backend->priv->proxy);
2417
2418         g_signal_connect (
2419                 backend->priv->proxy, "changed",
2420                 G_CALLBACK (proxy_settings_changed), backend);
2421 }
2422
2423 static void
2424 data_book_error_from_gdata_error (GError **dest_err,
2425                                   const GError *error)
2426 {
2427         if (!error || !dest_err)
2428                 return;
2429
2430         /* only last error is used */
2431         g_clear_error (dest_err);
2432
2433         if (error->domain == GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR) {
2434                 /* Authentication errors */
2435                 switch (error->code) {
2436                 case GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_BAD_AUTHENTICATION:
2437                         g_propagate_error (dest_err, EDB_ERROR (AUTHENTICATION_FAILED));
2438                         return;
2439                 case GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_NOT_VERIFIED:
2440                 case GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_TERMS_NOT_AGREED:
2441                 case GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_CAPTCHA_REQUIRED:
2442                 case GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_DELETED:
2443                 case GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_DISABLED:
2444                         g_propagate_error (dest_err, EDB_ERROR (PERMISSION_DENIED));
2445                         return;
2446                 case GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_SERVICE_DISABLED:
2447                         g_propagate_error (dest_err, EDB_ERROR (REPOSITORY_OFFLINE));
2448                         return;
2449                 default:
2450                         break;
2451                 }
2452         } else if (error->domain == GDATA_SERVICE_ERROR) {
2453                 /* General service errors */
2454                 switch (error->code) {
2455                 case GDATA_SERVICE_ERROR_UNAVAILABLE:
2456                         g_propagate_error (dest_err, EDB_ERROR (REPOSITORY_OFFLINE));
2457                         return;
2458                 case GDATA_SERVICE_ERROR_PROTOCOL_ERROR:
2459                         g_propagate_error (dest_err, e_data_book_create_error (E_DATA_BOOK_STATUS_INVALID_QUERY, error->message));
2460                         return;
2461                 case GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED:
2462                         g_propagate_error (dest_err, EDB_ERROR (CONTACTID_ALREADY_EXISTS));
2463                         return;
2464                 case GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED:
2465                         g_propagate_error (dest_err, EDB_ERROR (AUTHENTICATION_REQUIRED));
2466                         return;
2467                 case GDATA_SERVICE_ERROR_NOT_FOUND:
2468                         g_propagate_error (dest_err, EDB_ERROR (CONTACT_NOT_FOUND));
2469                         return;
2470                 case GDATA_SERVICE_ERROR_CONFLICT:
2471                         g_propagate_error (dest_err, EDB_ERROR (CONTACTID_ALREADY_EXISTS));
2472                         return;
2473                 case GDATA_SERVICE_ERROR_FORBIDDEN:
2474                         g_propagate_error (dest_err, EDB_ERROR (QUERY_REFUSED));
2475                         return;
2476                 case GDATA_SERVICE_ERROR_BAD_QUERY_PARAMETER:
2477                         g_propagate_error (dest_err, e_data_book_create_error (E_DATA_BOOK_STATUS_INVALID_QUERY, error->message));
2478                         return;
2479                 default:
2480                         break;
2481                 }
2482         }
2483
2484         g_propagate_error (dest_err, e_data_book_create_error (E_DATA_BOOK_STATUS_OTHER_ERROR, error->message));
2485 }