[evolution-data-server/wip/offline-cache] Make Google book backend derive from EBookMetaBackend
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/wip/offline-cache] Make Google book backend derive from EBookMetaBackend
- Date: Mon, 15 May 2017 17:26:07 +0000 (UTC)
commit bc1f968f344a31fde839055094dfb3624e10c01b
Author: Milan Crha <mcrha redhat com>
Date: Mon May 15 19:25:35 2017 +0200
Make Google book backend derive from EBookMetaBackend
.../backends/google/e-book-backend-google.c | 2398 ++++++--------------
.../backends/google/e-book-backend-google.h | 4 +-
.../backends/google/e-book-google-utils.c | 52 +-
.../backends/google/e-book-google-utils.h | 35 +-
.../backends/webdav/e-book-backend-webdav.c | 53 +-
src/addressbook/libebook-contacts/e-vcard.c | 69 +
src/addressbook/libebook-contacts/e-vcard.h | 6 +
.../backends/gtasks/e-cal-backend-gtasks.c | 6 -
8 files changed, 773 insertions(+), 1850 deletions(-)
---
diff --git a/src/addressbook/backends/google/e-book-backend-google.c
b/src/addressbook/backends/google/e-book-backend-google.c
index 1284c0e..92e9ca6 100644
--- a/src/addressbook/backends/google/e-book-backend-google.c
+++ b/src/addressbook/backends/google/e-book-backend-google.c
@@ -2,6 +2,7 @@
*
* Copyright (C) 2008 Joergen Scheibengruber
* Copyright (C) 2010, 2011 Philip Withnall
+ * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
@@ -31,26 +32,11 @@
#include "e-book-google-utils.h"
#include "e-gdata-oauth2-authorizer.h"
-#define E_BOOK_BACKEND_GOOGLE_GET_PRIVATE(obj) \
- (G_TYPE_INSTANCE_GET_PRIVATE \
- ((obj), E_TYPE_BOOK_BACKEND_GOOGLE, EBookBackendGooglePrivate))
-
-#define CLIENT_ID "evolution-client-0.1.0"
-
#define URI_GET_CONTACTS "https://www.google.com/m8/feeds/contacts/default/full"
-/* This macro was introduced in libgdata 0.11,
- * but we currently only require libgdata 0.10. */
-#ifndef GDATA_CHECK_VERSION
-#define GDATA_CHECK_VERSION(major,minor,micro) 0
-#endif
-
-G_DEFINE_TYPE (EBookBackendGoogle, e_book_backend_google, E_TYPE_BOOK_BACKEND)
+G_DEFINE_TYPE (EBookBackendGoogle, e_book_backend_google, E_TYPE_BOOK_META_BACKEND)
struct _EBookBackendGooglePrivate {
- EBookBackendCache *cache;
- GMutex cache_lock;
-
/* For all the group-related members */
GRecMutex groups_lock;
/* Mapping from group ID to (human readable) group name */
@@ -63,27 +49,25 @@ struct _EBookBackendGooglePrivate {
GHashTable *system_groups_by_entry_id;
/* Time when the groups were last queried */
GTimeVal groups_last_update;
+ /* Did the server-side groups change? If so, re-download the book */
+ gboolean groups_changed;
GDataAuthorizer *authorizer;
GDataService *service;
-
- guint refresh_id;
-
- /* Map of active opids to GCancellables */
- GHashTable *cancellables;
-
- /* Did the server-side groups change? If so, re-download the book */
- gboolean groups_changed;
+ GHashTable *preloaded; /* gchar *uid ~> EContact * */
};
static void
-data_book_error_from_gdata_error (GError **error,
- const GError *gdata_error)
+ebb_google_data_book_error_from_gdata_error (GError **error,
+ const GError *gdata_error)
{
gboolean use_fallback = FALSE;
g_return_if_fail (gdata_error != NULL);
+ if (!error)
+ return;
+
/* Authentication errors */
if (gdata_error->domain == GDATA_SERVICE_ERROR) {
switch (gdata_error->code) {
@@ -158,214 +142,62 @@ data_book_error_from_gdata_error (GError **error,
gdata_error->message);
}
-static void
-migrate_cache (EBookBackendCache *cache)
-{
- const gchar *version;
- const gchar *version_key = "book-cache-version";
-
- g_return_if_fail (cache != NULL);
-
- version = e_file_cache_get_object (E_FILE_CACHE (cache), version_key);
- if (!version || atoi (version) < 2) {
- /* not versioned yet or too old, dump the cache and reload it from the server */
- e_file_cache_clean (E_FILE_CACHE (cache));
- e_file_cache_add_object (E_FILE_CACHE (cache), version_key, "2");
- }
-}
-
-static void
-cache_init (EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
- const gchar *cache_dir;
- gchar *filename;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_mutex_lock (&priv->cache_lock);
-
- cache_dir = e_book_backend_get_cache_dir (backend);
- filename = g_build_filename (cache_dir, "cache.xml", NULL);
- priv->cache = e_book_backend_cache_new (filename);
- g_free (filename);
-
- migrate_cache (priv->cache);
-
- g_mutex_unlock (&priv->cache_lock);
-}
-
-static EContact *
-cache_add_contact (EBookBackend *backend,
- GDataEntry *entry)
-{
- EBookBackendGooglePrivate *priv;
- EContact *contact;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_rec_mutex_lock (&priv->groups_lock);
- contact = e_contact_new_from_gdata_entry (entry, priv->groups_by_id, priv->system_groups_by_entry_id);
- g_rec_mutex_unlock (&priv->groups_lock);
-
- if (!contact)
- return NULL;
-
- e_contact_add_gdata_entry_xml (contact, entry);
- g_mutex_lock (&priv->cache_lock);
- e_book_backend_cache_add_contact (priv->cache, contact);
- g_mutex_unlock (&priv->cache_lock);
- e_contact_remove_gdata_entry_xml (contact);
-
- return contact;
-}
-
static gboolean
-cache_remove_contact (EBookBackend *backend,
- const gchar *uid)
+ebb_google_is_authorized (EBookBackendGoogle *bbgoogle)
{
- EBookBackendGooglePrivate *priv;
- gboolean removed;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (bbgoogle), FALSE);
- g_mutex_lock (&priv->cache_lock);
- removed = e_book_backend_cache_remove_contact (priv->cache, uid);
- g_mutex_unlock (&priv->cache_lock);
+ if (!bbgoogle->priv->service)
+ return FALSE;
- return removed;
+ return gdata_service_is_authorized (GDATA_SERVICE (bbgoogle->priv->service));
}
static gboolean
-cache_has_contact (EBookBackend *backend,
- const gchar *uid)
-{
- EBookBackendGooglePrivate *priv;
- gboolean has_contact;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_mutex_lock (&priv->cache_lock);
- has_contact = e_book_backend_cache_check_contact (priv->cache, uid);
- g_mutex_unlock (&priv->cache_lock);
-
- return has_contact;
-}
-
-static EContact *
-cache_get_contact (EBookBackend *backend,
- const gchar *uid,
- GDataEntry **entry)
+ebb_google_request_authorization (EBookBackendGoogle *bbgoogle,
+ const ENamedParameters *credentials,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendGooglePrivate *priv;
- EContact *contact;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_mutex_lock (&priv->cache_lock);
- contact = e_book_backend_cache_get_contact (priv->cache, uid);
- g_mutex_unlock (&priv->cache_lock);
-
- if (contact) {
- if (entry) {
- const gchar *entry_xml, *edit_uri = NULL;
+ /* Make sure we have the GDataService configured
+ * before requesting authorization. */
- entry_xml = e_contact_get_gdata_entry_xml (contact, &edit_uri);
- *entry = GDATA_ENTRY (gdata_parsable_new_from_xml (GDATA_TYPE_CONTACTS_CONTACT,
entry_xml, -1, NULL));
+ if (!bbgoogle->priv->authorizer) {
+ ESource *source;
+ EGDataOAuth2Authorizer *authorizer;
- if (*entry) {
- GDataLink *edit_link = gdata_link_new (edit_uri, GDATA_LINK_EDIT);
- gdata_entry_add_link (*entry, edit_link);
- g_object_unref (edit_link);
- }
- }
+ source = e_backend_get_source (E_BACKEND (bbgoogle));
- e_contact_remove_gdata_entry_xml (contact);
+ /* Only OAuth2 is supported with Google Tasks */
+ authorizer = e_gdata_oauth2_authorizer_new (source);
+ bbgoogle->priv->authorizer = GDATA_AUTHORIZER (authorizer);
}
- return contact;
-}
-
-static void
-cache_get_contacts (EBookBackend *backend,
- GQueue *out_contacts)
-{
- EBookBackendGooglePrivate *priv;
- GList *list, *link;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_mutex_lock (&priv->cache_lock);
- list = e_book_backend_cache_get_contacts (
- priv->cache, "(contains \"x-evolution-any-field\" \"\")");
- g_mutex_unlock (&priv->cache_lock);
-
- for (link = list; link != NULL; link = g_list_next (link)) {
- EContact *contact = E_CONTACT (link->data);
-
- e_contact_remove_gdata_entry_xml (contact);
- g_queue_push_tail (out_contacts, g_object_ref (contact));
+ if (E_IS_GDATA_OAUTH2_AUTHORIZER (bbgoogle->priv->authorizer)) {
+ e_gdata_oauth2_authorizer_set_credentials (E_GDATA_OAUTH2_AUTHORIZER
(bbgoogle->priv->authorizer), credentials);
}
- g_list_free_full (list, (GDestroyNotify) g_object_unref);
-}
-
-static void
-cache_freeze (EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- e_file_cache_freeze_changes (E_FILE_CACHE (priv->cache));
-}
-
-static void
-cache_thaw (EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- e_file_cache_thaw_changes (E_FILE_CACHE (priv->cache));
-}
-
-static gchar *
-cache_get_last_update (EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
- gchar *last_update;
+ if (!bbgoogle->priv->service) {
+ GDataContactsService *contacts_service;
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ contacts_service = gdata_contacts_service_new (bbgoogle->priv->authorizer);
+ bbgoogle->priv->service = GDATA_SERVICE (contacts_service);
- g_mutex_lock (&priv->cache_lock);
- last_update = e_book_backend_cache_get_time (priv->cache);
- g_mutex_unlock (&priv->cache_lock);
+ e_binding_bind_property (
+ bbgoogle, "proxy-resolver",
+ bbgoogle->priv->service, "proxy-resolver",
+ G_BINDING_SYNC_CREATE);
+ }
- return last_update;
-}
+ /* If we're using OAuth tokens, then as far as the backend
+ * is concerned it's always authorized. The GDataAuthorizer
+ * will take care of everything in the background. */
+ if (!GDATA_IS_CLIENT_LOGIN_AUTHORIZER (bbgoogle->priv->authorizer))
+ return TRUE;
-static void
-cache_set_last_update (EBookBackend *backend,
- GTimeVal *tv)
-{
- EBookBackendGooglePrivate *priv;
- gchar *_time;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- if (tv)
- _time = g_time_val_to_iso8601 (tv);
- else
- _time = NULL;
-
- g_mutex_lock (&priv->cache_lock);
- if (tv)
- e_book_backend_cache_set_time (priv->cache, _time);
- else
- e_file_cache_remove_object (E_FILE_CACHE (priv->cache), "last_update_time");
- g_mutex_unlock (&priv->cache_lock);
- g_free (_time);
+ /* Otherwise it's up to us to obtain a login secret, but
+ there is currently no way to do it, thus simply fail. */
+ return FALSE;
}
/* returns whether group changed from the one stored in the cache;
@@ -374,661 +206,476 @@ cache_set_last_update (EBookBackend *backend,
* use group_name = NULL to remove it from the cache.
*/
static gboolean
-cache_update_group (EBookBackend *backend,
- const gchar *group_id,
- const gchar *group_name)
+ebb_google_cache_update_group (EBookBackendGoogle *bbgoogle,
+ const gchar *group_id,
+ const gchar *group_name)
{
- EBookBackendGooglePrivate *priv;
- EFileCache *file_cache;
+ EBookCache *book_cache;
gboolean changed;
- gchar *key;
+ gchar *key, *old_value;
- g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (backend), FALSE);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (bbgoogle), FALSE);
g_return_val_if_fail (group_id != NULL, FALSE);
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
- key = g_strconcat ("google-group", ":", group_id, NULL);
+ book_cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (bbgoogle));
+ g_return_val_if_fail (book_cache != NULL, FALSE);
- g_mutex_lock (&priv->cache_lock);
-
- file_cache = E_FILE_CACHE (priv->cache);
+ key = g_strconcat ("google-group", ":", group_id, NULL);
+ old_value = e_cache_dup_key (E_CACHE (book_cache), key, NULL);
if (group_name) {
- const gchar *old_value;
-
- old_value = e_file_cache_get_object (file_cache, key);
changed = old_value && g_strcmp0 (old_value, group_name) != 0;
- if (!e_file_cache_replace_object (file_cache, key, group_name))
- e_file_cache_add_object (file_cache, key, group_name);
+ e_cache_set_key (E_CACHE (book_cache), key, group_name, NULL);
/* Add the category to Evolution’s category list. */
e_categories_add (group_name, NULL, NULL, TRUE);
} else {
- const gchar *old_value;
+ changed = old_value != NULL;
- old_value = e_file_cache_get_object (file_cache, key);
- changed = e_file_cache_remove_object (file_cache, key);
+ e_cache_set_key (E_CACHE (book_cache), key, NULL, NULL);
/* Remove the category from Evolution’s category list. */
- if (old_value != NULL) {
+ if (changed)
e_categories_remove (old_value);
- }
}
- g_mutex_unlock (&priv->cache_lock);
-
+ g_object_unref (book_cache);
+ g_free (old_value);
g_free (key);
return changed;
}
-static gboolean
-backend_is_authorized (EBookBackend *backend)
+static void
+ebb_google_process_group (GDataEntry *entry,
+ guint entry_key,
+ guint entry_count,
+ gpointer user_data)
{
- EBookBackendGooglePrivate *priv;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- if (priv->service == NULL)
- return FALSE;
-
- return gdata_service_is_authorized (priv->service);
-}
+ EBookBackendGoogle *bbgoogle = user_data;
+ const gchar *uid, *system_group_id;
+ gchar *name;
+ gboolean is_deleted;
-static GCancellable *
-start_operation (EBookBackend *backend,
- guint32 opid,
- GCancellable *cancellable,
- const gchar *message)
-{
- EBookBackendGooglePrivate *priv;
- GList *list, *link;
+ g_return_if_fail (E_IS_BOOK_BACKEND_GOOGLE (bbgoogle));
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ uid = gdata_entry_get_id (entry);
+ name = e_contact_sanitise_google_group_name (entry);
- /* Insert the operation into the set of active cancellable operations */
- if (cancellable)
- g_object_ref (cancellable);
- else
- cancellable = g_cancellable_new ();
- g_hash_table_insert (priv->cancellables, GUINT_TO_POINTER (opid), g_object_ref (cancellable));
+ system_group_id = gdata_contacts_group_get_system_group_id (GDATA_CONTACTS_GROUP (entry));
+ is_deleted = gdata_contacts_group_is_deleted (GDATA_CONTACTS_GROUP (entry));
- /* Send out a status message to each view */
- list = e_book_backend_list_views (backend);
+ g_rec_mutex_lock (&bbgoogle->priv->groups_lock);
- for (link = list; link != NULL; link = g_list_next (link)) {
- EDataBookView *view = E_DATA_BOOK_VIEW (link->data);
- e_data_book_view_notify_progress (view, -1, message);
- }
+ if (system_group_id) {
+ if (is_deleted) {
+ gchar *entry_id = g_hash_table_lookup (bbgoogle->priv->system_groups_by_id,
system_group_id);
+ g_hash_table_remove (bbgoogle->priv->system_groups_by_entry_id, entry_id);
+ g_hash_table_remove (bbgoogle->priv->system_groups_by_id, system_group_id);
+ } else {
+ gchar *entry_id, *system_group_id_dup;
- g_list_free_full (list, (GDestroyNotify) g_object_unref);
+ entry_id = e_contact_sanitise_google_group_id (uid);
+ system_group_id_dup = g_strdup (system_group_id);
- return cancellable;
-}
+ g_hash_table_replace (bbgoogle->priv->system_groups_by_entry_id, entry_id,
system_group_id_dup);
+ g_hash_table_replace (bbgoogle->priv->system_groups_by_id, system_group_id_dup,
entry_id);
+ }
-static void
-finish_operation (EBookBackend *backend,
- guint32 opid,
- const GError *gdata_error)
-{
- EBookBackendGooglePrivate *priv;
- GError *book_error = NULL;
+ g_free (name);
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ /* use evolution's names for google's system groups */
+ name = g_strdup (e_contact_map_google_with_evo_group (system_group_id, TRUE));
- if (gdata_error != NULL) {
- data_book_error_from_gdata_error (&book_error, gdata_error);
- g_debug ("Book view query failed: %s", book_error->message);
+ g_warn_if_fail (name != NULL);
+ if (!name)
+ name = g_strdup (system_group_id);
}
- if (g_hash_table_remove (priv->cancellables, GUINT_TO_POINTER (opid))) {
- GList *list, *link;
-
- list = e_book_backend_list_views (backend);
+ if (is_deleted) {
+ g_hash_table_remove (bbgoogle->priv->groups_by_id, uid);
+ g_hash_table_remove (bbgoogle->priv->groups_by_name, name);
- for (link = list; link != NULL; link = g_list_next (link)) {
- EDataBookView *view = E_DATA_BOOK_VIEW (link->data);
- e_data_book_view_notify_complete (view, book_error);
- }
+ bbgoogle->priv->groups_changed = ebb_google_cache_update_group (bbgoogle, uid, NULL) ||
bbgoogle->priv->groups_changed;
+ } else {
+ g_hash_table_replace (bbgoogle->priv->groups_by_id, e_contact_sanitise_google_group_id (uid),
g_strdup (name));
+ g_hash_table_replace (bbgoogle->priv->groups_by_name, g_strdup (name),
e_contact_sanitise_google_group_id (uid));
- g_list_free_full (list, (GDestroyNotify) g_object_unref);
+ bbgoogle->priv->groups_changed = ebb_google_cache_update_group (bbgoogle, uid, name) ||
bbgoogle->priv->groups_changed;
}
- g_clear_error (&book_error);
-}
-
-static void
-process_contact_finish (EBookBackend *backend,
- GDataEntry *entry)
-{
- EContact *new_contact;
-
- g_debug (G_STRFUNC);
-
- new_contact = cache_add_contact (backend, entry);
+ g_rec_mutex_unlock (&bbgoogle->priv->groups_lock);
- if (!new_contact)
- return;
-
- e_book_backend_notify_update (backend, new_contact);
-
- g_object_unref (new_contact);
+ g_free (name);
}
-typedef struct {
- EBookBackend *backend;
- GCancellable *cancellable;
- GError *gdata_error;
+static gboolean
+ebb_google_get_groups_sync (EBookBackendGoogle *bbgoogle,
+ gboolean with_time_constraint,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDataQuery *query;
+ GDataFeed *feed;
+ gboolean success;
- /* These two don't need locking; they're only accessed from the main thread. */
- gboolean update_complete;
- guint num_contacts_pending_photos;
-} GetContactsData;
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (bbgoogle), FALSE);
+ g_return_val_if_fail (ebb_google_is_authorized (bbgoogle), FALSE);
-static void
-check_get_new_contacts_finished (GetContactsData *data)
-{
- g_debug (G_STRFUNC);
+ g_rec_mutex_lock (&bbgoogle->priv->groups_lock);
- /* Are we finished yet? */
- if (data->update_complete == FALSE || data->num_contacts_pending_photos > 0) {
- g_debug (
- "Bailing from check_get_new_contacts_finished(): update_complete: %u,
num_contacts_pending_photos: %u, data: %p",
- data->update_complete, data->num_contacts_pending_photos, data);
- return;
+ /* Build our query, always fetch all of them */
+ query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
+ if (with_time_constraint && bbgoogle->priv->groups_last_update.tv_sec != 0) {
+ gdata_query_set_updated_min (query, bbgoogle->priv->groups_last_update.tv_sec);
+ gdata_contacts_query_set_show_deleted (GDATA_CONTACTS_QUERY (query), TRUE);
}
- g_debug ("Proceeding with check_get_new_contacts_finished() for data: %p.", data);
-
- finish_operation (data->backend, -1, data->gdata_error);
+ bbgoogle->priv->groups_changed = FALSE;
- /* Tidy up */
- g_object_unref (data->cancellable);
- g_object_unref (data->backend);
- g_clear_error (&data->gdata_error);
+ /* Run the query synchronously */
+ feed = gdata_contacts_service_query_groups (
+ GDATA_CONTACTS_SERVICE (bbgoogle->priv->service),
+ query, cancellable, ebb_google_process_group, bbgoogle, error);
- g_slice_free (GetContactsData, data);
-}
+ success = feed != NULL;
-typedef struct {
- GetContactsData *parent_data;
+ if (success)
+ g_get_current_time (&bbgoogle->priv->groups_last_update);
- GCancellable *cancellable;
- gulong cancelled_handle;
-} PhotoData;
+ g_rec_mutex_unlock (&bbgoogle->priv->groups_lock);
-static void
-process_contact_photo_cancelled_cb (GCancellable *parent_cancellable,
- GCancellable *photo_cancellable)
-{
- g_debug (G_STRFUNC);
+ g_clear_object (&feed);
+ g_object_unref (query);
- g_cancellable_cancel (photo_cancellable);
+ return success;
}
-static void
-process_contact_photo_cb (GDataContactsContact *gdata_contact,
- GAsyncResult *async_result,
- PhotoData *data)
+static gboolean
+ebb_google_connect_sync (EBookMetaBackend *meta_backend,
+ const ENamedParameters *credentials,
+ ESourceAuthenticationResult *out_auth_result,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackend *backend = data->parent_data->backend;
- guint8 *photo_data = NULL;
- gsize photo_length;
- gchar *photo_content_type = NULL;
- GError *error = NULL;
-
- g_debug (G_STRFUNC);
-
- /* Finish downloading the photo */
- photo_data = gdata_contacts_contact_get_photo_finish (gdata_contact, async_result, &photo_length,
&photo_content_type, &error);
+ EBookBackendGoogle *bbgoogle;
+ gboolean success;
+ GError *local_error = NULL;
- if (error == NULL) {
- EContactPhoto *photo;
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (meta_backend), FALSE);
+ g_return_val_if_fail (out_auth_result != NULL, FALSE);
- /* Success! Create an EContactPhoto and store it on the final GDataContactsContact object so
it makes it into the cache. */
- photo = e_contact_photo_new ();
- photo->type = E_CONTACT_PHOTO_TYPE_INLINED;
- photo->data.inlined.data = (guchar *) photo_data;
- photo->data.inlined.length = photo_length;
- photo->data.inlined.mime_type = photo_content_type;
+ bbgoogle = E_BOOK_BACKEND_GOOGLE (meta_backend);
- g_object_set_data_full (G_OBJECT (gdata_contact), "photo", photo, (GDestroyNotify)
e_contact_photo_free);
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
- photo_data = NULL;
- photo_content_type = NULL;
- } else {
- /* Error. */
- g_debug ("Downloading contact photo for '%s' failed: %s", gdata_entry_get_id (GDATA_ENTRY
(gdata_contact)), error->message);
- g_error_free (error);
- }
+ if (ebb_google_is_authorized (bbgoogle))
+ return TRUE;
- process_contact_finish (backend, GDATA_ENTRY (gdata_contact));
+ success = ebb_google_request_authorization (bbgoogle, credentials, cancellable, &local_error);
+ if (success)
+ success = gdata_authorizer_refresh_authorization (bbgoogle->priv->authorizer, cancellable,
&local_error);
- g_free (photo_data);
- g_free (photo_content_type);
+ if (success)
+ success = ebb_google_get_groups_sync (bbgoogle, FALSE, cancellable, &local_error);
- /* Disconnect from the cancellable. */
- g_cancellable_disconnect (data->parent_data->cancellable, data->cancelled_handle);
- g_object_unref (data->cancellable);
+ if (!success) {
+ if (g_error_matches (local_error, GDATA_SERVICE_ERROR,
GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED)) {
+ if (!e_named_parameters_exists (credentials, E_SOURCE_CREDENTIAL_PASSWORD))
+ *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED;
+ else
+ *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED;
+ } else {
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
+ ebb_google_data_book_error_from_gdata_error (error, local_error);
+ }
- data->parent_data->num_contacts_pending_photos--;
- check_get_new_contacts_finished (data->parent_data);
+ g_clear_error (&local_error);
+ }
- g_slice_free (PhotoData, data);
+ return success;
}
-static void
-process_contact_cb (GDataEntry *entry,
- guint entry_key,
- guint entry_count,
- GetContactsData *data)
+static gboolean
+ebb_google_disconnect_sync (EBookMetaBackend *meta_backend,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendGooglePrivate *priv;
- EBookBackend *backend = data->backend;
- gboolean is_deleted, is_cached;
- const gchar *uid;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_debug (G_STRFUNC);
- uid = gdata_entry_get_id (entry);
- is_deleted = gdata_contacts_contact_is_deleted (GDATA_CONTACTS_CONTACT (entry));
-
- is_cached = cache_has_contact (backend, uid);
- if (is_deleted) {
- /* Do we have this item in our cache? */
- if (is_cached) {
- cache_remove_contact (backend, uid);
- e_book_backend_notify_remove (backend, uid);
- }
- } else {
- gchar *old_photo_etag = NULL;
- const gchar *new_photo_etag;
-
- /* Download the contact's photo first, if the contact's uncached or if the photo's been
updated. */
- if (is_cached == TRUE) {
- EContact *old_contact;
- EContactPhoto *photo;
- EVCardAttribute *old_attr;
-
- old_contact = cache_get_contact (backend, uid, NULL);
-
- /* Get the old ETag. */
- old_attr = e_vcard_get_attribute (E_VCARD (old_contact), GDATA_PHOTO_ETAG_ATTR);
- old_photo_etag = (old_attr != NULL) ? e_vcard_attribute_get_value (old_attr) : NULL;
-
- /* Attach the old photo to the new contact. */
- photo = e_contact_get (old_contact, E_CONTACT_PHOTO);
-
- if (photo != NULL && photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
- g_object_set_data_full (G_OBJECT (entry), "photo", photo, (GDestroyNotify)
e_contact_photo_free);
- } else if (photo != NULL) {
- e_contact_photo_free (photo);
- }
-
- g_object_unref (old_contact);
- }
+ EBookBackendGoogle *bbgoogle;
- new_photo_etag = gdata_contacts_contact_get_photo_etag (GDATA_CONTACTS_CONTACT (entry));
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (meta_backend), FALSE);
- if ((old_photo_etag == NULL && new_photo_etag != NULL) ||
- (old_photo_etag != NULL && new_photo_etag != NULL && strcmp (old_photo_etag,
new_photo_etag) != 0)) {
- GCancellable *cancellable;
- PhotoData *photo_data;
+ bbgoogle = E_BOOK_BACKEND_GOOGLE (meta_backend);
- photo_data = g_slice_new (PhotoData);
- photo_data->parent_data = data;
+ g_clear_object (&bbgoogle->priv->service);
+ g_clear_object (&bbgoogle->priv->authorizer);
- /* Increment the count of contacts whose photos we're waiting for. */
- data->num_contacts_pending_photos++;
+ return TRUE;
+}
- /* Cancel downloading if the get_new_contacts() operation is cancelled. */
- cancellable = g_cancellable_new ();
+static gboolean
+ebb_google_get_changes_sync (EBookMetaBackend *meta_backend,
+ const gchar *last_sync_tag,
+ gboolean is_repeat,
+ gchar **out_new_sync_tag,
+ gboolean *out_repeat,
+ GSList **out_created_objects, /* EBookMetaBackendInfo * */
+ GSList **out_modified_objects, /* EBookMetaBackendInfo * */
+ GSList **out_removed_objects, /* EBookMetaBackendInfo * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EBookBackendGoogle *bbgoogle;
+ EBookCache *book_cache;
+ gint64 updated_time = 0;
+ GTimeVal last_updated;
+ GDataFeed *feed;
+ GDataContactsQuery *contacts_query;
+ GError *local_error = NULL;
- photo_data->cancellable = g_object_ref (cancellable);
- photo_data->cancelled_handle = g_cancellable_connect (
- data->cancellable, (GCallback) process_contact_photo_cancelled_cb,
- g_object_ref (cancellable), (GDestroyNotify) g_object_unref);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (meta_backend), FALSE);
+ g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
+ g_return_val_if_fail (out_created_objects != NULL, FALSE);
+ g_return_val_if_fail (out_modified_objects != NULL, FALSE);
+ g_return_val_if_fail (out_removed_objects != NULL, FALSE);
- /* Download the photo. */
- gdata_contacts_contact_get_photo_async (
- GDATA_CONTACTS_CONTACT (entry),
- GDATA_CONTACTS_SERVICE (priv->service), cancellable,
- (GAsyncReadyCallback) process_contact_photo_cb, photo_data);
+ bbgoogle = E_BOOK_BACKEND_GOOGLE (meta_backend);
- g_object_unref (cancellable);
- g_free (old_photo_etag);
+ *out_created_objects = NULL;
+ *out_modified_objects = NULL;
+ *out_removed_objects = NULL;
- return;
- }
+ if (!ebb_google_get_groups_sync (bbgoogle, TRUE, cancellable, error))
+ return FALSE;
- g_free (old_photo_etag);
+ book_cache = e_book_meta_backend_ref_cache (meta_backend);
- /* Since we're not downloading a photo, add the contact to the cache now. */
- process_contact_finish (backend, entry);
+ if (!last_sync_tag ||
+ !g_time_val_from_iso8601 (last_sync_tag, &last_updated)) {
+ last_updated.tv_sec = 0;
}
-}
-
-static void
-get_new_contacts_cb (GDataService *service,
- GAsyncResult *result,
- GetContactsData *data)
-{
- EBookBackend *backend = data->backend;
- GDataFeed *feed;
- GError *gdata_error = NULL;
- g_debug (G_STRFUNC);
- feed = gdata_service_query_finish (service, result, &gdata_error);
- if (feed != NULL) {
- GList *entries = gdata_feed_get_entries (feed);
- g_debug ("Feed has %d entries", g_list_length (entries));
+ contacts_query = gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT);
+ if (last_updated.tv_sec > 0 && !bbgoogle->priv->groups_changed) {
+ gdata_query_set_updated_min (GDATA_QUERY (contacts_query), last_updated.tv_sec);
+ gdata_contacts_query_set_show_deleted (contacts_query, TRUE);
}
- if (feed != NULL)
- g_object_unref (feed);
+ feed = gdata_contacts_service_query_contacts (GDATA_CONTACTS_SERVICE (bbgoogle->priv->service),
GDATA_QUERY (contacts_query), cancellable, NULL, NULL, &local_error);
- if (!gdata_error) {
- /* Finish updating the cache */
- GTimeVal current_time;
- g_get_current_time (¤t_time);
- cache_set_last_update (backend, ¤t_time);
+ if (feed && !g_cancellable_is_cancelled (cancellable) && !local_error) {
+ GList *link;
- e_backend_ensure_source_status_connected (E_BACKEND (backend));
- }
+ if (gdata_feed_get_updated (feed) > updated_time)
+ updated_time = gdata_feed_get_updated (feed);
- /* Thaw the cache again */
- cache_thaw (backend);
+ for (link = gdata_feed_get_entries (feed); link && !g_cancellable_is_cancelled (cancellable);
link = g_list_next (link)) {
+ GDataContactsContact *gdata_contact = link->data;
+ EContact *cached_contact = NULL;
+ gchar *uid;
- /* Note: The operation's only marked as finished when all the
- * process_contact_photo_cb() callbacks have been called as well. */
- data->update_complete = TRUE;
- data->gdata_error = gdata_error;
- check_get_new_contacts_finished (data);
-}
-
-static void
-get_new_contacts (EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
- gchar *last_updated;
- GTimeVal updated;
- GDataQuery *query;
- GCancellable *cancellable;
- GetContactsData *data;
+ if (!GDATA_IS_CONTACTS_CONTACT (gdata_contact))
+ continue;
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ uid = g_strdup (gdata_entry_get_id (GDATA_ENTRY (gdata_contact)));
+ if (!uid || !*uid) {
+ g_free (uid);
+ continue;
+ }
- g_debug (G_STRFUNC);
- g_return_if_fail (backend_is_authorized (backend));
+ if (!e_book_cache_get_contact (book_cache, uid, FALSE, &cached_contact, cancellable,
NULL))
+ cached_contact = NULL;
- /* Sort out update times */
- last_updated = cache_get_last_update (backend);
- g_return_if_fail (last_updated == NULL || g_time_val_from_iso8601 (last_updated, &updated) == TRUE);
- g_free (last_updated);
+ if (gdata_contacts_contact_is_deleted (gdata_contact)) {
+ *out_removed_objects = g_slist_prepend (*out_removed_objects,
+ e_book_meta_backend_info_new (uid, NULL, NULL, NULL));
+ } else {
+ EContact *new_contact;
- /* Prevent the cache writing each change to disk individually (thawed in get_new_contacts_cb()) */
- cache_freeze (backend);
+ if (cached_contact) {
+ gchar *old_etag;
- /* Build our query */
- query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
- if (last_updated) {
- gdata_query_set_updated_min (query, updated.tv_sec);
- gdata_contacts_query_set_show_deleted (GDATA_CONTACTS_QUERY (query), TRUE);
- }
+ old_etag = e_contact_get (cached_contact, E_CONTACT_REV);
- /* Query for new contacts asynchronously */
- cancellable = start_operation (backend, -1, NULL, _("Querying for updated contacts…"));
-
- data = g_slice_new (GetContactsData);
- data->backend = g_object_ref (backend);
- data->cancellable = g_object_ref (cancellable);
- data->gdata_error = NULL;
- data->num_contacts_pending_photos = 0;
- data->update_complete = FALSE;
-
- gdata_contacts_service_query_contacts_async (
- GDATA_CONTACTS_SERVICE (priv->service),
- query,
- cancellable,
- (GDataQueryProgressCallback) process_contact_cb,
- data,
- (GDestroyNotify) NULL,
- (GAsyncReadyCallback) get_new_contacts_cb,
- data);
-
- g_object_unref (cancellable);
- g_object_unref (query);
-}
+ if (g_strcmp0 (gdata_entry_get_etag (GDATA_ENTRY (gdata_contact)),
old_etag) == 0) {
+ g_object_unref (cached_contact);
+ g_free (old_etag);
+ g_free (uid);
+ continue;
+ }
-static void
-process_group (GDataEntry *entry,
- guint entry_key,
- guint entry_count,
- EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
- const gchar *uid, *system_group_id;
- gchar *name;
- gboolean is_deleted;
+ g_free (old_etag);
+ }
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ g_rec_mutex_lock (&bbgoogle->priv->groups_lock);
+ new_contact = e_contact_new_from_gdata_entry (GDATA_ENTRY (gdata_contact),
+ bbgoogle->priv->groups_by_id,
bbgoogle->priv->system_groups_by_entry_id);
+ g_rec_mutex_unlock (&bbgoogle->priv->groups_lock);
- g_debug (G_STRFUNC);
- uid = gdata_entry_get_id (entry);
- name = e_contact_sanitise_google_group_name (entry);
+ if (new_contact) {
+ const gchar *revision, *photo_etag;
+ gchar *object, *extra;
- system_group_id = gdata_contacts_group_get_system_group_id (GDATA_CONTACTS_GROUP (entry));
- is_deleted = gdata_contacts_group_is_deleted (GDATA_CONTACTS_GROUP (entry));
+ photo_etag = gdata_contacts_contact_get_photo_etag (gdata_contact);
+ if (photo_etag && cached_contact) {
+ gchar *old_photo_etag;
- g_rec_mutex_lock (&priv->groups_lock);
+ old_photo_etag = e_vcard_util_dup_x_attribute (E_VCARD
(cached_contact), E_GOOGLE_X_PHOTO_ETAG);
+ if (g_strcmp0 (photo_etag, old_photo_etag) == 0) {
+ EContactPhoto *photo;
- if (system_group_id) {
- g_debug ("Processing %ssystem group %s, %s", is_deleted ? "(deleted) " : "", system_group_id,
uid);
+ /* To not download it again, when it's already
available locally */
+ photo_etag = NULL;
- if (is_deleted) {
- gchar *entry_id = g_hash_table_lookup (priv->system_groups_by_id, system_group_id);
- g_hash_table_remove (priv->system_groups_by_entry_id, entry_id);
- g_hash_table_remove (priv->system_groups_by_id, system_group_id);
- } else {
- gchar *entry_id, *system_group_id_dup;
+ /* Copy the photo attribute to the changed contact */
+ photo = e_contact_get (cached_contact,
E_CONTACT_PHOTO);
+ e_contact_set (new_contact, E_CONTACT_PHOTO, photo);
- entry_id = e_contact_sanitise_google_group_id (uid);
- system_group_id_dup = g_strdup (system_group_id);
+ e_contact_photo_free (photo);
+ }
- g_hash_table_replace (priv->system_groups_by_entry_id, entry_id, system_group_id_dup);
- g_hash_table_replace (priv->system_groups_by_id, system_group_id_dup, entry_id);
- }
+ g_free (old_photo_etag);
+ }
- g_free (name);
+ if (photo_etag) {
+ guint8 *photo_data;
+ gsize photo_length = 0;
+ gchar *photo_content_type = NULL;
+ GError *local_error2 = NULL;
- /* use evolution's names for google's system groups */
- name = g_strdup (e_contact_map_google_with_evo_group (system_group_id, TRUE));
+ photo_data = gdata_contacts_contact_get_photo (gdata_contact,
GDATA_CONTACTS_SERVICE (bbgoogle->priv->service),
+ &photo_length, &photo_content_type, cancellable,
&local_error2);
- g_warn_if_fail (name != NULL);
- if (!name)
- name = g_strdup (system_group_id);
- }
+ if (!local_error2) {
+ EContactPhoto *photo;
- if (is_deleted) {
- g_debug ("Processing (deleting) group %s, %s", uid, name);
- g_hash_table_remove (priv->groups_by_id, uid);
- g_hash_table_remove (priv->groups_by_name, name);
+ photo = e_contact_photo_new ();
+ photo->type = E_CONTACT_PHOTO_TYPE_INLINED;
+ photo->data.inlined.data = (guchar *) photo_data;
+ photo->data.inlined.length = photo_length;
+ photo->data.inlined.mime_type = photo_content_type;
- priv->groups_changed = cache_update_group (backend, uid, NULL) || priv->groups_changed;
- } else {
- g_debug ("Processing group %s, %s", uid, name);
- g_hash_table_replace (priv->groups_by_id, e_contact_sanitise_google_group_id (uid), g_strdup
(name));
- g_hash_table_replace (priv->groups_by_name, g_strdup (name),
e_contact_sanitise_google_group_id (uid));
+ e_contact_set (E_CONTACT (new_contact),
E_CONTACT_PHOTO, photo);
- priv->groups_changed = cache_update_group (backend, uid, name) || priv->groups_changed;
- }
+ e_contact_photo_free (photo);
- g_rec_mutex_unlock (&priv->groups_lock);
+ /* Read of the photo frees previously obtained
photo_etag */
+ photo_etag = gdata_contacts_contact_get_photo_etag
(gdata_contact);
- g_free (name);
-}
+ e_vcard_util_set_x_attribute (E_VCARD (new_contact),
E_GOOGLE_X_PHOTO_ETAG, photo_etag);
+ } else {
+ g_debug ("%s: Downloading contact photo for '%s'
failed: %s", G_STRFUNC,
+ gdata_entry_get_id (GDATA_ENTRY
(gdata_contact)), local_error2->message);
-static void
-get_groups_cb (GDataService *service,
- GAsyncResult *result,
- EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
- GDataFeed *feed;
- GError *gdata_error = NULL;
+ g_clear_error (&local_error2);
+ }
+ }
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ revision = gdata_entry_get_etag (GDATA_ENTRY (gdata_contact));
+ e_contact_set (new_contact, E_CONTACT_REV, revision);
+ object = e_vcard_to_string (E_VCARD (new_contact),
EVC_FORMAT_VCARD_30);
+ extra = gdata_parsable_get_xml (GDATA_PARSABLE (gdata_contact));
- g_debug (G_STRFUNC);
- feed = gdata_service_query_finish (service, result, &gdata_error);
- if (feed != NULL) {
- GList *entries = gdata_feed_get_entries (feed);
- g_debug ("Group feed has %d entries", g_list_length (entries));
- }
+ if (cached_contact) {
+ *out_modified_objects = g_slist_prepend
(*out_modified_objects,
+ e_book_meta_backend_info_new (uid, revision, object,
extra));
+ } else {
+ *out_created_objects = g_slist_prepend (*out_created_objects,
+ e_book_meta_backend_info_new (uid, revision, object,
extra));
+ }
- if (feed != NULL)
- g_object_unref (feed);
+ g_free (object);
+ g_free (extra);
+ }
- if (!gdata_error) {
- /* Update the update time */
- g_rec_mutex_lock (&priv->groups_lock);
- g_get_current_time (&(priv->groups_last_update));
- g_rec_mutex_unlock (&priv->groups_lock);
+ g_clear_object (&new_contact);
+ }
- e_backend_ensure_source_status_connected (E_BACKEND (backend));
+ g_clear_object (&cached_contact);
+ g_free (uid);
+ }
}
- finish_operation (backend, -2, gdata_error);
+ g_clear_object (&contacts_query);
+ g_clear_object (&feed);
- g_rec_mutex_lock (&priv->groups_lock);
+ if (!g_cancellable_is_cancelled (cancellable) && !local_error) {
+ last_updated.tv_sec = updated_time;
+ last_updated.tv_usec = 0;
- if (priv->groups_changed) {
- priv->groups_changed = FALSE;
+ *out_new_sync_tag = g_time_val_to_iso8601 (&last_updated);
+ }
- g_rec_mutex_unlock (&priv->groups_lock);
+ g_clear_object (&book_cache);
- /* do the update for all contacts, like with an empty cache */
- cache_set_last_update (backend, NULL);
- get_new_contacts (backend);
- } else {
- g_rec_mutex_unlock (&priv->groups_lock);
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ return FALSE;
}
- g_object_unref (backend);
-
- g_clear_error (&gdata_error);
+ return TRUE;
}
-static void
-get_groups_and_update_cache_cb (GDataService *service,
- GAsyncResult *result,
- EBookBackend *backend)
+static gboolean
+ebb_google_load_contact_sync (EBookMetaBackend *meta_backend,
+ const gchar *uid,
+ const gchar *extra,
+ EContact **out_contact,
+ gchar **out_extra,
+ GCancellable *cancellable,
+ GError **error)
{
- g_object_ref (backend);
+ EBookBackendGoogle *bbgoogle;
- get_groups_cb (service, result, backend);
- get_new_contacts (backend);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (meta_backend), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (out_contact != NULL, FALSE);
+ g_return_val_if_fail (out_extra != NULL, FALSE);
- g_object_unref (backend);
-}
+ bbgoogle = E_BOOK_BACKEND_GOOGLE (meta_backend);
-static void
-get_groups (EBookBackend *backend,
- gboolean and_update_cache)
-{
- EBookBackendGooglePrivate *priv;
- GDataQuery *query;
- GCancellable *cancellable;
+ /* Only "load" preloaded during save, otherwise fail with an error,
+ because the backend provides objects within get_changes_sync() */
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ if (bbgoogle->priv->preloaded) {
+ EContact *contact;
- g_debug (G_STRFUNC);
- g_return_if_fail (backend_is_authorized (backend));
+ contact = g_hash_table_lookup (bbgoogle->priv->preloaded, uid);
+ if (contact) {
+ *out_contact = e_contact_duplicate (contact);
- g_rec_mutex_lock (&priv->groups_lock);
+ g_hash_table_remove (bbgoogle->priv->preloaded, uid);
- /* Build our query */
- query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
- if (priv->groups_last_update.tv_sec != 0 || priv->groups_last_update.tv_usec != 0) {
- gdata_query_set_updated_min (query, priv->groups_last_update.tv_sec);
- gdata_contacts_query_set_show_deleted (GDATA_CONTACTS_QUERY (query), TRUE);
+ return TRUE;
+ }
}
- priv->groups_changed = FALSE;
-
- g_rec_mutex_unlock (&priv->groups_lock);
-
- g_object_ref (backend);
-
- /* Run the query asynchronously */
- cancellable = start_operation (backend, -2, NULL, _("Querying for updated groups…"));
- gdata_contacts_service_query_groups_async (
- GDATA_CONTACTS_SERVICE (priv->service),
- query,
- cancellable,
- (GDataQueryProgressCallback) process_group,
- backend,
- (GDestroyNotify) NULL,
- (GAsyncReadyCallback) (and_update_cache ? get_groups_and_update_cache_cb : get_groups_cb),
- backend);
-
- g_object_unref (cancellable);
- g_object_unref (query);
-}
+ g_set_error_literal (error, E_BOOK_CLIENT_ERROR, E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
+ e_book_client_error_to_string (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND));
-static void
-get_groups_sync (EBookBackend *backend,
- GCancellable *cancellable,
- GError **error)
-{
- EBookBackendGooglePrivate *priv;
- GDataQuery *query;
- GDataFeed *feed;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_debug (G_STRFUNC);
- g_return_if_fail (backend_is_authorized (backend));
-
- /* Build our query, always fetch all of them */
- query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 0, G_MAXINT));
-
- /* Run the query synchronously */
- feed = gdata_contacts_service_query_groups (
- GDATA_CONTACTS_SERVICE (priv->service),
- query,
- cancellable,
- (GDataQueryProgressCallback) process_group,
- backend,
- error);
-
- if (feed)
- g_object_unref (feed);
-
- g_object_unref (query);
+ return FALSE;
}
static gchar *
-create_group (EBookBackend *backend,
- const gchar *category_name,
- GError **error)
+ebb_google_create_group_sync (EBookBackendGoogle *bbgoogle,
+ const gchar *category_name,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendGooglePrivate *priv;
GDataEntry *group, *new_group;
- gchar *uid;
const gchar *system_group_id;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ gchar *uid;
system_group_id = e_contact_map_google_with_evo_group (category_name, FALSE);
if (system_group_id) {
gchar *group_entry_id;
- g_rec_mutex_lock (&priv->groups_lock);
- group_entry_id = g_strdup (g_hash_table_lookup (priv->system_groups_by_id, system_group_id));
- g_rec_mutex_unlock (&priv->groups_lock);
+ g_rec_mutex_lock (&bbgoogle->priv->groups_lock);
+ group_entry_id = g_strdup (g_hash_table_lookup (bbgoogle->priv->system_groups_by_id,
system_group_id));
+ g_rec_mutex_unlock (&bbgoogle->priv->groups_lock);
g_return_val_if_fail (group_entry_id != NULL, NULL);
@@ -1038,14 +685,12 @@ create_group (EBookBackend *backend,
group = GDATA_ENTRY (gdata_contacts_group_new (NULL));
gdata_entry_set_title (group, category_name);
- g_debug ("Creating group %s", category_name);
/* Insert the new group */
- new_group = GDATA_ENTRY (
- gdata_contacts_service_insert_group (
- GDATA_CONTACTS_SERVICE (priv->service),
+ new_group = GDATA_ENTRY (gdata_contacts_service_insert_group (
+ GDATA_CONTACTS_SERVICE (bbgoogle->priv->service),
GDATA_CONTACTS_GROUP (group),
- NULL, error));
+ cancellable, error));
g_object_unref (group);
if (new_group == NULL)
@@ -1054,217 +699,60 @@ create_group (EBookBackend *backend,
/* Add the new group to the group mappings */
uid = g_strdup (gdata_entry_get_id (new_group));
- g_rec_mutex_lock (&priv->groups_lock);
- g_hash_table_replace (priv->groups_by_id, e_contact_sanitise_google_group_id (uid), g_strdup
(category_name));
- g_hash_table_replace (priv->groups_by_name, g_strdup (category_name),
e_contact_sanitise_google_group_id (uid));
- g_rec_mutex_unlock (&priv->groups_lock);
+ g_rec_mutex_lock (&bbgoogle->priv->groups_lock);
+ g_hash_table_replace (bbgoogle->priv->groups_by_id, e_contact_sanitise_google_group_id (uid),
g_strdup (category_name));
+ g_hash_table_replace (bbgoogle->priv->groups_by_name, g_strdup (category_name),
e_contact_sanitise_google_group_id (uid));
+ g_rec_mutex_unlock (&bbgoogle->priv->groups_lock);
g_object_unref (new_group);
/* Update the cache. */
- cache_update_group (backend, uid, category_name);
-
- g_debug ("...got UID %s", uid);
+ ebb_google_cache_update_group (bbgoogle, uid, category_name);
return uid;
}
-static gchar *
-_create_group (const gchar *category_name,
- gpointer user_data,
- GError **error)
-{
- return create_group (E_BOOK_BACKEND (user_data), category_name, error);
-}
-
-static void
-refresh_local_cache_cb (ESource *source,
- gpointer user_data)
-{
- EBookBackend *backend = user_data;
-
- g_debug ("Invoking cache refresh");
-
- /* The TRUE means the cache update will be run immediately
- after groups are updated */
- get_groups (backend, TRUE);
-}
-
-static void
-cache_refresh_if_needed (EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
- gboolean is_online;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_debug (G_STRFUNC);
-
- is_online = e_backend_get_online (E_BACKEND (backend));
-
- if (!is_online || !backend_is_authorized (backend)) {
- g_debug ("We are not connected to Google%s.", (!is_online) ? " (offline mode)" : "");
- return;
- }
-
- if (!priv->refresh_id) {
- /* Update the cache asynchronously */
- refresh_local_cache_cb (NULL, backend);
-
- priv->refresh_id = e_source_refresh_add_timeout (
- e_backend_get_source (E_BACKEND (backend)),
- NULL,
- refresh_local_cache_cb,
- backend,
- NULL);
- } else {
- g_rec_mutex_lock (&priv->groups_lock);
- if (g_hash_table_size (priv->system_groups_by_id) == 0) {
- g_rec_mutex_unlock (&priv->groups_lock);
- get_groups (backend, FALSE);
- } else {
- g_rec_mutex_unlock (&priv->groups_lock);
- }
- }
-
- return;
-}
-
-#if !GDATA_CHECK_VERSION(0,15,0)
-static void
-fallback_set_proxy_uri (EBookBackend *backend)
-{
- EBookBackendGooglePrivate *priv;
- GProxyResolver *proxy_resolver;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- proxy_resolver = e_book_backend_ref_proxy_resolver (backend);
-
- if (proxy_resolver != NULL) {
- SoupURI *proxy_uri = NULL;
- gchar **proxies;
-
- /* Don't worry about errors since this is a
- * fallback function. It works if it works. */
- proxies = g_proxy_resolver_lookup (
- proxy_resolver, URI_GET_CONTACTS, NULL, NULL);
-
- if (proxies != NULL && strcmp (proxies[0], "direct://") != 0) {
- proxy_uri = soup_uri_new (proxies[0]);
- g_strfreev (proxies);
- }
-
- if (proxy_uri != NULL) {
- gdata_service_set_proxy_uri (priv->service, proxy_uri);
- soup_uri_free (proxy_uri);
- }
-
- g_object_unref (proxy_resolver);
- }
-}
-#endif
-
static gboolean
-connect_without_password (EBookBackend *backend,
- GCancellable *cancellable,
- GError **error)
-{
- ESource *source;
- ESourceAuthentication *extension;
- EGDataOAuth2Authorizer *authorizer;
- gchar *method;
- gboolean is_oauth2_method;
- EBookBackendGooglePrivate *priv;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- source = e_backend_get_source (E_BACKEND (backend));
- extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
- method = e_source_authentication_dup_method (extension);
- is_oauth2_method = g_strcmp0 (method, "OAuth2") == 0;
- g_free (method);
-
- /* Make sure we have the GDataService configured
- * before requesting authorization. */
-
- if (priv->authorizer == NULL) {
- authorizer = e_gdata_oauth2_authorizer_new (source);
- priv->authorizer = GDATA_AUTHORIZER (authorizer);
- }
-
- if (priv->service == NULL) {
- GDataContactsService *contacts_service;
-
- contacts_service =
- gdata_contacts_service_new (priv->authorizer);
- priv->service = GDATA_SERVICE (contacts_service);
-
-#if GDATA_CHECK_VERSION(0,15,0)
- /* proxy-resolver was added in 0.15.0.
- * (https://bugzilla.gnome.org/709758) */
- e_binding_bind_property (
- backend, "proxy-resolver",
- priv->service, "proxy-resolver",
- G_BINDING_SYNC_CREATE);
-#else
- /* XXX The fallback approach doesn't listen for proxy
- * setting changes, but really how often do proxy
- * settings change? */
- fallback_set_proxy_uri (backend);
-#endif
- }
-
- /* If we're using OAuth tokens, then as far as the backend
- * is concerned it's always authorized. The GDataAuthorizer
- * will take care of everything in the background. */
- if (is_oauth2_method)
- return TRUE;
-
- /* Otherwise it's up to us to obtain an OAuth 2 token. */
- return FALSE;
-}
-
-typedef enum {
- LEAVE_PHOTO,
- ADD_PHOTO,
- REMOVE_PHOTO,
- UPDATE_PHOTO,
-} PhotoOperation;
-
-static PhotoOperation
-pick_photo_operation (EContact *old_contact,
- EContact *new_contact)
+ebb_google_photo_changed (EBookMetaBackend *meta_backend,
+ EContact *old_contact,
+ EContact *new_contact,
+ GCancellable *cancellable)
{
+ EContact *old_contact_copy = NULL;
EContactPhoto *old_photo;
EContactPhoto *new_photo;
- gboolean have_old_photo;
- gboolean have_new_photo;
- PhotoOperation photo_operation = LEAVE_PHOTO;
+ gboolean changed = FALSE;
old_photo = e_contact_get (old_contact, E_CONTACT_PHOTO);
new_photo = e_contact_get (new_contact, E_CONTACT_PHOTO);
- have_old_photo =
- (old_photo != NULL) &&
- (old_photo->type == E_CONTACT_PHOTO_TYPE_INLINED);
+ if (!old_photo && new_photo)
+ changed = TRUE;
+
+ if (old_photo && !new_photo)
+ changed = TRUE;
- have_new_photo =
- (new_photo != NULL) &&
- (new_photo->type == E_CONTACT_PHOTO_TYPE_INLINED);
+ /* old_photo comes from cache, thus it's always URI (to local file or elsewhere),
+ while the new_photo is to be saved, which is always inlined. */
+ if (!changed && old_photo && new_photo &&
+ old_photo->type == E_CONTACT_PHOTO_TYPE_URI &&
+ new_photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
+ e_contact_photo_free (old_photo);
+ old_photo = NULL;
- if (!have_old_photo && have_new_photo)
- photo_operation = ADD_PHOTO;
+ old_contact_copy = e_contact_duplicate (old_contact);
- if (have_old_photo && !have_new_photo)
- photo_operation = REMOVE_PHOTO;
+ if (e_book_meta_backend_inline_local_photos_sync (meta_backend, old_contact_copy,
cancellable, NULL))
+ old_photo = e_contact_get (old_contact_copy, E_CONTACT_PHOTO);
+ }
- if (have_old_photo && have_new_photo) {
+ if (old_photo && new_photo &&
+ old_photo->type == E_CONTACT_PHOTO_TYPE_INLINED &&
+ new_photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
guchar *old_data;
guchar *new_data;
gsize old_length;
gsize new_length;
- gboolean changed;
old_data = old_photo->data.inlined.data;
new_data = new_photo->data.inlined.data;
@@ -1275,36 +763,30 @@ pick_photo_operation (EContact *old_contact,
changed =
(old_length != new_length) ||
(memcmp (old_data, new_data, old_length) != 0);
-
- if (changed)
- photo_operation = UPDATE_PHOTO;
}
- if (old_photo != NULL)
- e_contact_photo_free (old_photo);
-
- if (new_photo != NULL)
- e_contact_photo_free (new_photo);
+ e_contact_photo_free (old_photo);
+ e_contact_photo_free (new_photo);
+ g_clear_object (&old_contact_copy);
- return photo_operation;
+ return changed;
}
static GDataEntry *
-update_contact_photo (GDataContactsContact *contact,
- GDataContactsService *service,
- EContactPhoto *photo,
- GCancellable *cancellable,
- GError **error)
+ebb_google_update_contact_photo_sync (GDataContactsContact *contact,
+ GDataContactsService *service,
+ EContactPhoto *photo,
+ GCancellable *cancellable,
+ GError **error)
{
GDataAuthorizationDomain *authorization_domain;
- GDataEntry *new_contact = NULL;
+ GDataEntry *gdata_contact = NULL;
const gchar *content_type;
const guint8 *photo_data;
gsize photo_length;
gboolean success;
- authorization_domain =
- gdata_contacts_service_get_primary_authorization_domain ();
+ authorization_domain = gdata_contacts_service_get_primary_authorization_domain ();
if (photo != NULL) {
photo_data = (guint8 *) photo->data.inlined.data;
@@ -1325,7 +807,7 @@ update_contact_photo (GDataContactsContact *contact,
if (success) {
/* Setting the photo changes the contact's ETag,
* so query for the contact to obtain its new ETag. */
- new_contact = gdata_service_query_single_entry (
+ gdata_contact = gdata_service_query_single_entry (
GDATA_SERVICE (service),
authorization_domain,
gdata_entry_get_id (GDATA_ENTRY (contact)),
@@ -1333,146 +815,231 @@ update_contact_photo (GDataContactsContact *contact,
cancellable, error);
}
- return new_contact;
+ return gdata_contact;
}
-static void
-google_cancel_all_operations (EBookBackend *backend)
+static gboolean
+ebb_google_save_contact_sync (EBookMetaBackend *meta_backend,
+ gboolean overwrite_existing,
+ EConflictResolution conflict_resolution,
+ /* const */ EContact *contact,
+ const gchar *extra,
+ gchar **out_new_uid,
+ gchar **out_new_extra,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendGooglePrivate *priv;
- GHashTableIter iter;
- gpointer opid_ptr;
- GCancellable *cancellable;
+ EBookBackendGoogle *bbgoogle;
+ EBookCache *book_cache;
+ GDataEntry *entry = NULL;
+ GDataContactsContact *gdata_contact;
+ EContact *cached_contact = NULL;
+ EContact *new_contact;
+ const gchar *uid;
+ EContactPhoto *photo;
+ gboolean photo_changed;
+ GError *local_error = NULL;
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (meta_backend), FALSE);
+ g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
+ g_return_val_if_fail (out_new_uid != NULL, FALSE);
+ g_return_val_if_fail (out_new_extra != NULL, FALSE);
- g_debug (G_STRFUNC);
+ book_cache = e_book_meta_backend_ref_cache (meta_backend);
+ g_return_val_if_fail (book_cache != NULL, FALSE);
- if (!priv->cancellables)
- return;
+ bbgoogle = E_BOOK_BACKEND_GOOGLE (meta_backend);
- /* Cancel all active operations */
- g_hash_table_iter_init (&iter, priv->cancellables);
- while (g_hash_table_iter_next (&iter, &opid_ptr, (gpointer *) &cancellable)) {
- g_cancellable_cancel (cancellable);
+ if (!overwrite_existing || !e_book_cache_get_contact (book_cache, e_contact_get_const (contact,
E_CONTACT_UID),
+ FALSE, &cached_contact, cancellable, NULL)) {
+ cached_contact = NULL;
}
-}
-static void
-e_book_backend_google_notify_online_cb (EBookBackend *backend,
- GParamSpec *pspec)
-{
- EBookBackendGooglePrivate *priv;
- ESource *source;
- gboolean is_online;
+ if (extra && *extra)
+ entry = GDATA_ENTRY (gdata_parsable_new_from_xml (GDATA_TYPE_CONTACTS_CONTACT, extra, -1,
NULL));
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
+ g_rec_mutex_lock (&bbgoogle->priv->groups_lock);
- g_debug (G_STRFUNC);
+ /* Ensure the system groups have been fetched. */
+ if (g_hash_table_size (bbgoogle->priv->system_groups_by_id) == 0)
+ ebb_google_get_groups_sync (bbgoogle, FALSE, cancellable, NULL);
+
+ if (overwrite_existing || entry) {
+ if (gdata_entry_update_from_e_contact (entry, contact, FALSE,
+ bbgoogle->priv->groups_by_name,
+ bbgoogle->priv->system_groups_by_id,
+ ebb_google_create_group_sync,
+ bbgoogle,
+ cancellable)) {
+ overwrite_existing = TRUE;
+ } else {
+ g_clear_object (&entry);
+ }
+ } else {
+ /* Build the GDataEntry from the vCard */
+ entry = gdata_entry_new_from_e_contact (
+ contact,
+ bbgoogle->priv->groups_by_name,
+ bbgoogle->priv->system_groups_by_id,
+ ebb_google_create_group_sync,
+ bbgoogle,
+ cancellable);
+ }
- is_online = e_backend_get_online (E_BACKEND (backend));
- source = e_backend_get_source (E_BACKEND (backend));
+ g_rec_mutex_unlock (&bbgoogle->priv->groups_lock);
- if (is_online && e_book_backend_is_opened (backend)) {
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING);
+ photo_changed = cached_contact && ebb_google_photo_changed (meta_backend, cached_contact, contact,
cancellable);
- if (connect_without_password (backend, NULL, NULL)) {
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED);
+ g_clear_object (&cached_contact);
+ g_clear_object (&book_cache);
- e_book_backend_set_writable (backend, TRUE);
- cache_refresh_if_needed (backend);
- } else {
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
+ if (!entry) {
+ g_propagate_error (error, e_data_book_create_error (E_DATA_BOOK_STATUS_OTHER_ERROR, _("Object
to save is not a valid vCard")));
+ return FALSE;
+ }
- e_backend_schedule_credentials_required (E_BACKEND (backend),
E_SOURCE_CREDENTIALS_REASON_REQUIRED,
- NULL, 0, NULL, NULL, G_STRFUNC);
- }
+ if (overwrite_existing) {
+ gdata_contact = GDATA_CONTACTS_CONTACT (gdata_service_update_entry (
+ bbgoogle->priv->service,
+ gdata_contacts_service_get_primary_authorization_domain (),
+ entry, cancellable, &local_error));
} else {
- /* Going offline, so cancel all running operations */
- google_cancel_all_operations (backend);
+ gdata_contact = gdata_contacts_service_insert_contact (
+ GDATA_CONTACTS_SERVICE (bbgoogle->priv->service),
+ GDATA_CONTACTS_CONTACT (entry),
+ cancellable, &local_error);
+ }
+
+ photo = g_object_steal_data (G_OBJECT (entry), "photo");
- /* Mark the book as unwriteable if we're going offline,
- * but don't do the inverse when we go online;
- * e_book_backend_google_authenticate_user() will mark us
- * as writeable again once the user's authenticated again. */
- e_book_backend_set_writable (backend, FALSE);
+ g_object_unref (entry);
- if (e_source_get_connection_status (source) != E_SOURCE_CONNECTION_STATUS_DISCONNECTED)
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
+ if (!gdata_contact) {
+ ebb_google_data_book_error_from_gdata_error (error, local_error);
+ g_clear_error (&local_error);
+ e_contact_photo_free (photo);
- /* We can free our service. */
- g_clear_object (&priv->service);
+ return FALSE;
}
-}
-static void
-book_backend_google_dispose (GObject *object)
-{
- EBookBackendGooglePrivate *priv;
+ if (photo_changed) {
+ entry = ebb_google_update_contact_photo_sync (gdata_contact, GDATA_CONTACTS_SERVICE
(bbgoogle->priv->service), photo, cancellable, &local_error);
+ if (!entry) {
+ ebb_google_data_book_error_from_gdata_error (error, local_error);
+ g_clear_error (&local_error);
+ e_contact_photo_free (photo);
+ g_clear_object (&gdata_contact);
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (object);
+ return FALSE;
+ }
- g_debug (G_STRFUNC);
+ g_object_unref (gdata_contact);
+ gdata_contact = GDATA_CONTACTS_CONTACT (entry);
+ }
- /* Cancel all outstanding operations */
- google_cancel_all_operations (E_BOOK_BACKEND (object));
+ g_rec_mutex_lock (&bbgoogle->priv->groups_lock);
+ new_contact = e_contact_new_from_gdata_entry (GDATA_ENTRY (gdata_contact),
+ bbgoogle->priv->groups_by_id,
+ bbgoogle->priv->system_groups_by_entry_id);
+ g_rec_mutex_unlock (&bbgoogle->priv->groups_lock);
- if (priv->refresh_id > 0) {
- e_source_refresh_remove_timeout (
- e_backend_get_source (E_BACKEND (object)),
- priv->refresh_id);
- priv->refresh_id = 0;
+ if (!new_contact) {
+ g_object_unref (gdata_contact);
+ e_contact_photo_free (photo);
+ g_propagate_error (error, e_data_book_create_error (E_DATA_BOOK_STATUS_OTHER_ERROR, _("Failed
to create contact from returned server data")));
+ return FALSE;
}
- g_clear_object (&priv->service);
- g_clear_object (&priv->authorizer);
- g_clear_object (&priv->cache);
+ e_contact_set (new_contact, E_CONTACT_PHOTO, photo);
+ e_vcard_util_set_x_attribute (E_VCARD (new_contact), E_GOOGLE_X_PHOTO_ETAG,
gdata_contacts_contact_get_photo_etag (gdata_contact));
- /* Chain up to parent's dispose() method. */
- G_OBJECT_CLASS (e_book_backend_google_parent_class)->dispose (object);
+ *out_new_extra = gdata_parsable_get_xml (GDATA_PARSABLE (gdata_contact));
+
+ g_object_unref (gdata_contact);
+
+ e_contact_photo_free (photo);
+
+ uid = e_contact_get_const (new_contact, E_CONTACT_UID);
+
+ if (!uid) {
+ g_propagate_error (error, e_data_book_create_error (E_DATA_BOOK_STATUS_OTHER_ERROR, _("Server
returned contact without UID")));
+
+ g_object_unref (new_contact);
+ g_free (*out_new_extra);
+ *out_new_extra = NULL;
+
+ return FALSE;
+ }
+
+ if (bbgoogle->priv->preloaded) {
+ *out_new_uid = g_strdup (uid);
+ g_hash_table_insert (bbgoogle->priv->preloaded, g_strdup (uid), new_contact);
+ } else {
+ g_object_unref (new_contact);
+ }
+
+ return TRUE;
}
-static void
-book_backend_google_finalize (GObject *object)
+static gboolean
+ebb_google_remove_contact_sync (EBookMetaBackend *meta_backend,
+ EConflictResolution conflict_resolution,
+ const gchar *uid,
+ const gchar *extra,
+ const gchar *object,
+ GCancellable *cancellable,
+ GError **error)
{
- EBookBackendGooglePrivate *priv;
+ EBookBackendGoogle *bbgoogle;
+ GDataEntry *entry;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (meta_backend), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (extra != NULL, FALSE);
+
+ entry = GDATA_ENTRY (gdata_parsable_new_from_xml (GDATA_TYPE_CONTACTS_CONTACT, extra, -1, NULL));
+ if (!entry) {
+ g_propagate_error (error, e_data_book_create_error (E_DATA_BOOK_STATUS_INVALID_ARG, NULL));
+ return FALSE;
+ }
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (object);
+ bbgoogle = E_BOOK_BACKEND_GOOGLE (meta_backend);
- g_debug (G_STRFUNC);
+ if (!gdata_service_delete_entry (bbgoogle->priv->service,
+ gdata_contacts_service_get_primary_authorization_domain (), entry,
+ cancellable, &local_error)) {
+ ebb_google_data_book_error_from_gdata_error (error, local_error);
+ g_error_free (local_error);
+ g_object_unref (entry);
- if (priv->cancellables) {
- g_hash_table_destroy (priv->groups_by_id);
- g_hash_table_destroy (priv->groups_by_name);
- g_hash_table_destroy (priv->system_groups_by_entry_id);
- g_hash_table_destroy (priv->system_groups_by_id);
- g_hash_table_destroy (priv->cancellables);
+ return FALSE;
}
- g_mutex_clear (&priv->cache_lock);
- g_rec_mutex_clear (&priv->groups_lock);
+ g_object_unref (entry);
- /* Chain up to parent's finalize() method. */
- G_OBJECT_CLASS (e_book_backend_google_parent_class)->finalize (object);
+ return TRUE;
}
static gchar *
-book_backend_google_get_backend_property (EBookBackend *backend,
- const gchar *prop_name)
+ebb_google_get_backend_property (EBookBackend *book_backend,
+ const gchar *prop_name)
{
- g_debug (G_STRFUNC);
-
g_return_val_if_fail (prop_name != NULL, NULL);
if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
- return g_strdup ("net,do-initial-query,contact-lists,refresh-supported");
+ return g_strjoin (",",
+ "net",
+ "do-initial-query",
+ "contact-lists",
+ e_book_meta_backend_get_capabilities (E_BOOK_META_BACKEND (book_backend)),
+ NULL);
} else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
return g_strdup ("");
} else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
- return g_strjoin (
- ",",
+ return g_strjoin (",",
e_contact_field_name (E_CONTACT_UID),
e_contact_field_name (E_CONTACT_REV),
e_contact_field_name (E_CONTACT_FULL_NAME),
@@ -1600,774 +1167,99 @@ book_backend_google_get_backend_property (EBookBackend *backend,
e_contact_field_name (E_CONTACT_NOTE),
e_contact_field_name (E_CONTACT_PHOTO),
e_contact_field_name (E_CONTACT_CATEGORIES),
-#if defined(GDATA_CHECK_VERSION)
-#if GDATA_CHECK_VERSION(0, 11, 0)
e_contact_field_name (E_CONTACT_CATEGORY_LIST),
e_contact_field_name (E_CONTACT_FILE_AS),
-#else
- e_contact_field_name (E_CONTACT_CATEGORY_LIST),
-#endif
-#else
- e_contact_field_name (E_CONTACT_CATEGORY_LIST),
-#endif
e_contact_field_name (E_CONTACT_NICKNAME),
NULL);
}
- /* Chain up to parent's get_backend_property() method. */
- return E_BOOK_BACKEND_CLASS (e_book_backend_google_parent_class)->
- get_backend_property (backend, prop_name);
+ /* Chain up to parent's method. */
+ return E_BOOK_BACKEND_CLASS (e_book_backend_google_parent_class)->get_backend_property (book_backend,
prop_name);
}
-static gboolean
-book_backend_google_open_sync (EBookBackend *backend,
- GCancellable *cancellable,
- GError **error)
-{
- EBookBackendGooglePrivate *priv;
- gboolean is_online;
- gboolean success = TRUE;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_debug (G_STRFUNC);
-
- if (priv->cancellables && backend_is_authorized (backend))
- return TRUE;
-
- /* Set up our object */
- if (priv->cancellables == NULL) {
- priv->groups_by_id = g_hash_table_new_full (
- (GHashFunc) g_str_hash,
- (GEqualFunc) g_str_equal,
- (GDestroyNotify) g_free,
- (GDestroyNotify) g_free);
- priv->groups_by_name = g_hash_table_new_full (
- (GHashFunc) g_str_hash,
- (GEqualFunc) g_str_equal,
- (GDestroyNotify) g_free,
- (GDestroyNotify) g_free);
- priv->system_groups_by_id = g_hash_table_new_full (
- (GHashFunc) g_str_hash,
- (GEqualFunc) g_str_equal,
- (GDestroyNotify) g_free,
- (GDestroyNotify) g_free);
- /* shares keys and values with system_groups_by_id */
- priv->system_groups_by_entry_id = g_hash_table_new (
- (GHashFunc) g_str_hash,
- (GEqualFunc) g_str_equal);
- priv->cancellables = g_hash_table_new_full (
- (GHashFunc) g_direct_hash,
- (GEqualFunc) g_direct_equal,
- (GDestroyNotify) NULL,
- (GDestroyNotify) g_object_unref);
- }
-
- cache_init (backend);
-
- /* Set up ready to be interacted with */
- is_online = e_backend_get_online (E_BACKEND (backend));
- e_book_backend_set_writable (backend, FALSE);
-
- if (is_online) {
- ESource *source = e_backend_get_source (E_BACKEND (backend));
-
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING);
-
- success = connect_without_password (backend, cancellable, error);
- if (success) {
- GError *local_error = NULL;
-
- /* Refresh the authorizer. This may block. */
- success = gdata_authorizer_refresh_authorization (
- priv->authorizer, cancellable, &local_error);
-
- if (success) {
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED);
- } else {
- GError *local_error2 = NULL;
-
- e_source_set_connection_status (source,
E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
-
- if (local_error && !e_backend_credentials_required_sync (E_BACKEND (backend),
E_SOURCE_CREDENTIALS_REASON_ERROR,
- NULL, 0, local_error, cancellable, &local_error2)) {
- g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC,
local_error2 ? local_error2->message : "Unknown error");
- }
-
- g_clear_error (&local_error2);
-
- if (local_error)
- g_propagate_error (error, local_error);
- }
- } else {
- GError *local_error = NULL;
-
- e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
-
- if (!e_backend_credentials_required_sync (E_BACKEND (backend),
E_SOURCE_CREDENTIALS_REASON_REQUIRED,
- NULL, 0, NULL, cancellable, &local_error)) {
- g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC,
local_error ? local_error->message : "Unknown error");
- }
-
- g_clear_error (&local_error);
- }
- }
-
- if (is_online && backend_is_authorized (backend)) {
- e_book_backend_set_writable (backend, TRUE);
- cache_refresh_if_needed (backend);
- }
-
- return success;
-}
-
-static gboolean
-book_backend_google_create_contacts_sync (EBookBackend *backend,
- const gchar * const *vcards,
- GQueue *out_contacts,
- GCancellable *cancellable,
- GError **error)
-{
- EBookBackendGooglePrivate *priv;
- EContactPhoto *photo = NULL;
- EContact *contact;
- GDataEntry *entry;
- GDataContactsContact *new_contact;
- gchar *xml;
- gboolean success = TRUE;
- GError *gdata_error = NULL;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- /* We make the assumption that the vCard list we're passed is always
- * exactly one element long, since we haven't specified "bulk-adds"
- * in our static capability list. This simplifies the logic. */
- if (g_strv_length ((gchar **) vcards) > 1) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_NOT_SUPPORTED,
- _("The backend does not support bulk additions"));
- return FALSE;
- }
-
- g_debug (G_STRFUNC);
-
- g_debug ("Creating: %s", vcards[0]);
-
- if (!e_backend_get_online (E_BACKEND (backend))) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OFFLINE_UNAVAILABLE,
- e_client_error_to_string (
- E_CLIENT_ERROR_OFFLINE_UNAVAILABLE));
- return FALSE;
- }
-
- g_warn_if_fail (backend_is_authorized (backend));
-
- g_rec_mutex_lock (&priv->groups_lock);
-
- /* Ensure the system groups have been fetched. */
- if (g_hash_table_size (priv->system_groups_by_id) == 0)
- get_groups_sync (backend, cancellable, NULL);
-
- /* Build the GDataEntry from the vCard */
- contact = e_contact_new_from_vcard (vcards[0]);
- entry = gdata_entry_new_from_e_contact (
- contact,
- priv->groups_by_name,
- priv->system_groups_by_id,
- _create_group, backend);
- g_object_unref (contact);
-
- g_rec_mutex_unlock (&priv->groups_lock);
-
- /* Debug XML output */
- xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
- g_debug ("new entry with xml: %s", xml);
- g_free (xml);
-
- new_contact = gdata_contacts_service_insert_contact (
- GDATA_CONTACTS_SERVICE (priv->service),
- GDATA_CONTACTS_CONTACT (entry),
- cancellable, &gdata_error);
-
- if (new_contact == NULL) {
- success = FALSE;
- goto exit;
- }
-
- /* Add a photo for the new contact, if appropriate. This has to
- * be done before we finish the contact creation operation so we
- * can update the EContact with the photo data and ETag. */
- photo = g_object_steal_data (G_OBJECT (entry), "photo");
- if (photo != NULL) {
- GDataEntry *updated_entry;
- gchar *xml;
-
- updated_entry = update_contact_photo (
- new_contact,
- GDATA_CONTACTS_SERVICE (priv->service),
- photo, cancellable, &gdata_error);
-
- /* Sanity check. */
- g_return_val_if_fail (
- ((updated_entry != NULL) && (gdata_error == NULL)) ||
- ((updated_entry == NULL) && (gdata_error != NULL)),
- FALSE);
-
- if (gdata_error != NULL) {
- g_debug (
- "Uploading contact photo "
- "for '%s' failed: %s",
- gdata_entry_get_id (GDATA_ENTRY (new_contact)),
- gdata_error->message);
- success = FALSE;
- goto exit;
- }
-
- /* Output debug XML */
- xml = gdata_parsable_get_xml (
- GDATA_PARSABLE (updated_entry));
- g_debug ("After re-querying:\n%s", xml);
- g_free (xml);
-
- g_object_unref (new_contact);
- new_contact = GDATA_CONTACTS_CONTACT (updated_entry);
-
- /* Store the photo on the final GDataContactsContact
- * object so it makes it into the cache. */
- g_object_set_data_full (
- G_OBJECT (new_contact), "photo", photo,
- (GDestroyNotify) e_contact_photo_free);
- photo = NULL;
- }
-
- contact = cache_add_contact (backend, GDATA_ENTRY (new_contact));
- if (contact) {
- g_queue_push_tail (out_contacts, g_object_ref (contact));
- g_object_unref (contact);
- }
-
-exit:
- g_clear_object (&entry);
- g_clear_object (&new_contact);
-
- if (photo != NULL)
- e_contact_photo_free (photo);
-
- if (gdata_error != NULL) {
- g_warn_if_fail (success == FALSE);
- data_book_error_from_gdata_error (error, gdata_error);
- g_error_free (gdata_error);
- } else {
- e_backend_ensure_source_status_connected (E_BACKEND (backend));
- }
-
- return success;
-}
-
-static gboolean
-book_backend_google_modify_contacts_sync (EBookBackend *backend,
- const gchar * const *vcards,
- GQueue *out_contacts,
- GCancellable *cancellable,
- GError **error)
-{
- EBookBackendGooglePrivate *priv;
- GDataAuthorizationDomain *authorization_domain;
- EContact *contact, *cached_contact;
- PhotoOperation photo_operation;
- EContactPhoto *photo;
- GDataEntry *entry = NULL;
- GDataEntry *new_contact;
- const gchar *uid;
- gboolean success = TRUE;
- gchar *xml;
- GError *gdata_error = NULL;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- authorization_domain =
- gdata_contacts_service_get_primary_authorization_domain ();
-
- g_debug (G_STRFUNC);
-
- g_debug ("Updating: %s", vcards[0]);
-
- /* We make the assumption that the vCard list we're passed is
- * always exactly one element long, since we haven't specified
- * "bulk-modifies" in our static capability list. This is because
- * there is no clean way to roll back changes in case of an error. */
- if (g_strv_length ((gchar **) vcards) > 1) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_NOT_SUPPORTED,
- _("The backend does not support bulk modifications"));
- return FALSE;
- }
-
- if (!e_backend_get_online (E_BACKEND (backend))) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OFFLINE_UNAVAILABLE,
- e_client_error_to_string (
- E_CLIENT_ERROR_OFFLINE_UNAVAILABLE));
- return FALSE;
- }
-
- g_warn_if_fail (backend_is_authorized (backend));
-
- /* Get the new contact and its UID. */
- contact = e_contact_new_from_vcard (vcards[0]);
- uid = e_contact_get (contact, E_CONTACT_UID);
-
- /* Get the old cached contact with the same UID,
- * and its associated GDataEntry. */
- cached_contact = cache_get_contact (backend, uid, &entry);
-
- if (cached_contact == NULL) {
- g_debug (
- "Modifying contact failed: "
- "Contact with uid %s not found in cache.", uid);
- g_object_unref (contact);
-
- g_set_error_literal (
- error, E_BOOK_CLIENT_ERROR,
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
- e_book_client_error_to_string (
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND));
- return FALSE;
- }
-
- g_rec_mutex_lock (&priv->groups_lock);
-
- /* Ensure the system groups have been fetched. */
- if (g_hash_table_size (priv->system_groups_by_id) == 0)
- get_groups_sync (backend, cancellable, NULL);
-
- /* Update the old GDataEntry from the new contact. */
- gdata_entry_update_from_e_contact (
- entry, contact, FALSE,
- priv->groups_by_name,
- priv->system_groups_by_id,
- _create_group, backend);
-
- g_rec_mutex_unlock (&priv->groups_lock);
-
- /* Output debug XML */
- xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
- g_debug ("Before:\n%s", xml);
- g_free (xml);
-
- photo = g_object_steal_data (G_OBJECT (entry), "photo");
-
- /* Update the contact's photo. We can't rely on the ETags at this
- * point, as the ETag in @contact may be out of sync with the photo
- * in the EContact (since the photo may have been updated).
- * Consequently, after updating @entry its ETag may also be out of
- * sync with its attached photo data. This means that we have to
- * detect whether the photo has changed by comparing the photo data
- * itself, which is guaranteed to be in sync between @contact and
- * @entry. */
- photo_operation = pick_photo_operation (cached_contact, contact);
-
- /* Sanity check the photo operation. */
- switch (photo_operation) {
- case LEAVE_PHOTO:
- break;
-
- case ADD_PHOTO:
- case UPDATE_PHOTO:
- g_return_val_if_fail (photo != NULL, FALSE);
- break;
-
- case REMOVE_PHOTO:
- g_return_val_if_fail (photo == NULL, FALSE);
- break;
-
- default:
- g_return_val_if_reached (FALSE);
- }
-
- g_clear_object (&cached_contact);
- g_clear_object (&contact);
-
- new_contact = gdata_service_update_entry (
- priv->service,
- authorization_domain,
- entry,
- cancellable, &gdata_error);
-
- if (new_contact == NULL) {
- g_debug (
- "Modifying contact failed: %s",
- gdata_error->message);
- success = FALSE;
- goto exit;
- }
-
- /* Output debug XML */
- xml = gdata_parsable_get_xml (GDATA_PARSABLE (new_contact));
- g_debug ("After:\n%s", xml);
- g_free (xml);
-
- /* Add a photo for the new contact, if appropriate. This has to be
- * done before we respond to the contact creation operation so that
- * we can update the EContact with the photo data and ETag. */
- if (photo_operation != LEAVE_PHOTO) {
- GDataEntry *updated_entry;
-
- updated_entry = update_contact_photo (
- GDATA_CONTACTS_CONTACT (new_contact),
- GDATA_CONTACTS_SERVICE (priv->service),
- photo, cancellable, &gdata_error);
-
- /* Sanity check. */
- g_return_val_if_fail (
- ((updated_entry != NULL) && (gdata_error == NULL)) ||
- ((updated_entry == NULL) && (gdata_error != NULL)),
- FALSE);
-
- if (gdata_error != NULL) {
- g_debug (
- "Uploading contact photo "
- "for '%s' failed: %s",
- gdata_entry_get_id (new_contact),
- gdata_error->message);
- success = FALSE;
- goto exit;
- }
-
- /* Output debug XML */
- xml = gdata_parsable_get_xml (
- GDATA_PARSABLE (updated_entry));
- g_debug ("After re-querying:\n%s", xml);
- g_free (xml);
-
- g_object_unref (new_contact);
- new_contact = updated_entry;
- }
-
- /* Store the photo on the final GDataEntry
- * object so it makes it to the cache. */
- if (photo != NULL) {
- g_object_set_data_full (
- G_OBJECT (new_contact), "photo", photo,
- (GDestroyNotify) e_contact_photo_free);
- photo = NULL;
- } else {
- g_object_set_data (
- G_OBJECT (new_contact), "photo", NULL);
- }
-
- contact = cache_add_contact (backend, new_contact);
- if (contact) {
- g_queue_push_tail (out_contacts, g_object_ref (contact));
- g_object_unref (contact);
- }
-
-exit:
- g_clear_object (&entry);
- g_clear_object (&new_contact);
-
- if (photo != NULL)
- e_contact_photo_free (photo);
-
- if (gdata_error != NULL) {
- g_warn_if_fail (success == FALSE);
- data_book_error_from_gdata_error (error, gdata_error);
- g_error_free (gdata_error);
- } else {
- e_backend_ensure_source_status_connected (E_BACKEND (backend));
- }
-
- return success;
-}
-
-static gboolean
-book_backend_google_remove_contacts_sync (EBookBackend *backend,
- const gchar *const *uids,
- GCancellable *cancellable,
- GError **error)
-{
- EBookBackendGooglePrivate *priv;
- GDataAuthorizationDomain *authorization_domain;
- GDataEntry *entry = NULL;
- EContact *cached_contact;
- gboolean success;
- GError *gdata_error = NULL;
-
- priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- authorization_domain =
- gdata_contacts_service_get_primary_authorization_domain ();
-
- g_debug (G_STRFUNC);
-
- /* We make the assumption that the ID list we're passed is always
- * exactly one element long, since we haven't specified "bulk-removes"
- * in our static capability list. This simplifies the logic. */
- if (g_strv_length ((gchar **) uids) > 1) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_NOT_SUPPORTED,
- _("The backend does not support bulk removals"));
- return FALSE;
- }
-
- if (!e_backend_get_online (E_BACKEND (backend))) {
- g_set_error_literal (
- error, E_CLIENT_ERROR,
- E_CLIENT_ERROR_OFFLINE_UNAVAILABLE,
- e_client_error_to_string (
- E_CLIENT_ERROR_OFFLINE_UNAVAILABLE));
- return FALSE;
- }
-
- g_warn_if_fail (backend_is_authorized (backend));
-
- /* Get the contact and associated GDataEntry from the cache */
- cached_contact = cache_get_contact (backend, uids[0], &entry);
-
- if (cached_contact == NULL) {
- g_set_error_literal (
- error, E_BOOK_CLIENT_ERROR,
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
- e_book_client_error_to_string (
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND));
- return FALSE;
- }
-
- g_object_unref (cached_contact);
-
- /* Remove the contact from the cache */
- cache_remove_contact (backend, uids[0]);
-
- success = gdata_service_delete_entry (
- priv->service,
- authorization_domain, entry,
- cancellable, &gdata_error);
-
- g_object_unref (entry);
-
- if (gdata_error != NULL) {
- g_warn_if_fail (success == FALSE);
- data_book_error_from_gdata_error (error, gdata_error);
- g_error_free (gdata_error);
- } else {
- e_backend_ensure_source_status_connected (E_BACKEND (backend));
- }
-
- return success;
-}
-
-static EContact *
-book_backend_google_get_contact_sync (EBookBackend *backend,
- const gchar *uid,
- GCancellable *cancellable,
- GError **error)
-{
- EContact *contact;
-
- g_debug (G_STRFUNC);
-
- /* Get the contact */
- contact = cache_get_contact (backend, uid, NULL);
- if (contact == NULL) {
- g_set_error_literal (
- error, E_BOOK_CLIENT_ERROR,
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
- e_book_client_error_to_string (
- E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND));
- }
-
- return contact;
-}
-
-static gboolean
-book_backend_google_get_contact_list_sync (EBookBackend *backend,
- const gchar *query,
- GQueue *out_contacts,
- GCancellable *cancellable,
- GError **error)
+static void
+ebb_google_constructed (GObject *object)
{
- EBookBackendSExp *sexp;
- GQueue queue = G_QUEUE_INIT;
-
- g_debug (G_STRFUNC);
-
- sexp = e_book_backend_sexp_new (query);
-
- /* Get all contacts */
- cache_get_contacts (backend, &queue);
-
- while (!g_queue_is_empty (&queue)) {
- EContact *contact;
-
- contact = g_queue_pop_head (&queue);
+ EBookBackendGoogle *bbgoogle = E_BOOK_BACKEND_GOOGLE (object);
- /* If the search expression matches the contact,
- * include it in the search results. */
- if (e_book_backend_sexp_match_contact (sexp, contact)) {
- g_object_ref (contact);
- g_queue_push_tail (out_contacts, contact);
- }
-
- g_object_unref (contact);
- }
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_book_backend_google_parent_class)->constructed (object);
- g_object_unref (sexp);
-
- return TRUE;
+ /* Set it as always writable, regardless online/offline state */
+ e_book_backend_set_writable (E_BOOK_BACKEND (bbgoogle), TRUE);
}
static void
-book_backend_google_start_view (EBookBackend *backend,
- EDataBookView *bookview)
+ebb_google_dispose (GObject *object)
{
- GQueue queue = G_QUEUE_INIT;
- GError *error = NULL;
-
- g_return_if_fail (E_IS_BOOK_BACKEND_GOOGLE (backend));
- g_return_if_fail (E_IS_DATA_BOOK_VIEW (bookview));
+ EBookBackendGoogle *bbgoogle = E_BOOK_BACKEND_GOOGLE (object);
- g_debug (G_STRFUNC);
-
- e_data_book_view_notify_progress (bookview, -1, _("Loading…"));
-
- /* Ensure that we're ready to support a view */
- cache_refresh_if_needed (backend);
-
- /* Get the contacts */
- cache_get_contacts (backend, &queue);
- g_debug (
- "%d contacts found in cache",
- g_queue_get_length (&queue));
-
- /* Notify the view that all the contacts have changed (i.e. been added) */
- while (!g_queue_is_empty (&queue)) {
- EContact *contact;
+ g_clear_object (&bbgoogle->priv->service);
+ g_clear_object (&bbgoogle->priv->authorizer);
- contact = g_queue_pop_head (&queue);
- e_data_book_view_notify_update (bookview, contact);
- g_object_unref (contact);
- }
+ g_hash_table_destroy (bbgoogle->priv->preloaded);
+ bbgoogle->priv->preloaded = NULL;
- /* This function frees the GError passed to it. */
- e_data_book_view_notify_complete (bookview, error);
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_book_backend_google_parent_class)->dispose (object);
}
static void
-book_backend_google_stop_view (EBookBackend *backend,
- EDataBookView *bookview)
+ebb_google_finalize (GObject *object)
{
- g_debug (G_STRFUNC);
-}
+ EBookBackendGoogle *bbgoogle = E_BOOK_BACKEND_GOOGLE (object);
-static gboolean
-book_backend_google_refresh_sync (EBookBackend *backend,
- GCancellable *cancellable,
- GError **error)
-{
- g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (backend), FALSE);
+ g_clear_pointer (&bbgoogle->priv->groups_by_id, (GDestroyNotify) g_hash_table_destroy);
+ g_clear_pointer (&bbgoogle->priv->groups_by_id, (GDestroyNotify) g_hash_table_destroy);
+ g_clear_pointer (&bbgoogle->priv->groups_by_name, (GDestroyNotify) g_hash_table_destroy);
+ g_clear_pointer (&bbgoogle->priv->system_groups_by_entry_id, (GDestroyNotify) g_hash_table_destroy);
+ g_clear_pointer (&bbgoogle->priv->system_groups_by_id, (GDestroyNotify) g_hash_table_destroy);
- /* get only changes, it's not needed to redownload whole cache */
- get_new_contacts (backend);
+ g_rec_mutex_clear (&bbgoogle->priv->groups_lock);
- return TRUE;
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_book_backend_google_parent_class)->finalize (object);
}
-static ESourceAuthenticationResult
-book_backend_google_authenticate_sync (EBackend *backend,
- const ENamedParameters *credentials,
- gchar **out_certificate_pem,
- GTlsCertificateFlags *out_certificate_errors,
- GCancellable *cancellable,
- GError **error)
+static void
+e_book_backend_google_init (EBookBackendGoogle *bbgoogle)
{
- EBookBackend *book_backend = E_BOOK_BACKEND (backend);
- EBookBackendGooglePrivate *priv;
- ESourceAuthenticationResult result;
- EGDataOAuth2Authorizer *authorizer;
- GError *local_error = NULL;
+ bbgoogle->priv = G_TYPE_INSTANCE_GET_PRIVATE (bbgoogle, E_TYPE_BOOK_BACKEND_GOOGLE,
EBookBackendGooglePrivate);
+ bbgoogle->priv->preloaded = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
- g_debug (G_STRFUNC);
+ g_rec_mutex_init (&bbgoogle->priv->groups_lock);
- /* We should not have gotten here if we're offline. */
- g_return_val_if_fail (e_backend_get_online (backend), E_SOURCE_AUTHENTICATION_ERROR);
-
- priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
-
- g_return_val_if_fail (E_IS_GDATA_OAUTH2_AUTHORIZER (priv->authorizer), E_SOURCE_AUTHENTICATION_ERROR);
-
- authorizer = E_GDATA_OAUTH2_AUTHORIZER (priv->authorizer);
- e_gdata_oauth2_authorizer_set_credentials (authorizer, credentials);
-
- get_groups_sync (E_BOOK_BACKEND (backend), cancellable, &local_error);
-
- if (local_error == NULL) {
- result = E_SOURCE_AUTHENTICATION_ACCEPTED;
-
- if (backend_is_authorized (book_backend)) {
- e_book_backend_set_writable (book_backend, TRUE);
- cache_refresh_if_needed (book_backend);
- }
- } else if (g_error_matches (local_error, GDATA_SERVICE_ERROR,
GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED)) {
- if (!e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_PASSWORD))
- result = E_SOURCE_AUTHENTICATION_REQUIRED;
- else
- result = E_SOURCE_AUTHENTICATION_REJECTED;
- g_clear_error (&local_error);
- } else {
- g_propagate_error (error, local_error);
- result = E_SOURCE_AUTHENTICATION_ERROR;
- }
-
- return result;
+ bbgoogle->priv->groups_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ bbgoogle->priv->groups_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ bbgoogle->priv->system_groups_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ /* shares keys and values with system_groups_by_id */
+ bbgoogle->priv->system_groups_by_entry_id = g_hash_table_new (g_str_hash, g_str_equal);
}
static void
-e_book_backend_google_class_init (EBookBackendGoogleClass *class)
+e_book_backend_google_class_init (EBookBackendGoogleClass *klass)
{
GObjectClass *object_class;
- EBackendClass *backend_class;
EBookBackendClass *book_backend_class;
-
- g_type_class_add_private (class, sizeof (EBookBackendGooglePrivate));
-
- object_class = G_OBJECT_CLASS (class);
- object_class->dispose = book_backend_google_dispose;
- object_class->finalize = book_backend_google_finalize;
-
- backend_class = E_BACKEND_CLASS (class);
- backend_class->authenticate_sync = book_backend_google_authenticate_sync;
-
- book_backend_class = E_BOOK_BACKEND_CLASS (class);
- book_backend_class->get_backend_property = book_backend_google_get_backend_property;
- book_backend_class->open_sync = book_backend_google_open_sync;
- book_backend_class->create_contacts_sync = book_backend_google_create_contacts_sync;
- book_backend_class->modify_contacts_sync = book_backend_google_modify_contacts_sync;
- book_backend_class->remove_contacts_sync = book_backend_google_remove_contacts_sync;
- book_backend_class->get_contact_sync = book_backend_google_get_contact_sync;
- book_backend_class->get_contact_list_sync = book_backend_google_get_contact_list_sync;
- book_backend_class->start_view = book_backend_google_start_view;
- book_backend_class->stop_view = book_backend_google_stop_view;
- book_backend_class->refresh_sync = book_backend_google_refresh_sync;
-}
-
-static void
-e_book_backend_google_init (EBookBackendGoogle *backend)
-{
- g_debug (G_STRFUNC);
-
- backend->priv = E_BOOK_BACKEND_GOOGLE_GET_PRIVATE (backend);
-
- g_mutex_init (&backend->priv->cache_lock);
- g_rec_mutex_init (&backend->priv->groups_lock);
-
- g_signal_connect (
- backend, "notify::online",
- G_CALLBACK (e_book_backend_google_notify_online_cb), NULL);
+ EBookMetaBackendClass *book_meta_backend_class;
+
+ g_type_class_add_private (klass, sizeof (EBookBackendGooglePrivate));
+
+ book_meta_backend_class = E_BOOK_META_BACKEND_CLASS (klass);
+ book_meta_backend_class->backend_module_filename = "libebookbackendgoogle.so";
+ book_meta_backend_class->backend_factory_type_name = "EBookBackendGoogleFactory";
+ book_meta_backend_class->connect_sync = ebb_google_connect_sync;
+ book_meta_backend_class->disconnect_sync = ebb_google_disconnect_sync;
+ book_meta_backend_class->get_changes_sync = ebb_google_get_changes_sync;
+ book_meta_backend_class->load_contact_sync = ebb_google_load_contact_sync;
+ book_meta_backend_class->save_contact_sync = ebb_google_save_contact_sync;
+ book_meta_backend_class->remove_contact_sync = ebb_google_remove_contact_sync;
+
+ book_backend_class = E_BOOK_BACKEND_CLASS (klass);
+ book_backend_class->get_backend_property = ebb_google_get_backend_property;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->constructed = ebb_google_constructed;
+ object_class->dispose = ebb_google_dispose;
+ object_class->finalize = ebb_google_finalize;
}
-
diff --git a/src/addressbook/backends/google/e-book-backend-google.h
b/src/addressbook/backends/google/e-book-backend-google.h
index c910efd..fcbf1de 100644
--- a/src/addressbook/backends/google/e-book-backend-google.h
+++ b/src/addressbook/backends/google/e-book-backend-google.h
@@ -48,12 +48,12 @@ typedef struct _EBookBackendGoogleClass EBookBackendGoogleClass;
typedef struct _EBookBackendGooglePrivate EBookBackendGooglePrivate;
struct _EBookBackendGoogle {
- EBookBackend parent_object;
+ EBookMetaBackend parent_object;
EBookBackendGooglePrivate *priv;
};
struct _EBookBackendGoogleClass {
- EBookBackendClass parent_class;
+ EBookMetaBackendClass parent_class;
};
GType e_book_backend_google_get_type (void);
diff --git a/src/addressbook/backends/google/e-book-google-utils.c
b/src/addressbook/backends/google/e-book-google-utils.c
index 4157894..027eb25 100644
--- a/src/addressbook/backends/google/e-book-google-utils.c
+++ b/src/addressbook/backends/google/e-book-google-utils.c
@@ -68,10 +68,11 @@ static gboolean is_known_google_im_protocol (const gchar *protocol);
GDataEntry *
gdata_entry_new_from_e_contact (EContact *contact,
- GHashTable *groups_by_name,
- GHashTable *system_groups_by_id,
- EContactGoogleCreateGroupFunc create_group,
- gpointer create_group_user_data)
+ GHashTable *groups_by_name,
+ GHashTable *system_groups_by_id,
+ EContactGoogleCreateGroupFunc create_group,
+ EBookBackendGoogle *bbgoogle,
+ GCancellable *cancellable)
{
GDataEntry *entry;
@@ -83,7 +84,7 @@ gdata_entry_new_from_e_contact (EContact *contact,
entry = GDATA_ENTRY (gdata_contacts_contact_new (NULL));
- if (gdata_entry_update_from_e_contact (entry, contact, TRUE, groups_by_name, system_groups_by_id,
create_group, create_group_user_data))
+ if (gdata_entry_update_from_e_contact (entry, contact, TRUE, groups_by_name, system_groups_by_id,
create_group, bbgoogle, cancellable))
return entry;
g_object_unref (entry);
@@ -117,12 +118,13 @@ remove_anniversary (GDataContactsContact *contact)
gboolean
gdata_entry_update_from_e_contact (GDataEntry *entry,
- EContact *contact,
- gboolean ensure_personal_group,
- GHashTable *groups_by_name,
- GHashTable *system_groups_by_id,
- EContactGoogleCreateGroupFunc create_group,
- gpointer create_group_user_data)
+ EContact *contact,
+ gboolean ensure_personal_group,
+ GHashTable *groups_by_name,
+ GHashTable *system_groups_by_id,
+ EContactGoogleCreateGroupFunc create_group,
+ EBookBackendGoogle *bbgoogle,
+ GCancellable *cancellable)
{
GList *attributes, *iter, *category_names, *extended_property_names;
EContactName *name_struct = NULL;
@@ -228,6 +230,7 @@ gdata_entry_update_from_e_contact (GDataEntry *entry,
name = e_vcard_attribute_get_name (attr);
if (0 == g_ascii_strcasecmp (name, EVC_UID) ||
+ 0 == g_ascii_strcasecmp (name, EVC_REV) ||
0 == g_ascii_strcasecmp (name, EVC_N) ||
0 == g_ascii_strcasecmp (name, EVC_FN) ||
0 == g_ascii_strcasecmp (name, EVC_LABEL) ||
@@ -239,7 +242,8 @@ gdata_entry_update_from_e_contact (GDataEntry *entry,
0 == g_ascii_strcasecmp (name, EVC_CATEGORIES) ||
0 == g_ascii_strcasecmp (name, EVC_PHOTO) ||
0 == g_ascii_strcasecmp (name, GOOGLE_SYSTEM_GROUP_ATTR) ||
- 0 == g_ascii_strcasecmp (name, e_contact_field_name (E_CONTACT_NICKNAME))) {
+ 0 == g_ascii_strcasecmp (name, e_contact_field_name (E_CONTACT_NICKNAME)) ||
+ 0 == g_ascii_strcasecmp (name, E_GOOGLE_X_PHOTO_ETAG)) {
/* Ignore attributes which are treated separately */
} else if (0 == g_ascii_strcasecmp (name, EVC_EMAIL)) {
/* EMAIL */
@@ -450,12 +454,12 @@ gdata_entry_update_from_e_contact (GDataEntry *entry,
if (category_id == NULL)
category_id = g_strdup (g_hash_table_lookup (groups_by_name, category_name));
if (category_id == NULL) {
- GError *error = NULL;
+ GError *local_error = NULL;
- category_id = create_group (category_name, create_group_user_data, &error);
+ category_id = create_group (bbgoogle, category_name, cancellable, &local_error);
if (category_id == NULL) {
- g_warning ("Error creating group '%s': %s", category_name, error->message);
- g_error_free (error);
+ g_warning ("Error creating group '%s': %s", category_name, local_error ?
local_error->message : "Unknown error");
+ g_clear_error (&local_error);
continue;
}
}
@@ -565,8 +569,6 @@ e_contact_new_from_gdata_entry (GDataEntry *entry,
{
EVCard *vcard;
EVCardAttribute *attr, *system_group_ids_attr;
- EContactPhoto *photo;
- const gchar *photo_etag;
GList *email_addresses, *im_addresses, *phone_numbers, *postal_addresses, *orgs, *category_names,
*category_ids;
const gchar *uid, *note, *nickname;
GList *itr;
@@ -858,20 +860,6 @@ e_contact_new_from_gdata_entry (GDataEntry *entry,
break;
}
- /* PHOTO */
- photo = g_object_get_data (G_OBJECT (entry), "photo");
- photo_etag = gdata_contacts_contact_get_photo_etag (GDATA_CONTACTS_CONTACT (entry));
-
- if (photo != NULL) {
- /* Photo */
- e_contact_set (E_CONTACT (vcard), E_CONTACT_PHOTO, photo);
-
- /* ETag */
- attr = e_vcard_attribute_new ("", GDATA_PHOTO_ETAG_ATTR);
- e_vcard_attribute_add_value (attr, photo_etag);
- e_vcard_add_attribute (vcard, attr);
- }
-
return E_CONTACT (vcard);
}
diff --git a/src/addressbook/backends/google/e-book-google-utils.h
b/src/addressbook/backends/google/e-book-google-utils.h
index fdeb24d..6d7e133 100644
--- a/src/addressbook/backends/google/e-book-google-utils.h
+++ b/src/addressbook/backends/google/e-book-google-utils.h
@@ -20,19 +20,34 @@
#ifndef E_BOOK_GOOGLE_UTILS_H
#define E_BOOK_GOOGLE_UTILS_H
-G_BEGIN_DECLS
+#include <gdata/gdata.h>
+
+#include "e-book-backend-google.h"
-/* Custom attribute names. */
-#define GDATA_PHOTO_ETAG_ATTR "X-GDATA-PHOTO-ETAG"
+#define E_GOOGLE_X_PHOTO_ETAG "X-EVOLUTION-GOOGLE-PHOTO-ETAG"
+
+G_BEGIN_DECLS
-typedef gchar *(*EContactGoogleCreateGroupFunc) (const gchar *category_name, gpointer user_data, GError
**error);
+typedef gchar *(*EContactGoogleCreateGroupFunc) (EBookBackendGoogle *bbgoogle,
+ const gchar *category_name,
+ GCancellable *cancellable,
+ GError **error);
-GDataEntry *gdata_entry_new_from_e_contact (EContact *contact, GHashTable *groups_by_name, GHashTable
*system_groups_by_id,
- EContactGoogleCreateGroupFunc create_group,
- gpointer create_group_user_data) G_GNUC_MALLOC
G_GNUC_WARN_UNUSED_RESULT;
-gboolean gdata_entry_update_from_e_contact (GDataEntry *entry, EContact *contact, gboolean
ensure_personal_group, GHashTable *groups_by_name,
- GHashTable *system_groups_by_id,
- EContactGoogleCreateGroupFunc create_group, gpointer
create_group_user_data);
+GDataEntry * gdata_entry_new_from_e_contact (EContact *contact,
+ GHashTable *groups_by_name,
+ GHashTable *system_groups_by_id,
+ EContactGoogleCreateGroupFunc create_group,
+ EBookBackendGoogle *bbgoogle,
+ GCancellable *cancellable) G_GNUC_MALLOC
G_GNUC_WARN_UNUSED_RESULT;
+gboolean gdata_entry_update_from_e_contact
+ (GDataEntry *entry,
+ EContact *contact,
+ gboolean ensure_personal_group,
+ GHashTable *groups_by_name,
+ GHashTable *system_groups_by_id,
+ EContactGoogleCreateGroupFunc create_group,
+ EBookBackendGoogle *bbgoogle,
+ GCancellable *cancellable);
EContact *e_contact_new_from_gdata_entry (GDataEntry *entry, GHashTable *groups_by_id,
GHashTable *system_groups_by_entry_id) G_GNUC_MALLOC
G_GNUC_WARN_UNUSED_RESULT;
diff --git a/src/addressbook/backends/webdav/e-book-backend-webdav.c
b/src/addressbook/backends/webdav/e-book-backend-webdav.c
index a4617d9..9ffbace 100644
--- a/src/addressbook/backends/webdav/e-book-backend-webdav.c
+++ b/src/addressbook/backends/webdav/e-book-backend-webdav.c
@@ -45,47 +45,6 @@ struct _EBookBackendWebDAVPrivate {
G_DEFINE_TYPE (EBookBackendWebDAV, e_book_backend_webdav, E_TYPE_BOOK_META_BACKEND)
-static void
-ebb_webdav_set_contact_etag (EContact *contact,
- const gchar *etag)
-{
- EVCardAttribute *attr;
-
- g_return_if_fail (E_IS_CONTACT (contact));
-
- attr = e_vcard_get_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG);
-
- if (attr) {
- e_vcard_attribute_remove_values (attr);
- if (etag) {
- e_vcard_attribute_add_value (attr, etag);
- } else {
- e_vcard_remove_attribute (E_VCARD (contact), attr);
- }
- } else if (etag) {
- e_vcard_append_attribute_with_value (
- E_VCARD (contact),
- e_vcard_attribute_new (NULL, E_WEBDAV_X_ETAG),
- etag);
- }
-}
-
-static gchar *
-ebb_webdav_dup_contact_etag (EContact *contact)
-{
- EVCardAttribute *attr;
- GList *v = NULL;
-
- g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
-
- attr = e_vcard_get_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG);
-
- if (attr)
- v = e_vcard_attribute_get_values (attr);
-
- return ((v && v->data) ? g_strstrip (g_strdup (v->data)) : NULL);
-}
-
static gboolean
ebb_webdav_connect_sync (EBookMetaBackend *meta_backend,
const ENamedParameters *credentials,
@@ -330,7 +289,7 @@ ebb_webdav_update_nfo_with_contact (EBookMetaBackendInfo *nfo,
if (!etag || !*etag)
etag = nfo->revision;
- ebb_webdav_set_contact_etag (contact, etag);
+ e_vcard_util_set_x_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG, etag);
g_warn_if_fail (nfo->object == NULL);
nfo->object = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
@@ -883,7 +842,7 @@ ebb_webdav_load_contact_sync (EBookMetaBackend *meta_backend,
contact = e_contact_new_from_vcard (bytes);
if (contact) {
- ebb_webdav_set_contact_etag (contact, etag);
+ e_vcard_util_set_x_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG, etag);
*out_contact = contact;
}
}
@@ -929,9 +888,9 @@ ebb_webdav_save_contact_sync (EBookMetaBackend *meta_backend,
bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
uid = e_contact_get (contact, E_CONTACT_UID);
- etag = ebb_webdav_dup_contact_etag (contact);
+ etag = e_vcard_util_dup_x_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG);
- ebb_webdav_set_contact_etag (contact, NULL);
+ e_vcard_util_set_x_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG, NULL);
vcard_string = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
@@ -1007,7 +966,7 @@ ebb_webdav_remove_contact_sync (EBookMetaBackend *meta_backend,
}
if (conflict_resolution == E_CONFLICT_RESOLUTION_FAIL)
- etag = ebb_webdav_dup_contact_etag (contact);
+ etag = e_vcard_util_dup_x_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG);
success = e_webdav_session_delete_sync (bbdav->priv->webdav, extra,
E_WEBDAV_DEPTH_THIS, etag, cancellable, &local_error);
@@ -1071,7 +1030,7 @@ ebb_webdav_dup_contact_revision_cb (EBookCache *book_cache,
{
g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
- return ebb_webdav_dup_contact_etag (contact);
+ return e_vcard_util_dup_x_attribute (E_VCARD (contact), E_WEBDAV_X_ETAG);
}
static void
diff --git a/src/addressbook/libebook-contacts/e-vcard.c b/src/addressbook/libebook-contacts/e-vcard.c
index c3b9493..f9d897c 100644
--- a/src/addressbook/libebook-contacts/e-vcard.c
+++ b/src/addressbook/libebook-contacts/e-vcard.c
@@ -2875,3 +2875,72 @@ e_vcard_attribute_param_get_values (EVCardAttributeParam *param)
return param->values;
}
+
+/**
+ * e_vcard_util_set_x_attribute:
+ * @vcard: an #EVCard
+ * @x_name: the attribute name, which starts with "X-"
+ * @value: (nullable): the value to set, or %NULL to unset
+ *
+ * Sets an "X-" attribute @x_name to value @value in @vcard, or
+ * removes it from @vcard, when @value is %NULL.
+ *
+ * Since: 3.26
+ **/
+void
+e_vcard_util_set_x_attribute (EVCard *vcard,
+ const gchar *x_name,
+ const gchar *value)
+{
+ EVCardAttribute *attr;
+
+ g_return_if_fail (E_IS_VCARD (vcard));
+ g_return_if_fail (x_name != NULL);
+ g_return_if_fail (g_str_has_prefix (x_name, "X-"));
+
+ attr = e_vcard_get_attribute (vcard, x_name);
+
+ if (attr) {
+ e_vcard_attribute_remove_values (attr);
+ if (value) {
+ e_vcard_attribute_add_value (attr, value);
+ } else {
+ e_vcard_remove_attribute (vcard, attr);
+ }
+ } else if (value) {
+ e_vcard_append_attribute_with_value (
+ vcard,
+ e_vcard_attribute_new (NULL, x_name),
+ value);
+ }
+}
+
+/**
+ * e_vcard_util_dup_x_attribute:
+ * @vcard: an #EVCard
+ * @x_name: the attribute name, which starts with "X-"
+ *
+ * Returns: (nullable) (transfer-full): Value of attribute @x_name, or %NULL,
+ * when there is no such attribute. Free the returned pointer with g_free(),
+ * when no longer needed.
+ *
+ * Since: 3.26
+ **/
+gchar *
+e_vcard_util_dup_x_attribute (EVCard *vcard,
+ const gchar *x_name)
+{
+ EVCardAttribute *attr;
+ GList *v = NULL;
+
+ g_return_val_if_fail (E_IS_VCARD (vcard), NULL);
+ g_return_val_if_fail (x_name != NULL, NULL);
+ g_return_val_if_fail (g_str_has_prefix (x_name, "X-"), NULL);
+
+ attr = e_vcard_get_attribute (vcard, x_name);
+
+ if (attr)
+ v = e_vcard_attribute_get_values (attr);
+
+ return ((v && v->data) ? g_strstrip (g_strdup (v->data)) : NULL);
+}
diff --git a/src/addressbook/libebook-contacts/e-vcard.h b/src/addressbook/libebook-contacts/e-vcard.h
index e3f9ed0..9efcb3d 100644
--- a/src/addressbook/libebook-contacts/e-vcard.h
+++ b/src/addressbook/libebook-contacts/e-vcard.h
@@ -316,6 +316,12 @@ gboolean e_vcard_attribute_has_type (EVCardAttribute *attr, cons
gchar * e_vcard_escape_string (const gchar *s);
gchar * e_vcard_unescape_string (const gchar *s);
+void e_vcard_util_set_x_attribute (EVCard *vcard,
+ const gchar *x_name,
+ const gchar *value);
+gchar * e_vcard_util_dup_x_attribute (EVCard *vcard,
+ const gchar *x_name);
+
G_END_DECLS
#endif /* _EVCARD_H */
diff --git a/src/calendar/backends/gtasks/e-cal-backend-gtasks.c
b/src/calendar/backends/gtasks/e-cal-backend-gtasks.c
index ddbc6cf..fd2e964 100644
--- a/src/calendar/backends/gtasks/e-cal-backend-gtasks.c
+++ b/src/calendar/backends/gtasks/e-cal-backend-gtasks.c
@@ -865,12 +865,6 @@ ecb_gtasks_remove_component_sync (ECalMetaBackend *meta_backend,
cbgtasks = E_CAL_BACKEND_GTASKS (meta_backend);
- if (!cached_comp) {
- g_propagate_error (error, EDC_ERROR (ObjectNotFound));
-
- return FALSE;
- }
-
task = ecb_gtasks_comp_to_gdata (cached_comp, NULL, FALSE);
if (!task) {
g_object_unref (cached_comp);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]