1 /* e-book-backend-google.c - Google contact backendy.
3 * Copyright (C) 2008 Joergen Scheibengruber
4 * Copyright (C) 2010, 2011 Philip Withnall
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.
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
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.
20 * Author: Joergen Scheibengruber <joergen.scheibengruber AT googlemail.com>
21 * Philip Withnall <philip@tecnocode.co.uk>
28 #include <glib/gi18n-lib.h>
29 #include <gdata/gdata.h>
31 #include "e-book-backend-google.h"
32 #include "e-book-google-utils.h"
35 #include "e-gdata-goa-authorizer.h"
38 #define E_BOOK_BACKEND_GOOGLE_GET_PRIVATE(obj) \
39 (G_TYPE_INSTANCE_GET_PRIVATE \
40 ((obj), E_TYPE_BOOK_BACKEND_GOOGLE, EBookBackendGooglePrivate))
42 #define CLIENT_ID "evolution-client-0.1.0"
44 #define URI_GET_CONTACTS "https://www.google.com/m8/feeds/contacts/default/full"
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)
49 /* Forward Declarations */
50 static void e_book_backend_google_source_authenticator_init
51 (ESourceAuthenticatorInterface *interface);
53 G_DEFINE_TYPE_WITH_CODE (
55 e_book_backend_google,
57 G_IMPLEMENT_INTERFACE (
58 E_TYPE_SOURCE_AUTHENTICATOR,
59 e_book_backend_google_source_authenticator_init))
61 struct _EBookBackendGooglePrivate {
64 EBookBackendCache *cache;
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;
77 GDataAuthorizer *authorizer;
78 GDataService *service;
80 guint refresh_interval;
82 /* If views are open we will send out signals in an idle_handler */
87 /* Map of active opids to GCancellables */
88 GHashTable *cancellables;
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)
94 static void data_book_error_from_gdata_error (GError **dest_err, const GError *error);
97 migrate_cache (EBookBackendCache *cache)
100 const gchar *version_key = "book-cache-version";
102 g_return_if_fail (cache != NULL);
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");
113 cache_init (EBookBackend *backend)
115 EBookBackendGooglePrivate *priv;
116 const gchar *cache_dir;
119 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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);
126 migrate_cache (priv->cache);
130 cache_add_contact (EBookBackend *backend,
133 EBookBackendGooglePrivate *priv;
136 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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);
147 cache_remove_contact (EBookBackend *backend,
150 EBookBackendGooglePrivate *priv;
152 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
154 return e_book_backend_cache_remove_contact (priv->cache, uid);
158 cache_has_contact (EBookBackend *backend,
161 EBookBackendGooglePrivate *priv;
163 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
165 return e_book_backend_cache_check_contact (priv->cache, uid);
169 cache_get_contact (EBookBackend *backend,
173 EBookBackendGooglePrivate *priv;
176 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
178 contact = e_book_backend_cache_get_contact (priv->cache, uid);
181 const gchar *entry_xml, *edit_uri = NULL;
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));
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);
193 e_contact_remove_gdata_entry_xml (contact);
200 cache_get_contacts (EBookBackend *backend)
202 EBookBackendGooglePrivate *priv;
203 GList *contacts, *iter;
205 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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);
215 cache_freeze (EBookBackend *backend)
217 EBookBackendGooglePrivate *priv;
219 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
221 e_file_cache_freeze_changes (E_FILE_CACHE (priv->cache));
225 cache_thaw (EBookBackend *backend)
227 EBookBackendGooglePrivate *priv;
229 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
231 e_file_cache_thaw_changes (E_FILE_CACHE (priv->cache));
235 cache_get_last_update (EBookBackend *backend)
237 EBookBackendGooglePrivate *priv;
239 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
241 return e_book_backend_cache_get_time (priv->cache);
245 cache_get_last_update_tv (EBookBackend *backend,
248 EBookBackendGooglePrivate *priv;
252 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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);
262 cache_set_last_update (EBookBackend *backend,
265 EBookBackendGooglePrivate *priv;
268 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
270 _time = g_time_val_to_iso8601 (tv);
271 e_book_backend_cache_set_time (priv->cache, _time);
276 cache_needs_update (EBookBackend *backend,
277 guint *remaining_secs)
279 EBookBackendGooglePrivate *priv;
280 GTimeVal last, current;
284 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
287 *remaining_secs = G_MAXUINT;
289 /* We never want to update in offline mode */
290 if (!e_backend_get_online (E_BACKEND (backend)))
293 rv = cache_get_last_update_tv (backend, &last);
298 g_get_current_time (¤t);
299 if (last.tv_sec > current.tv_sec) {
300 g_warning ("last update is in the feature?");
302 /* Do an update so we can fix this */
305 diff = current.tv_sec - last.tv_sec;
307 if (diff >= priv->refresh_interval)
311 *remaining_secs = priv->refresh_interval - diff;
313 __debug__ ("No update needed. Next update needed in %d secs", priv->refresh_interval - diff);
319 backend_is_authorized (EBookBackend *backend)
321 EBookBackendGooglePrivate *priv;
323 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
325 if (priv->service == NULL)
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))
337 return gdata_service_is_authorized (priv->service);
341 on_contact_added (EBookBackend *backend,
344 EBookBackendGooglePrivate *priv;
347 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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));
354 on_contact_removed (EBookBackend *backend,
357 EBookBackendGooglePrivate *priv;
360 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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));
367 on_contact_changed (EBookBackend *backend,
370 EBookBackendGooglePrivate *priv;
373 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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));
379 static GCancellable *
380 start_operation (EBookBackend *backend,
382 GCancellable *cancellable,
383 const gchar *message)
385 EBookBackendGooglePrivate *priv;
388 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
390 /* Insert the operation into the set of active cancellable operations */
392 g_object_ref (cancellable);
394 cancellable = g_cancellable_new ();
395 g_hash_table_insert (priv->cancellables, GUINT_TO_POINTER (opid), g_object_ref (cancellable));
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);
405 finish_operation (EBookBackend *backend,
407 const GError *gdata_error)
409 EBookBackendGooglePrivate *priv;
410 GError *book_error = NULL;
412 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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);
419 if (g_hash_table_remove (priv->cancellables, GUINT_TO_POINTER (opid))) {
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);
427 g_clear_error (&book_error);
431 process_contact_finish (EBookBackend *backend,
434 EContact *new_contact;
437 __debug__ (G_STRFUNC);
439 was_cached = cache_has_contact (backend, gdata_entry_get_id (entry));
440 new_contact = cache_add_contact (backend, entry);
442 if (was_cached == TRUE) {
443 on_contact_changed (backend, new_contact);
445 on_contact_added (backend, new_contact);
448 g_object_unref (new_contact);
452 EBookBackend *backend;
453 GCancellable *cancellable;
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;
462 check_get_new_contacts_finished (GetContactsData *data)
464 __debug__ (G_STRFUNC);
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);
473 __debug__ ("Proceeding with check_get_new_contacts_finished() for data: %p.", data);
475 finish_operation (data->backend, -1, data->gdata_error);
478 g_object_unref (data->cancellable);
479 g_object_unref (data->backend);
480 g_clear_error (&data->gdata_error);
482 g_slice_free (GetContactsData, data);
486 GetContactsData *parent_data;
488 GCancellable *cancellable;
489 gulong cancelled_handle;
493 process_contact_photo_cancelled_cb (GCancellable *parent_cancellable,
494 GCancellable *photo_cancellable)
496 __debug__ (G_STRFUNC);
498 g_cancellable_cancel (photo_cancellable);
502 process_contact_photo_cb (GDataContactsContact *gdata_contact,
503 GAsyncResult *async_result,
506 EBookBackend *backend = data->parent_data->backend;
507 guint8 *photo_data = NULL;
509 gchar *photo_content_type = NULL;
510 GError *error = NULL;
512 __debug__ (G_STRFUNC);
514 /* Finish downloading the photo */
515 photo_data = gdata_contacts_contact_get_photo_finish (gdata_contact, async_result, &photo_length, &photo_content_type, &error);
518 EContactPhoto *photo;
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;
527 g_object_set_data_full (G_OBJECT (gdata_contact), "photo", photo, (GDestroyNotify) e_contact_photo_free);
530 photo_content_type = NULL;
533 __debug__ ("Downloading contact photo for '%s' failed: %s", gdata_entry_get_id (GDATA_ENTRY (gdata_contact)), error->message);
534 g_error_free (error);
537 process_contact_finish (backend, GDATA_ENTRY (gdata_contact));
540 g_free (photo_content_type);
542 /* Disconnect from the cancellable. */
543 g_cancellable_disconnect (data->parent_data->cancellable, data->cancelled_handle);
544 g_object_unref (data->cancellable);
546 data->parent_data->num_contacts_pending_photos--;
547 check_get_new_contacts_finished (data->parent_data);
549 g_slice_free (PhotoData, data);
553 process_contact_cb (GDataEntry *entry,
556 GetContactsData *data)
558 EBookBackendGooglePrivate *priv;
559 EBookBackend *backend = data->backend;
560 gboolean is_deleted, is_cached;
563 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
565 __debug__ (G_STRFUNC);
566 uid = gdata_entry_get_id (entry);
567 is_deleted = gdata_contacts_contact_is_deleted (GDATA_CONTACTS_CONTACT (entry));
569 is_cached = cache_has_contact (backend, uid);
571 /* Do we have this item in our cache? */
573 cache_remove_contact (backend, uid);
574 on_contact_removed (backend, uid);
577 gchar *old_photo_etag = NULL;
578 const gchar *new_photo_etag;
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;
586 old_contact = cache_get_contact (backend, uid, NULL);
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;
592 /* Attach the old photo to the new contact. */
593 photo = e_contact_get (old_contact, E_CONTACT_PHOTO);
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);
601 g_object_unref (old_contact);
604 new_photo_etag = gdata_contacts_contact_get_photo_etag (GDATA_CONTACTS_CONTACT (entry));
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;
611 photo_data = g_slice_new (PhotoData);
612 photo_data->parent_data = data;
614 /* Increment the count of contacts whose photos we're waiting for. */
615 data->num_contacts_pending_photos++;
617 /* Cancel downloading if the get_new_contacts() operation is cancelled. */
618 cancellable = g_cancellable_new ();
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);
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);
629 g_object_unref (cancellable);
630 g_free (old_photo_etag);
635 g_free (old_photo_etag);
637 /* Since we're not downloading a photo, add the contact to the cache now. */
638 process_contact_finish (backend, entry);
643 get_new_contacts_cb (GDataService *service,
644 GAsyncResult *result,
645 GetContactsData *data)
647 EBookBackend *backend = data->backend;
649 GError *gdata_error = NULL;
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));
659 g_object_unref (feed);
662 /* Finish updating the cache */
663 GTimeVal current_time;
664 g_get_current_time (¤t_time);
665 cache_set_last_update (backend, ¤t_time);
668 /* Thaw the cache again */
669 cache_thaw (backend);
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);
679 get_new_contacts (EBookBackend *backend)
681 EBookBackendGooglePrivate *priv;
685 GCancellable *cancellable;
686 GetContactsData *data;
688 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
690 __debug__ (G_STRFUNC);
691 g_return_if_fail (backend_is_authorized (backend));
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);
698 /* Prevent the cache writing each change to disk individually (thawed in get_new_contacts_cb()) */
699 cache_freeze (backend);
701 /* Build our query */
702 query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
704 gdata_query_set_updated_min (query, updated.tv_sec);
705 gdata_contacts_query_set_show_deleted (GDATA_CONTACTS_QUERY (query), TRUE);
708 /* Query for new contacts asynchronously */
709 cancellable = start_operation (backend, -1, NULL, _("Querying for updated contacts…"));
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;
718 gdata_contacts_service_query_contacts_async (
719 GDATA_CONTACTS_SERVICE (priv->service),
722 (GDataQueryProgressCallback) process_contact_cb,
724 (GDestroyNotify) NULL,
725 (GAsyncReadyCallback) get_new_contacts_cb,
728 g_object_unref (cancellable);
729 g_object_unref (query);
733 process_group (GDataEntry *entry,
736 EBookBackend *backend)
738 EBookBackendGooglePrivate *priv;
739 const gchar *uid, *system_group_id;
743 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
745 __debug__ (G_STRFUNC);
746 uid = gdata_entry_get_id (entry);
747 name = e_contact_sanitise_google_group_name (entry);
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));
752 if (system_group_id) {
753 __debug__ ("Processing %ssystem group %s, %s", is_deleted ? "(deleted) " : "", system_group_id, uid);
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);
760 gchar *entry_id, *system_group_id_dup;
762 entry_id = e_contact_sanitise_google_group_id (uid);
763 system_group_id_dup = g_strdup (system_group_id);
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);
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));
774 g_warn_if_fail (name != NULL);
776 name = g_strdup (system_group_id);
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);
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));
793 get_groups_cb (GDataService *service,
794 GAsyncResult *result,
795 EBookBackend *backend)
797 EBookBackendGooglePrivate *priv;
799 GError *gdata_error = NULL;
801 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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));
811 g_object_unref (feed);
814 /* Update the update time */
815 g_get_current_time (&(priv->last_groups_update));
818 finish_operation (backend, -2, gdata_error);
819 g_object_unref (backend);
821 g_clear_error (&gdata_error);
825 get_groups (EBookBackend *backend)
827 EBookBackendGooglePrivate *priv;
829 GCancellable *cancellable;
831 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
833 __debug__ (G_STRFUNC);
834 g_return_if_fail (backend_is_authorized (backend));
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);
843 g_object_ref (backend);
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),
851 (GDataQueryProgressCallback) process_group,
853 (GDestroyNotify) NULL,
854 (GAsyncReadyCallback) get_groups_cb,
857 g_object_unref (cancellable);
858 g_object_unref (query);
862 get_groups_sync (EBookBackend *backend,
863 GCancellable *cancellable)
865 EBookBackendGooglePrivate *priv;
869 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
871 __debug__ (G_STRFUNC);
872 g_return_if_fail (backend_is_authorized (backend));
874 /* Build our query, always fetch all of them */
875 query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
877 /* Run the query synchronously */
878 feed = gdata_contacts_service_query_groups (
879 GDATA_CONTACTS_SERVICE (priv->service),
882 (GDataQueryProgressCallback) process_group,
887 g_object_unref (feed);
889 g_object_unref (query);
893 create_group (EBookBackend *backend,
894 const gchar *category_name,
897 EBookBackendGooglePrivate *priv;
898 GDataEntry *group, *new_group;
900 const gchar *system_group_id;
902 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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);
908 g_return_val_if_fail (group_entry_id != NULL, NULL);
910 return g_strdup (group_entry_id);
913 group = GDATA_ENTRY (gdata_contacts_group_new (NULL));
915 gdata_entry_set_title (group, category_name);
916 __debug__ ("Creating group %s", category_name);
918 /* Insert the new group */
919 new_group = GDATA_ENTRY (gdata_contacts_service_insert_group (GDATA_CONTACTS_SERVICE (priv->service), GDATA_CONTACTS_GROUP (group),
921 g_object_unref (group);
923 if (new_group == NULL)
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);
932 __debug__ ("...got UID %s", uid);
938 _create_group (const gchar *category_name,
942 return create_group (E_BOOK_BACKEND (user_data), category_name, error);
945 static gboolean cache_refresh_if_needed (EBookBackend *backend);
948 on_refresh_timeout (EBookBackend *backend)
950 EBookBackendGooglePrivate *priv;
952 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
954 __debug__ (G_STRFUNC);
956 priv->refresh_id = 0;
957 if (priv->bookviews != NULL)
958 cache_refresh_if_needed (backend);
964 cache_refresh_if_needed (EBookBackend *backend)
966 EBookBackendGooglePrivate *priv;
967 guint remaining_secs;
968 gboolean install_timeout;
971 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
973 __debug__ (G_STRFUNC);
975 is_online = e_backend_get_online (E_BACKEND (backend));
977 if (!is_online || !backend_is_authorized (backend)) {
978 __debug__ ("We are not connected to Google%s.", (!is_online) ? " (offline mode)" : "");
982 install_timeout = (priv->bookviews != NULL && priv->refresh_interval > 0 && 0 == priv->refresh_id);
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);
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);
1001 proxy_settings_changed (EProxy *proxy,
1002 EBookBackend *backend)
1004 EBookBackendGooglePrivate *priv;
1005 SoupURI *proxy_uri = NULL;
1007 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1009 if (!priv || !priv->service)
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);
1019 request_authorization (EBookBackend *backend,
1020 GCancellable *cancellable,
1023 EBookBackendGooglePrivate *priv;
1025 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1027 /* Make sure we have the GDataService configured
1028 * before requesting authorization. */
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;
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);
1046 if (priv->authorizer == NULL) {
1047 GDataClientLoginAuthorizer *authorizer;
1049 authorizer = gdata_client_login_authorizer_new (
1050 CLIENT_ID, GDATA_TYPE_CONTACTS_SERVICE);
1051 priv->authorizer = GDATA_AUTHORIZER (authorizer);
1054 if (priv->service == NULL) {
1055 GDataContactsService *contacts_service;
1058 gdata_contacts_service_new (priv->authorizer);
1059 priv->service = GDATA_SERVICE (contacts_service);
1060 proxy_settings_changed (priv->proxy, backend);
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))
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);
1079 EBookBackend *backend;
1082 GCancellable *cancellable;
1083 GDataContactsContact *new_contact;
1084 EContactPhoto *photo;
1085 } CreateContactData;
1088 create_contact_finish (CreateContactData *data,
1089 GDataContactsContact *new_contact,
1090 const GError *gdata_error)
1092 __debug__ (G_STRFUNC);
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));
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);
1105 GError *book_error = NULL;
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);
1112 finish_operation (data->backend, data->opid, gdata_error);
1114 if (data->photo != NULL) {
1115 e_contact_photo_free (data->photo);
1118 if (data->new_contact != NULL) {
1119 g_object_unref (data->new_contact);
1122 g_object_unref (data->cancellable);
1123 g_object_unref (data->book);
1124 g_object_unref (data->backend);
1125 g_slice_free (CreateContactData, data);
1129 create_contact_photo_query_cb (GDataService *service,
1130 GAsyncResult *async_result,
1131 CreateContactData *data)
1133 GDataEntry *queried_contact;
1134 EContactPhoto *photo;
1135 GError *gdata_error = NULL;
1137 __debug__ (G_STRFUNC);
1139 queried_contact = gdata_service_query_single_entry_finish (service, async_result, &gdata_error);
1141 if (gdata_error != NULL) {
1142 __debug__ ("Querying for created contact failed: %s", gdata_error->message);
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);
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");
1156 if (photo != NULL) {
1157 g_object_set_data_full (G_OBJECT (queried_contact), "photo", photo, (GDestroyNotify) e_contact_photo_free);
1161 create_contact_finish (data, GDATA_CONTACTS_CONTACT (queried_contact), gdata_error);
1163 g_clear_error (&gdata_error);
1165 if (queried_contact != NULL) {
1166 g_object_unref (queried_contact);
1171 create_contact_photo_cb (GDataContactsContact *contact,
1172 GAsyncResult *async_result,
1173 CreateContactData *data)
1175 EBookBackendGooglePrivate *priv;
1176 GError *gdata_error = NULL;
1178 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (data->backend);
1180 __debug__ (G_STRFUNC);
1182 gdata_contacts_contact_set_photo_finish (contact, async_result, &gdata_error);
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);
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);
1197 __debug__ ("Uploading initial contact photo for '%s' failed: %s", gdata_entry_get_id (GDATA_ENTRY (contact)), gdata_error->message);
1200 /* Respond to the initial create contact operation. */
1201 create_contact_finish (data, contact, gdata_error);
1203 g_clear_error (&gdata_error);
1207 create_contact_cb (GDataService *service,
1208 GAsyncResult *result,
1209 CreateContactData *data)
1211 GError *gdata_error = NULL;
1212 GDataEntry *new_contact;
1214 __debug__ (G_STRFUNC);
1216 new_contact = gdata_service_insert_entry_finish (service, result, &gdata_error);
1219 __debug__ ("Creating contact failed: %s", gdata_error->message);
1223 data->new_contact = g_object_ref (new_contact);
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);
1236 create_contact_finish (data, GDATA_CONTACTS_CONTACT (new_contact), gdata_error);
1238 g_clear_error (&gdata_error);
1240 if (new_contact != NULL) {
1241 g_object_unref (new_contact);
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().
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().
1255 e_book_backend_google_create_contacts (EBookBackend *backend,
1258 GCancellable *cancellable,
1259 const GSList *vcards)
1261 EBookBackendGooglePrivate *priv;
1265 CreateContactData *data;
1266 const gchar *vcard_str = (const gchar *) vcards->data;
1268 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
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")),
1280 __debug__ (G_STRFUNC);
1282 __debug__ ("Creating: %s", vcard_str);
1284 if (!e_backend_get_online (E_BACKEND (backend))) {
1285 e_data_book_respond_create_contacts (book, opid, EDB_ERROR (OFFLINE_UNAVAILABLE), NULL);
1289 g_return_if_fail (backend_is_authorized (backend));
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);
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);
1301 /* Debug XML output */
1302 xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
1303 __debug__ ("new entry with xml: %s", xml);
1306 /* Insert the entry on the server asynchronously */
1307 cancellable = start_operation (backend, opid, cancellable, _("Creating new contact…"));
1309 data = g_slice_new (CreateContactData);
1310 data->backend = g_object_ref (backend);
1311 data->book = g_object_ref (book);
1313 data->cancellable = g_object_ref (cancellable);
1314 data->new_contact = NULL;
1315 data->photo = g_object_steal_data (G_OBJECT (entry), "photo");
1317 gdata_contacts_service_insert_contact_async (GDATA_CONTACTS_SERVICE (priv->service), GDATA_CONTACTS_CONTACT (entry), cancellable,
1318 (GAsyncReadyCallback) create_contact_cb, data);
1320 g_object_unref (cancellable);
1321 g_object_unref (entry);
1325 EBookBackend *backend;
1329 } RemoveContactData;
1332 remove_contact_cb (GDataService *service,
1333 GAsyncResult *result,
1334 RemoveContactData *data)
1336 GError *gdata_error = NULL;
1340 __debug__ (G_STRFUNC);
1342 success = gdata_service_delete_entry_finish (service, result, &gdata_error);
1343 finish_operation (data->backend, data->opid, gdata_error);
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);
1351 e_data_book_respond_remove_contacts (data->book, data->opid, book_error, NULL);
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);
1362 g_object_unref (data->book);
1363 g_object_unref (data->backend);
1364 g_slice_free (RemoveContactData, data);
1368 e_book_backend_google_remove_contacts (EBookBackend *backend,
1371 GCancellable *cancellable,
1372 const GSList *id_list)
1374 EBookBackendGooglePrivate *priv;
1375 const gchar *uid = id_list->data;
1376 GDataEntry *entry = NULL;
1377 EContact *cached_contact;
1378 RemoveContactData *data;
1380 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1382 __debug__ (G_STRFUNC);
1384 if (!e_backend_get_online (E_BACKEND (backend))) {
1385 e_data_book_respond_remove_contacts (book, opid, EDB_ERROR (OFFLINE_UNAVAILABLE), NULL);
1389 g_return_if_fail (backend_is_authorized (backend));
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")),
1400 g_return_if_fail (!id_list->next);
1402 /* Get the contact and associated GDataEntry from the cache */
1403 cached_contact = cache_get_contact (backend, uid, &entry);
1405 if (!cached_contact) {
1406 __debug__ ("Deleting contact %s failed: Contact not found in cache.", uid);
1408 e_data_book_respond_remove_contacts (book, opid, EDB_ERROR (CONTACT_NOT_FOUND), NULL);
1412 g_object_unref (cached_contact);
1414 /* Remove the contact from the cache */
1415 cache_remove_contact (backend, uid);
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);
1422 data->uid = g_strdup (uid);
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);
1439 EBookBackend *backend;
1442 GCancellable *cancellable;
1443 GDataContactsContact *new_contact;
1444 EContactPhoto *photo;
1445 PhotoOperation photo_operation;
1446 } ModifyContactData;
1449 modify_contact_finish (ModifyContactData *data,
1450 GDataContactsContact *new_contact,
1451 const GError *gdata_error)
1453 EContact *e_contact;
1455 __debug__ (G_STRFUNC);
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);
1465 GError *book_error = NULL;
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);
1472 finish_operation (data->backend, data->opid, gdata_error);
1474 if (data->photo != NULL) {
1475 e_contact_photo_free (data->photo);
1478 if (data->new_contact != NULL) {
1479 g_object_unref (data->new_contact);
1482 g_object_unref (data->cancellable);
1483 g_object_unref (data->book);
1484 g_object_unref (data->backend);
1485 g_slice_free (ModifyContactData, data);
1489 modify_contact_photo_query_cb (GDataService *service,
1490 GAsyncResult *async_result,
1491 ModifyContactData *data)
1493 GDataEntry *queried_contact;
1494 EContactPhoto *photo;
1495 GError *gdata_error = NULL;
1497 __debug__ (G_STRFUNC);
1499 queried_contact = gdata_service_query_single_entry_finish (service, async_result, &gdata_error);
1501 if (gdata_error != NULL) {
1502 __debug__ ("Querying for modified contact failed: %s", gdata_error->message);
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);
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");
1516 if (photo != NULL) {
1517 g_object_set_data_full (G_OBJECT (queried_contact), "photo", photo, (GDestroyNotify) e_contact_photo_free);
1521 modify_contact_finish (data, GDATA_CONTACTS_CONTACT (queried_contact), gdata_error);
1523 g_clear_error (&gdata_error);
1525 if (queried_contact != NULL) {
1526 g_object_unref (queried_contact);
1531 modify_contact_photo_cb (GDataContactsContact *contact,
1532 GAsyncResult *async_result,
1533 ModifyContactData *data)
1535 EBookBackendGooglePrivate *priv;
1536 GError *gdata_error = NULL;
1538 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (data->backend);
1540 __debug__ (G_STRFUNC);
1542 gdata_contacts_contact_set_photo_finish (contact, async_result, &gdata_error);
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);
1550 g_object_set_data (G_OBJECT (contact), "photo", NULL);
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);
1561 __debug__ ("Uploading modified contact photo for '%s' failed: %s", gdata_entry_get_id (GDATA_ENTRY (contact)), gdata_error->message);
1564 /* Respond to the initial modify contact operation. */
1565 modify_contact_finish (data, contact, gdata_error);
1567 g_clear_error (&gdata_error);
1571 modify_contact_cb (GDataService *service,
1572 GAsyncResult *result,
1573 ModifyContactData *data)
1575 GDataEntry *new_contact;
1576 GError *gdata_error = NULL;
1578 __debug__ (G_STRFUNC);
1580 new_contact = gdata_service_update_entry_finish (service, result, &gdata_error);
1583 __debug__ ("Modifying contact failed: %s", gdata_error->message);
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);
1594 data->new_contact = g_object_ref (new_contact);
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) {
1600 /* Do nothing apart from copy the photo stolen from the old GDataContactsContact to the updated one we've just received from
1602 g_object_set_data_full (G_OBJECT (new_contact), "photo", data->photo, (GDestroyNotify) e_contact_photo_free);
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);
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);
1621 g_assert_not_reached ();
1625 modify_contact_finish (data, GDATA_CONTACTS_CONTACT (new_contact), gdata_error);
1627 g_clear_error (&gdata_error);
1629 if (new_contact != NULL) {
1630 g_object_unref (new_contact);
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().
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().
1644 e_book_backend_google_modify_contacts (EBookBackend *backend,
1647 GCancellable *cancellable,
1648 const GSList *vcards)
1650 EBookBackendGooglePrivate *priv;
1651 EContact *contact, *cached_contact;
1652 EContactPhoto *old_photo, *new_photo;
1653 GDataEntry *entry = NULL;
1655 ModifyContactData *data;
1656 const gchar *vcard_str = vcards->data;
1658 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1660 __debug__ (G_STRFUNC);
1662 __debug__ ("Updating: %s", vcard_str);
1664 if (!e_backend_get_online (E_BACKEND (backend))) {
1665 e_data_book_respond_modify_contacts (book, opid, EDB_ERROR (OFFLINE_UNAVAILABLE), NULL);
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")),
1679 g_return_if_fail (backend_is_authorized (backend));
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);
1685 /* Get the old cached contact with the same UID and its associated GDataEntry */
1686 cached_contact = cache_get_contact (backend, uid, &entry);
1688 if (!cached_contact) {
1689 __debug__ ("Modifying contact failed: Contact with uid %s not found in cache.", uid);
1690 g_object_unref (contact);
1692 e_data_book_respond_modify_contacts (book, opid, EDB_ERROR (CONTACT_NOT_FOUND), NULL);
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);
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);
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);
1711 /* Update the contact on the server asynchronously */
1712 cancellable = start_operation (backend, opid, cancellable, _("Modifying contact…"));
1714 data = g_slice_new (ModifyContactData);
1715 data->backend = g_object_ref (backend);
1716 data->book = g_object_ref (book);
1719 data->cancellable = g_object_ref (cancellable);
1720 data->new_contact = NULL;
1721 data->photo = g_object_steal_data (G_OBJECT (entry), "photo");
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);
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;
1743 data->photo_operation = LEAVE_PHOTO;
1746 if (new_photo != NULL) {
1747 e_contact_photo_free (new_photo);
1750 if (old_photo != NULL) {
1751 e_contact_photo_free (old_photo);
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);
1758 g_object_unref (cached_contact);
1759 g_object_unref (contact);
1760 g_object_unref (entry);
1764 e_book_backend_google_get_contact (EBookBackend *backend,
1767 GCancellable *cancellable,
1773 __debug__ (G_STRFUNC);
1775 /* Get the contact */
1776 contact = cache_get_contact (backend, uid, NULL);
1778 __debug__ ("Getting contact with uid %s failed: Contact not found in cache.", uid);
1780 e_data_book_respond_get_contact (book, opid, EDB_ERROR (CONTACT_NOT_FOUND), NULL);
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);
1788 g_object_unref (contact);
1792 e_book_backend_google_get_contact_list (EBookBackend *backend,
1795 GCancellable *cancellable,
1798 EBookBackendSExp *sexp;
1799 GList *all_contacts;
1800 GSList *filtered_contacts = NULL;
1802 __debug__ (G_STRFUNC);
1804 /* Get all contacts */
1805 sexp = e_book_backend_sexp_new (query);
1806 all_contacts = cache_get_contacts (backend);
1808 for (; all_contacts; all_contacts = g_list_delete_link (all_contacts, all_contacts)) {
1809 EContact *contact = all_contacts->data;
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);
1817 g_object_unref (contact);
1820 g_object_unref (sexp);
1822 e_data_book_respond_get_contact_list (book, opid, NULL, filtered_contacts);
1824 g_slist_foreach (filtered_contacts, (GFunc) g_free, NULL);
1825 g_slist_free (filtered_contacts);
1829 e_book_backend_google_get_contact_list_uids (EBookBackend *backend,
1832 GCancellable *cancellable,
1835 EBookBackendSExp *sexp;
1836 GList *all_contacts;
1837 GSList *filtered_uids = NULL;
1839 __debug__ (G_STRFUNC);
1841 /* Get all contacts */
1842 sexp = e_book_backend_sexp_new (query);
1843 all_contacts = cache_get_contacts (backend);
1845 for (; all_contacts; all_contacts = g_list_delete_link (all_contacts, all_contacts)) {
1846 EContact *contact = all_contacts->data;
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));
1853 g_object_unref (contact);
1856 g_object_unref (sexp);
1858 e_data_book_respond_get_contact_list_uids (book, opid, NULL, filtered_uids);
1860 g_slist_foreach (filtered_uids, (GFunc) g_free, NULL);
1861 g_slist_free (filtered_uids);
1865 on_refresh_idle (EBookBackend *backend)
1867 EBookBackendGooglePrivate *priv;
1869 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1872 cache_refresh_if_needed (backend);
1878 e_book_backend_google_start_book_view (EBookBackend *backend,
1879 EDataBookView *bookview)
1881 EBookBackendGooglePrivate *priv;
1882 GList *cached_contacts;
1883 GError *error = NULL;
1885 g_return_if_fail (E_IS_BOOK_BACKEND_GOOGLE (backend));
1886 g_return_if_fail (E_IS_DATA_BOOK_VIEW (bookview));
1888 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1890 __debug__ (G_STRFUNC);
1892 priv->bookviews = g_list_append (priv->bookviews, bookview);
1894 e_data_book_view_ref (bookview);
1895 e_data_book_view_notify_progress (bookview, -1, _("Loading…"));
1897 /* Ensure that we're ready to support a view */
1898 cache_refresh_if_needed (backend);
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);
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);
1913 /* Get the contacts */
1914 cached_contacts = cache_get_contacts (backend);
1915 __debug__ ("%d contacts found in cache", g_list_length (cached_contacts));
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);
1925 /* This function frees the GError passed to it. */
1926 e_data_book_view_notify_complete (bookview, error);
1930 e_book_backend_google_stop_book_view (EBookBackend *backend,
1931 EDataBookView *bookview)
1933 EBookBackendGooglePrivate *priv;
1936 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1938 __debug__ (G_STRFUNC);
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);
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;
1954 e_book_backend_google_remove (EBookBackend *backend,
1957 GCancellable *cancellable)
1959 __debug__ (G_STRFUNC);
1960 e_data_book_respond_remove (book, opid, NULL);
1964 e_book_backend_google_open (EBookBackend *backend,
1967 GCancellable *cancellable,
1968 gboolean only_if_exists)
1970 EBookBackendGooglePrivate *priv;
1971 ESourceRefresh *refresh_extension;
1973 guint interval_in_minutes;
1974 const gchar *extension_name;
1976 GError *error = NULL;
1978 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
1980 __debug__ (G_STRFUNC);
1982 if (priv->cancellables && backend_is_authorized (backend)) {
1983 e_book_backend_respond_opened (backend, book, opid, NULL);
1987 source = e_backend_get_source (E_BACKEND (backend));
1989 extension_name = E_SOURCE_EXTENSION_REFRESH;
1990 refresh_extension = e_source_get_extension (source, extension_name);
1992 interval_in_minutes =
1993 e_source_refresh_get_enabled (refresh_extension) ?
1994 e_source_refresh_get_interval_minutes (refresh_extension) : 0;
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);
2005 cache_init (backend);
2006 priv->refresh_interval = interval_in_minutes * 60;
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);
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);
2020 if (request_authorization (backend, cancellable, &error)) {
2021 /* Refresh the authorizer. This may block. */
2022 gdata_authorizer_refresh_authorization (
2023 priv->authorizer, cancellable, &error);
2027 if (!is_online || backend_is_authorized (backend)) {
2029 e_book_backend_notify_readonly (backend, FALSE);
2030 e_book_backend_notify_opened (backend, NULL /* Success */);
2033 /* This function frees the GError passed to it. */
2034 e_data_book_respond_open (book, opid, error);
2038 e_book_backend_google_get_backend_property (EBookBackend *backend,
2041 GCancellable *cancellable,
2042 const gchar *prop_name)
2044 __debug__ (G_STRFUNC);
2046 g_return_if_fail (prop_name != NULL);
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;
2056 const gint supported_fields[] = {
2057 E_CONTACT_FULL_NAME,
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,
2072 E_CONTACT_IM_JABBER,
2077 E_CONTACT_IM_GOOGLE_TALK,
2078 E_CONTACT_IM_GADUGADU,
2079 E_CONTACT_IM_GROUPWISE,
2081 E_CONTACT_ADDRESS_HOME,
2082 E_CONTACT_ADDRESS_WORK,
2083 E_CONTACT_ADDRESS_OTHER,
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,
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,
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,
2161 E_CONTACT_HOMEPAGE_URL,
2163 E_CONTACT_BIRTH_DATE,
2164 E_CONTACT_ANNIVERSARY,
2167 E_CONTACT_CATEGORIES,
2168 #if defined(GDATA_CHECK_VERSION)
2169 #if GDATA_CHECK_VERSION(0, 11, 0)
2170 E_CONTACT_CATEGORY_LIST,
2173 E_CONTACT_CATEGORY_LIST
2176 E_CONTACT_CATEGORY_LIST
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);
2186 fields_str = e_data_book_string_slist_to_comma_string (fields);
2188 e_data_book_respond_get_backend_property (book, opid, NULL, fields_str);
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");
2195 E_BOOK_BACKEND_CLASS (e_book_backend_google_parent_class)->get_backend_property (backend, book, opid, cancellable, prop_name);
2200 google_cancel_all_operations (EBookBackend *backend)
2202 EBookBackendGooglePrivate *priv;
2203 GHashTableIter iter;
2205 GCancellable *cancellable;
2207 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
2209 __debug__ (G_STRFUNC);
2211 if (!priv->cancellables)
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);
2222 e_book_backend_google_notify_online_cb (EBookBackend *backend,
2225 EBookBackendGooglePrivate *priv;
2228 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
2230 __debug__ (G_STRFUNC);
2232 is_online = e_backend_get_online (E_BACKEND (backend));
2233 e_book_backend_notify_online (backend, is_online);
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);
2240 /* Going offline, so cancel all running operations */
2241 google_cancel_all_operations (backend);
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);
2247 /* We can free our service. */
2249 g_object_unref (priv->service);
2250 priv->service = NULL;
2255 e_book_backend_google_dispose (GObject *object)
2257 EBookBackendGooglePrivate *priv;
2259 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (object);
2261 __debug__ (G_STRFUNC);
2263 /* Cancel all outstanding operations */
2264 google_cancel_all_operations (E_BOOK_BACKEND (object));
2266 while (priv->bookviews) {
2267 e_data_book_view_unref (priv->bookviews->data);
2268 priv->bookviews = g_list_delete_link (priv->bookviews, priv->bookviews);
2271 if (priv->idle_id) {
2272 g_source_remove (priv->idle_id);
2277 g_object_unref (priv->service);
2278 priv->service = NULL;
2280 if (priv->authorizer != NULL)
2281 g_object_unref (priv->authorizer);
2282 priv->authorizer = NULL;
2285 g_object_unref (priv->proxy);
2288 g_clear_object (&priv->cache);
2290 G_OBJECT_CLASS (e_book_backend_google_parent_class)->dispose (object);
2294 e_book_backend_google_finalize (GObject *object)
2296 EBookBackendGooglePrivate *priv;
2298 priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (object);
2300 __debug__ (G_STRFUNC);
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);
2310 G_OBJECT_CLASS (e_book_backend_google_parent_class)->finalize (object);
2313 static ESourceAuthenticationResult
2314 book_backend_google_try_password_sync (ESourceAuthenticator *authenticator,
2315 const GString *password,
2316 GCancellable *cancellable,
2319 EBookBackendGooglePrivate *priv;
2320 ESourceAuthentication *auth_extension;
2321 ESourceAuthenticationResult result;
2323 const gchar *extension_name;
2325 GError *local_error = NULL;
2327 __debug__ (G_STRFUNC);
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);
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);
2339 priv = E_BOOK_BACKEND_GOOGLE (authenticator)->priv;
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);
2346 gdata_client_login_authorizer_authenticate (
2347 GDATA_CLIENT_LOGIN_AUTHORIZER (priv->authorizer),
2348 user, password->str, cancellable, &local_error);
2352 if (local_error == NULL) {
2353 result = E_SOURCE_AUTHENTICATION_ACCEPTED;
2355 } else if (g_error_matches (
2356 local_error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR,
2357 GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_BAD_AUTHENTICATION)) {
2359 g_clear_error (&local_error);
2360 result = E_SOURCE_AUTHENTICATION_REJECTED;
2363 g_propagate_error (error, local_error);
2364 result = E_SOURCE_AUTHENTICATION_ERROR;
2371 e_book_backend_google_class_init (EBookBackendGoogleClass *class)
2373 GObjectClass *object_class = G_OBJECT_CLASS (class);
2374 EBookBackendClass *backend_class = E_BOOK_BACKEND_CLASS (class);
2376 g_type_class_add_private (class, sizeof (EBookBackendGooglePrivate));
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;
2391 object_class->dispose = e_book_backend_google_dispose;
2392 object_class->finalize = e_book_backend_google_finalize;
2394 __e_book_backend_google_debug__ = g_getenv ("GOOGLE_BACKEND_DEBUG") ? TRUE : FALSE;
2398 e_book_backend_google_source_authenticator_init (ESourceAuthenticatorInterface *interface)
2400 interface->try_password_sync = book_backend_google_try_password_sync;
2404 e_book_backend_google_init (EBookBackendGoogle *backend)
2406 __debug__ (G_STRFUNC);
2408 backend->priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
2411 backend, "notify::online",
2412 G_CALLBACK (e_book_backend_google_notify_online_cb), NULL);
2414 /* Set up our EProxy. */
2415 backend->priv->proxy = e_proxy_new ();
2416 e_proxy_setup_proxy (backend->priv->proxy);
2419 backend->priv->proxy, "changed",
2420 G_CALLBACK (proxy_settings_changed), backend);
2424 data_book_error_from_gdata_error (GError **dest_err,
2425 const GError *error)
2427 if (!error || !dest_err)
2430 /* only last error is used */
2431 g_clear_error (dest_err);
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));
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));
2446 case GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_SERVICE_DISABLED:
2447 g_propagate_error (dest_err, EDB_ERROR (REPOSITORY_OFFLINE));
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));
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));
2461 case GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED:
2462 g_propagate_error (dest_err, EDB_ERROR (CONTACTID_ALREADY_EXISTS));
2464 case GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED:
2465 g_propagate_error (dest_err, EDB_ERROR (AUTHENTICATION_REQUIRED));
2467 case GDATA_SERVICE_ERROR_NOT_FOUND:
2468 g_propagate_error (dest_err, EDB_ERROR (CONTACT_NOT_FOUND));
2470 case GDATA_SERVICE_ERROR_CONFLICT:
2471 g_propagate_error (dest_err, EDB_ERROR (CONTACTID_ALREADY_EXISTS));
2473 case GDATA_SERVICE_ERROR_FORBIDDEN:
2474 g_propagate_error (dest_err, EDB_ERROR (QUERY_REFUSED));
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));
2484 g_propagate_error (dest_err, e_data_book_create_error (E_DATA_BOOK_STATUS_OTHER_ERROR, error->message));