#include <string.h>
#include <errno.h>
+#include <libedataserver/e-proxy.h>
#include <libebook/e-contact.h>
#include <libedata-book/e-data-book.h>
#include <libedata-book/e-data-book-view.h>
#include <libedata-book/e-book-backend-sexp.h>
+#include <libedata-book/e-book-backend-cache.h>
#include <gdata/gdata-service.h>
+#include <gdata/services/contacts/gdata-contacts-service.h>
+#include <gdata/services/contacts/gdata-contacts-query.h>
+#include <gdata/services/contacts/gdata-contacts-contact.h>
#include "e-book-backend-google.h"
-#include "google-book.h"
#include "util.h"
+#define URI_GET_CONTACTS "://www.google.com/m8/feeds/contacts/default/full"
+
G_DEFINE_TYPE (EBookBackendGoogle, e_book_backend_google, E_TYPE_BOOK_BACKEND_SYNC)
+typedef enum {
+ NO_CACHE,
+ ON_DISK_CACHE,
+ IN_MEMORY_CACHE
+} CacheType;
+
struct _EBookBackendGooglePrivate {
GNOME_Evolution_Addressbook_BookMode mode;
- GoogleBook *book;
GList *bookviews;
-};
-#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), E_TYPE_BOOK_BACKEND_GOOGLE, EBookBackendGooglePrivate))
+ gchar *username;
+ CacheType cache_type;
+ union {
+ EBookBackendCache *on_disk;
+ struct {
+ GHashTable *contacts;
+ GHashTable *gdata_entries;
+ GTimeVal last_updated;
+ } in_memory;
+ } cache;
+
+ gboolean offline;
+ GDataService *service;
+ EProxy *proxy;
+ guint refresh_interval;
+ gboolean use_ssl;
+
+ /* Whether the backend is being used by a view at the moment; if it isn't, we don't need to do updates or send out notifications */
+ gboolean live_mode;
+
+ /* In live mode we will send out signals in an idle_handler */
+ guint idle_id;
+
+ guint refresh_id;
+};
gboolean __e_book_backend_google_debug__;
-static EBookBackendSyncStatus e_book_backend_status_from_google_book_error (GError *error);
+static EBookBackendSyncStatus e_book_backend_status_from_gdata_error (GError *error);
+
+static void
+cache_init (EBookBackend *backend, gboolean on_disk)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+ if (on_disk) {
+ priv->cache_type = ON_DISK_CACHE;
+ priv->cache.on_disk = e_book_backend_cache_new (priv->username);
+ } else {
+ priv->cache_type = IN_MEMORY_CACHE;
+ priv->cache.in_memory.contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ priv->cache.in_memory.gdata_entries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ memset (&priv->cache.in_memory.last_updated, 0, sizeof (GTimeVal));
+ }
+}
+
+static EContact *
+cache_add_contact (EBookBackend *backend, GDataEntry *entry)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ EContact *contact;
+ const gchar *uid;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ contact = _e_contact_new_from_gdata_entry (entry);
+ _e_contact_add_gdata_entry_xml (contact, entry);
+ e_book_backend_cache_add_contact (priv->cache.on_disk, contact);
+ _e_contact_remove_gdata_entry_xml (contact);
+ return contact;
+ case IN_MEMORY_CACHE:
+ contact = _e_contact_new_from_gdata_entry (entry);
+ uid = e_contact_get_const (contact, E_CONTACT_UID);
+ g_hash_table_insert (priv->cache.in_memory.contacts, g_strdup (uid), g_object_ref (contact));
+ g_hash_table_insert (priv->cache.in_memory.gdata_entries, g_strdup (uid), g_object_ref (entry));
+ return contact;
+ case NO_CACHE:
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static gboolean
+cache_remove_contact (EBookBackend *backend, const gchar *uid)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ gboolean success = TRUE;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ return e_book_backend_cache_remove_contact (priv->cache.on_disk, uid);
+ case IN_MEMORY_CACHE:
+ success = g_hash_table_remove (priv->cache.in_memory.contacts, uid);
+ return success && g_hash_table_remove (priv->cache.in_memory.gdata_entries, uid);
+ case NO_CACHE:
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+cache_has_contact (EBookBackend *backend, const gchar *uid)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ return e_book_backend_cache_check_contact (priv->cache.on_disk, uid);
+ case IN_MEMORY_CACHE:
+ return g_hash_table_lookup (priv->cache.in_memory.contacts, uid) ? TRUE : FALSE;
+ case NO_CACHE:
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static EContact *
+cache_get_contact (EBookBackend *backend, const gchar *uid, GDataEntry **entry)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ EContact *contact;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ contact = e_book_backend_cache_get_contact (priv->cache.on_disk, uid);
+ if (contact) {
+ if (entry) {
+ const gchar *entry_xml, *edit_link;
+
+ entry_xml = _e_contact_get_gdata_entry_xml (contact, &edit_link);
+ *entry = GDATA_ENTRY (gdata_parsable_new_from_xml (GDATA_TYPE_CONTACTS_CONTACT, entry_xml, -1, NULL));
+
+ if (*entry) {
+ GDataLink *link = gdata_link_new (edit_link, GDATA_LINK_EDIT);
+ gdata_entry_add_link (*entry, link);
+ g_object_unref (link);
+ }
+ }
+
+ _e_contact_remove_gdata_entry_xml (contact);
+ }
+ return contact;
+ case IN_MEMORY_CACHE:
+ contact = g_hash_table_lookup (priv->cache.in_memory.contacts, uid);
+ if (entry) {
+ *entry = g_hash_table_lookup (priv->cache.in_memory.gdata_entries, uid);
+ if (*entry)
+ g_object_ref (*entry);
+ }
+
+ if (contact)
+ g_object_ref (contact);
+
+ return contact;
+ case NO_CACHE:
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static GList *
+_g_hash_table_to_list (GHashTable *ht)
+{
+ GList *l = NULL;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, ht);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ l = g_list_prepend (l, g_object_ref (G_OBJECT (value)));
+
+ l = g_list_reverse (l);
+
+ return l;
+}
+
+static GList *
+cache_get_contacts (EBookBackend *backend)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ GList *contacts, *iter;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ contacts = e_book_backend_cache_get_contacts (priv->cache.on_disk, "(contains \"x-evolution-any-field\" \"\")");
+ for (iter = contacts; iter; iter = iter->next)
+ _e_contact_remove_gdata_entry_xml (iter->data);
+
+ return contacts;
+ case IN_MEMORY_CACHE:
+ return _g_hash_table_to_list (priv->cache.in_memory.contacts);
+ case NO_CACHE:
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static void
+cache_freeze (EBookBackend *backend)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+ if (priv->cache_type == ON_DISK_CACHE)
+ e_file_cache_freeze_changes (E_FILE_CACHE (priv->cache.on_disk));
+}
+
+static void
+cache_thaw (EBookBackend *backend)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+ if (priv->cache_type == ON_DISK_CACHE)
+ e_file_cache_thaw_changes (E_FILE_CACHE (priv->cache.on_disk));
+}
+
+static gchar *
+cache_get_last_update (EBookBackend *backend)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ return e_book_backend_cache_get_time (priv->cache.on_disk);
+ case IN_MEMORY_CACHE:
+ if (priv->cache.in_memory.contacts)
+ return g_time_val_to_iso8601 (&priv->cache.in_memory.last_updated);
+ break;
+ case NO_CACHE:
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static gboolean
+cache_get_last_update_tv (EBookBackend *backend, GTimeVal *tv)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ gchar *last_update;
+ gint rv;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ last_update = e_book_backend_cache_get_time (priv->cache.on_disk);
+ rv = last_update ? g_time_val_from_iso8601 (last_update, tv) : FALSE;
+ g_free (last_update);
+ return rv;
+ case IN_MEMORY_CACHE:
+ memcpy (tv, &priv->cache.in_memory.last_updated, sizeof (GTimeVal));
+ return priv->cache.in_memory.contacts != NULL;
+ case NO_CACHE:
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+cache_set_last_update (EBookBackend *backend, GTimeVal *tv)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ gchar *_time;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ _time = g_time_val_to_iso8601 (tv);
+ /* Work around a bug in EBookBackendCache */
+ e_file_cache_remove_object (E_FILE_CACHE (priv->cache.on_disk), "last_update_time");
+ e_book_backend_cache_set_time (priv->cache.on_disk, _time);
+ g_free (_time);
+ return;
+ case IN_MEMORY_CACHE:
+ memcpy (&priv->cache.in_memory.last_updated, tv, sizeof (GTimeVal));
+ case NO_CACHE:
+ default:
+ break;
+ }
+}
+
+static gboolean
+cache_needs_update (EBookBackend *backend, guint *remaining_secs)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ GTimeVal last, current;
+ guint diff;
+ gboolean rv;
+
+ if (remaining_secs)
+ *remaining_secs = G_MAXUINT;
+
+ /* We never want to update in offline mode */
+ if (priv->offline)
+ return FALSE;
+
+ rv = cache_get_last_update_tv (backend, &last);
+
+ if (!rv)
+ return TRUE;
+
+ g_get_current_time (¤t);
+ if (last.tv_sec > current.tv_sec) {
+ g_warning ("last update is in the feature?");
+
+ /* Do an update so we can fix this */
+ return TRUE;
+ }
+ diff = current.tv_sec - last.tv_sec;
+
+ if (diff >= priv->refresh_interval)
+ return TRUE;
+
+ if (remaining_secs)
+ *remaining_secs = priv->refresh_interval - diff;
+
+ __debug__ ("No update needed. Next update needed in %d secs", priv->refresh_interval - diff);
+
+ return FALSE;
+}
+
+static void
+on_contact_added (EBookBackend *backend, EContact *contact)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ GList *iter;
+
+ if (!priv->live_mode)
+ return;
+
+ for (iter = priv->bookviews; iter; iter = iter->next)
+ e_data_book_view_notify_update (E_DATA_BOOK_VIEW (iter->data), g_object_ref (contact));
+}
+
+static void
+on_contact_removed (EBookBackend *backend, const gchar *uid)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ GList *iter;
+
+ if (!priv->live_mode)
+ return;
+
+ for (iter = priv->bookviews; iter; iter = iter->next)
+ e_data_book_view_notify_remove (E_DATA_BOOK_VIEW (iter->data), g_strdup (uid));
+}
+
+static void
+on_contact_changed (EBookBackend *backend, EContact *contact)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ GList *iter;
+
+ if (!priv->live_mode)
+ return;
+
+ for (iter = priv->bookviews; iter; iter = iter->next)
+ e_data_book_view_notify_update (E_DATA_BOOK_VIEW (iter->data), g_object_ref (contact));
+}
+
+static void
+on_sequence_complete (EBookBackend *backend, GError *error)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_Success;
+ GList *iter;
+
+ if (!priv->live_mode)
+ return;
+
+ if (error) {
+ status = e_book_backend_status_from_gdata_error (error);
+ __debug__ ("Book-view query failed: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ for (iter = priv->bookviews; iter; iter = iter->next)
+ e_data_book_view_notify_complete (E_DATA_BOOK_VIEW (iter->data), GNOME_Evolution_Addressbook_Success);
+}
+
+static void
+process_subsequent_entry (GDataEntry *entry, EBookBackend *backend)
+{
+ gboolean is_deleted, is_cached;
+ const gchar *uid;
+
+ __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);
+ on_contact_removed (backend, uid);
+ }
+ } else {
+ EContact *contact = cache_add_contact (backend, entry);
+
+ if (is_cached)
+ on_contact_changed (backend, contact);
+ else
+ on_contact_added (backend, contact);
+
+ g_object_unref (contact);
+ }
+}
+
+static void
+process_initial_entry (GDataEntry *entry, EBookBackend *backend)
+{
+ EContact *contact;
+
+ __debug__ (G_STRFUNC);
+
+ contact = cache_add_contact (backend, entry);
+ on_contact_added (backend, contact);
+ g_object_unref (contact);
+}
+
+static gboolean
+get_new_contacts_in_chunks (EBookBackend *backend, gint chunk_size, GError **error)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ GDataFeed *feed;
+ GDataQuery *query;
+ gchar *last_updated;
+ GError *our_error = NULL;
+ gboolean rv = TRUE;
+ GTimeVal current_time;
+ int results;
+
+ __debug__ (G_STRFUNC);
+ g_return_val_if_fail (priv->service, FALSE);
+
+ last_updated = cache_get_last_update (backend);
+ cache_freeze (backend);
+
+ /* Build our query */
+ query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 1, chunk_size));
+ if (last_updated) {
+ GTimeVal updated;
+
+ g_assert (g_time_val_from_iso8601 (last_updated, &updated) == TRUE);
+ gdata_query_set_updated_min (query, &updated);
+ gdata_contacts_query_set_show_deleted (GDATA_CONTACTS_QUERY (query), TRUE);
+ }
+
+ /* Get the paginated results */
+ do {
+ GList *entries;
+
+ /* Run the query */
+ feed = gdata_contacts_service_query_contacts (GDATA_CONTACTS_SERVICE (priv->service), query, NULL, NULL, NULL, &our_error);
+
+ if (our_error) {
+ on_sequence_complete (backend, our_error);
+ g_propagate_error (error, our_error);
+
+ rv = FALSE;
+ goto out;
+ }
+
+ entries = gdata_feed_get_entries (feed);
+ results = entries ? g_list_length (entries) : 0;
+ __debug__ ("Feed has %d entries", results);
+
+ /* Process the entries from this page */
+ if (last_updated)
+ g_list_foreach (entries, (GFunc) process_subsequent_entry, backend);
+ else
+ g_list_foreach (entries, (GFunc) process_initial_entry, backend);
+ g_object_unref (feed);
+
+ /* Move to the next page */
+ gdata_query_next_page (query);
+ } while (results == chunk_size);
+
+ /* Finish updating the cache */
+ g_get_current_time (¤t_time);
+ cache_set_last_update (backend, ¤t_time);
+ on_sequence_complete (backend, NULL);
+
+out:
+ g_free (last_updated);
+ cache_thaw (backend);
+
+ return rv;
+}
+
+static gboolean cache_refresh_if_needed (EBookBackend *backend, GError **error);
+
+static gboolean
+on_refresh_timeout (EBookBackend *backend)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+ __debug__ (G_STRFUNC);
+
+ priv->refresh_id = 0;
+ if (priv->live_mode)
+ cache_refresh_if_needed (backend, NULL);
+
+ return FALSE;
+}
+
+static gboolean
+cache_refresh_if_needed (EBookBackend *backend, GError **error)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ guint remaining_secs;
+ gint rv = TRUE;
+ gboolean install_timeout;
+
+ __debug__ (G_STRFUNC);
+
+ if (priv->offline || !priv->service) {
+ __debug__ ("We are not connected to Google%s.", priv->offline ? " (offline mode)" : "");
+ return TRUE;
+ }
+
+ install_timeout = (priv->live_mode && priv->refresh_interval > 0 && 0 == priv->refresh_id);
+
+ if (cache_needs_update (backend, &remaining_secs)) {
+ rv = get_new_contacts_in_chunks (backend, 32, error);
+ if (install_timeout)
+ priv->refresh_id = g_timeout_add_seconds (priv->refresh_interval, (GSourceFunc) on_refresh_timeout, backend);
+ } else {
+ if (install_timeout) {
+ __debug__ ("Installing timeout with %d seconds", remaining_secs);
+ priv->refresh_id = g_timeout_add_seconds (remaining_secs, (GSourceFunc) on_refresh_timeout, backend);
+ }
+ }
+ return rv;
+}
+
+static void
+cache_destroy (EBookBackend *backend)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
+ switch (priv->cache_type) {
+ case ON_DISK_CACHE:
+ g_object_unref (priv->cache.on_disk);
+ break;
+ case IN_MEMORY_CACHE:
+ g_hash_table_destroy (priv->cache.in_memory.contacts);
+ g_hash_table_destroy (priv->cache.in_memory.gdata_entries);
+ break;
+ case NO_CACHE:
+ default:
+ break;
+ }
+
+ priv->cache_type = NO_CACHE;
+}
static EBookBackendSyncStatus
e_book_backend_google_create_contact (EBookBackendSync *backend, EDataBook *book, guint32 opid, const gchar *vcard_str, EContact **out_contact)
{
- EBookBackendGooglePrivate *priv;
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
EContact *contact;
GError *error = NULL;
+ GDataEntry *entry, *new_entry;
+ gchar *xml;
__debug__ (G_STRFUNC);
- priv = GET_PRIVATE (backend);
__debug__ ("Creating: %s", vcard_str);
*out_contact = NULL;
if (priv->mode != GNOME_Evolution_Addressbook_MODE_REMOTE)
return GNOME_Evolution_Addressbook_OfflineUnavailable;
+ g_return_val_if_fail (priv->service, status);
+
+ /* Build the GDataEntry from the vCard */
contact = e_contact_new_from_vcard (vcard_str);
- google_book_add_contact (priv->book, contact, out_contact, &error);
+ entry = _gdata_entry_new_from_e_contact (contact);
g_object_unref (contact);
- if (error) {
- status = e_book_backend_status_from_google_book_error (error);
+ /* Debug XML output */
+ xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
+ __debug__ ("new entry with xml: %s", xml);
+ g_free (xml);
+
+ /* Insert the entry on the server */
+ new_entry = GDATA_ENTRY (gdata_contacts_service_insert_contact (GDATA_CONTACTS_SERVICE (priv->service), GDATA_CONTACTS_CONTACT (entry),
+ NULL, &error));
+ g_object_unref (entry);
+
+ if (!new_entry) {
+ status = e_book_backend_status_from_gdata_error (error);
__debug__ ("Creating contact failed: %s", error->message);
- g_clear_error (&error);
- *out_contact = NULL;
+ g_error_free (error);
+
return status;
}
+ /* Add the new contact to the cache */
+ *out_contact = cache_add_contact (E_BOOK_BACKEND (backend), new_entry);
+ g_object_unref (new_entry);
+
return GNOME_Evolution_Addressbook_Success;
}
static EBookBackendSyncStatus
e_book_backend_google_remove_contacts (EBookBackendSync *backend, EDataBook *book, guint32 opid, GList *id_list, GList **ids)
{
- EBookBackendGooglePrivate *priv;
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
GList *id_iter;
__debug__ (G_STRFUNC);
- priv = GET_PRIVATE (backend);
*ids = NULL;
if (priv->mode != GNOME_Evolution_Addressbook_MODE_REMOTE)
return GNOME_Evolution_Addressbook_OfflineUnavailable;
+ g_return_val_if_fail (priv->service, status);
+
for (id_iter = id_list; id_iter; id_iter = id_iter->next) {
GError *error = NULL;
const gchar *uid;
+ GDataEntry *entry = NULL;
+ EContact *cached_contact;
+ /* Get the contact and associated GDataEntry from the cache */
uid = id_iter->data;
- google_book_remove_contact (priv->book, uid, &error);
+ cached_contact = cache_get_contact (E_BOOK_BACKEND (backend), uid, &entry);
- if (error) {
+ if (!cached_contact) {
+ /* Only the last error will be reported */
+ status = GNOME_Evolution_Addressbook_ContactNotFound;
+ __debug__ ("Deleting contact %s failed: Contact not found in cache.", uid);
+
+ continue;
+ }
+
+ g_object_unref (cached_contact);
+
+ /* Remove the contact from the cache */
+ cache_remove_contact (E_BOOK_BACKEND (backend), uid);
+
+ /* Delete the contact from the server */
+ if (!gdata_service_delete_entry (GDATA_SERVICE (priv->service), entry, NULL, &error)) {
/* Only last error will be reported */
- status = e_book_backend_status_from_google_book_error (error);
+ status = e_book_backend_status_from_gdata_error (error);
__debug__ ("Deleting contact %s failed: %s", uid, error->message);
- g_clear_error (&error);
+ g_error_free (error);
} else {
+ /* Success! */
*ids = g_list_append (*ids, g_strdup (uid));
}
+
+ g_object_unref (entry);
}
+ /* On error, return the status of the last error */
if (!*ids)
return status;
static EBookBackendSyncStatus
e_book_backend_google_modify_contact (EBookBackendSync *backend, EDataBook *book, guint32 opid, const gchar *vcard_str, EContact **out_contact)
{
- EBookBackendGooglePrivate *priv;
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
- EContact *contact;
+ EContact *contact, *cached_contact;
GError *error = NULL;
+ GDataEntry *entry = NULL, *new_entry;
+ gchar *xml;
+ const gchar *uid;
__debug__ (G_STRFUNC);
- priv = GET_PRIVATE (backend);
__debug__ ("Updating: %s", vcard_str);
*out_contact = NULL;
if (priv->mode != GNOME_Evolution_Addressbook_MODE_REMOTE)
return GNOME_Evolution_Addressbook_OfflineUnavailable;
+ g_return_val_if_fail (priv->service, status);
+
+ /* Get the new contact and its UID */
contact = e_contact_new_from_vcard (vcard_str);
- google_book_update_contact (priv->book, contact, out_contact, &error);
+ 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 (E_BOOK_BACKEND (backend), uid, &entry);
+
+ if (!cached_contact) {
+ __debug__ ("Modifying contact failed: Contact with uid %s not found in cache.", uid);
+ g_object_unref (contact);
+
+ return GNOME_Evolution_Addressbook_ContactNotFound;
+ }
+
+ g_object_unref (cached_contact);
+
+ /* Update the old GDataEntry from the new contact */
+ _gdata_entry_update_from_e_contact (entry, contact);
g_object_unref (contact);
- if (error) {
- status = e_book_backend_status_from_google_book_error (error);
+ /* Output debug XML */
+ xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
+ __debug__ ("Before:\n%s", xml);
+ g_free (xml);
+
+ /* Update the contact on the server */
+ new_entry = GDATA_ENTRY (gdata_contacts_service_update_contact (GDATA_CONTACTS_SERVICE (priv->service), GDATA_CONTACTS_CONTACT (entry),
+ NULL, &error));
+ g_object_unref (entry);
+
+ if (!new_entry) {
+ status = e_book_backend_status_from_gdata_error (error);
__debug__ ("Modifying contact failed: %s", error->message);
- g_clear_error (&error);
- *out_contact = NULL;
+ g_error_free (error);
+
return status;
}
+ /* Output debug XML */
+ xml = NULL;
+ if (new_entry)
+ xml = gdata_parsable_get_xml (GDATA_PARSABLE (new_entry));
+ __debug__ ("After:\n%s", xml);
+ g_free (xml);
+
+ /* Add the new entry to the cache */
+ *out_contact = cache_add_contact (E_BOOK_BACKEND (backend), new_entry);
+ g_object_unref (new_entry);
+
return GNOME_Evolution_Addressbook_Success;
}
static EBookBackendSyncStatus
e_book_backend_google_get_contact (EBookBackendSync *backend, EDataBook *book, guint32 opid, const gchar *uid, gchar **vcard_str)
{
- EBookBackendGooglePrivate *priv;
EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
EContact *contact;
GError *error = NULL;
__debug__ (G_STRFUNC);
- priv = GET_PRIVATE (backend);
- contact = google_book_get_contact (priv->book, uid, &error);
+ /* Refresh the cache */
+ cache_refresh_if_needed (E_BOOK_BACKEND (backend), &error);
if (error) {
- status = e_book_backend_status_from_google_book_error (error);
+ status = e_book_backend_status_from_gdata_error (error);
__debug__ ("Getting contact with uid %s failed: %s", uid, error->message);
- g_clear_error (&error);
+ g_error_free (error);
+
return status;
}
+ /* Get the contact */
+ contact = cache_get_contact (E_BOOK_BACKEND (backend), uid, NULL);
+
+ if (!contact) {
+ __debug__ ("Getting contact with uid %s failed: Contact not found in cache.", uid);
+ return GNOME_Evolution_Addressbook_ContactNotFound;
+ }
+
+ /* Success! Build and return a vCard of the contacts */
*vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
g_object_unref (contact);
static EBookBackendSyncStatus
e_book_backend_google_get_contact_list (EBookBackendSync *backend, EDataBook *book, guint32 opid, const gchar *query, GList **contacts)
{
- EBookBackendGooglePrivate *priv;
EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
EBookBackendSExp *sexp;
GError *error = NULL;
GList *all_contacts;
__debug__ (G_STRFUNC);
- priv = GET_PRIVATE (backend);
*contacts = NULL;
- all_contacts = google_book_get_all_contacts (priv->book, &error);
+ /* Refresh the cache */
+ cache_refresh_if_needed (E_BOOK_BACKEND (backend), &error);
if (error) {
- status = e_book_backend_status_from_google_book_error (error);
+ status = e_book_backend_status_from_gdata_error (error);
__debug__ ("Getting all contacts failed: %s", error->message);
g_clear_error (&error);
+
return status;
}
+ /* Get all contacts */
sexp = e_book_backend_sexp_new (query);
- while (all_contacts) {
- EContact *contact;
+ all_contacts = cache_get_contacts (E_BOOK_BACKEND (backend));
- contact = all_contacts->data;
+ for (; all_contacts; all_contacts = g_list_delete_link (all_contacts, all_contacts)) {
+ EContact *contact = all_contacts->data;
+
+ /* If the search expression matches the contact, include it in the search results */
if (e_book_backend_sexp_match_contact (sexp, contact)) {
gchar *vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
*contacts = g_list_append (*contacts, vcard_str);
}
- g_object_unref (contact);
- all_contacts = g_list_delete_link (all_contacts, all_contacts);
+ g_object_unref (contact);
}
+
g_object_unref (sexp);
return GNOME_Evolution_Addressbook_Success;
}
-static void
-on_google_book_contact_added (GoogleBook *book, EContact *contact, gpointer user_data)
+static gboolean
+on_refresh_idle (EBookBackend *backend)
{
- EBookBackendGooglePrivate *priv = GET_PRIVATE (user_data);
- GList *iter;
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
- for (iter = priv->bookviews; iter; iter = iter->next) {
- g_object_ref (contact);
- e_data_book_view_notify_update (E_DATA_BOOK_VIEW (iter->data), contact);
- }
-}
+ priv->idle_id = 0;
+ cache_refresh_if_needed (backend, NULL);
-static void
-on_google_book_contact_removed (GoogleBook *book, const gchar *uid, gpointer user_data)
-{
- EBookBackendGooglePrivate *priv = GET_PRIVATE (user_data);
- GList *iter;
-
- for (iter = priv->bookviews; iter; iter = iter->next)
- e_data_book_view_notify_remove (E_DATA_BOOK_VIEW (iter->data), g_strdup (uid));
+ return FALSE;
}
static void
-on_google_book_contact_changed (GoogleBook *book, EContact *contact, gpointer user_data)
+set_live_mode (EBookBackend *backend, gboolean live_mode)
{
- EBookBackendGooglePrivate *priv = GET_PRIVATE (user_data);
- GList *iter;
-
- for (iter = priv->bookviews; iter; iter = iter->next) {
- g_object_ref (contact);
- e_data_book_view_notify_update (E_DATA_BOOK_VIEW (iter->data), contact);
- }
-}
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
-static void
-on_google_book_sequence_complete (GoogleBook *book, GError *error, gpointer user_data)
-{
- EBookBackendGooglePrivate *priv = GET_PRIVATE (user_data);
- EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_Success;
- GList *iter;
+ __debug__ (G_STRFUNC);
+ priv->live_mode = live_mode;
- if (error) {
- status = e_book_backend_status_from_google_book_error (error);
- __debug__ ("Book-view query failed: %s", error->message);
- g_clear_error (&error);
+ if (!live_mode && priv->refresh_id > 0) {
+ g_source_remove (priv->refresh_id);
+ priv->refresh_id = 0;
}
- for (iter = priv->bookviews; iter; iter = iter->next)
- e_data_book_view_notify_complete (E_DATA_BOOK_VIEW (iter->data), GNOME_Evolution_Addressbook_Success);
+ if (priv->live_mode)
+ cache_refresh_if_needed (backend, NULL);
}
static void
e_book_backend_google_start_book_view (EBookBackend *backend, EDataBookView *bookview)
{
- EBookBackendGooglePrivate *priv;
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
GList *cached_contacts;
g_return_if_fail (E_IS_BOOK_BACKEND_GOOGLE (backend));
g_return_if_fail (E_IS_DATA_BOOK_VIEW (bookview));
__debug__ (G_STRFUNC);
- priv = GET_PRIVATE (backend);
priv->bookviews = g_list_append (priv->bookviews, bookview);
e_data_book_view_ref (bookview);
e_data_book_view_notify_status_message (bookview, "Loading...");
- google_book_set_live_mode (priv->book, TRUE);
- cached_contacts = google_book_get_all_contacts_in_live_mode (priv->book);
- while (cached_contacts) {
- EContact *contact = cached_contacts->data;
+ /* Ensure that we're ready to support a view */
+ set_live_mode (backend, TRUE);
+
+ /* Update the cache if necessary */
+ if (cache_needs_update (backend, NULL)) {
+ if (!priv->service) {
+ /* We need authorization first */
+ e_book_backend_notify_auth_required (backend);
+ } else {
+ /* Update in an idle function, so that this call doesn't block */
+ priv->idle_id = g_idle_add ((GSourceFunc) on_refresh_idle, backend);
+ }
+ }
+ /* Get the contacts */
+ cached_contacts = cache_get_contacts (backend);
+ __debug__ ("%d contacts found in cache", g_list_length (cached_contacts));
+
+ /* Notify the view that all the contacts have changed (i.e. been added) */
+ for (; cached_contacts; cached_contacts = g_list_delete_link (cached_contacts, cached_contacts)) {
+ EContact *contact = cached_contacts->data;
e_data_book_view_notify_update (bookview, contact);
g_object_unref (contact);
- cached_contacts = g_list_delete_link (cached_contacts, cached_contacts);
}
+
e_data_book_view_notify_complete (bookview, GNOME_Evolution_Addressbook_Success);
}
static void
e_book_backend_google_stop_book_view (EBookBackend *backend, EDataBookView *bookview)
{
- EBookBackendGooglePrivate *priv;
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
__debug__ (G_STRFUNC);
- priv = GET_PRIVATE (backend);
+ /* Remove the view from the list of active views */
priv->bookviews = g_list_remove (priv->bookviews, bookview);
e_data_book_view_unref (bookview);
+ /* If there are no book views left, we can stop doing certain things, like refreshes */
if (!priv->bookviews)
- google_book_set_live_mode (priv->book, FALSE);
+ set_live_mode (backend, FALSE);
+}
+
+static void
+proxy_settings_changed (EProxy *proxy, EBookBackend *backend)
+{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+ SoupURI *proxy_uri = NULL;
+ gchar *uri;
+
+ if (!priv || !priv->service)
+ return;
+
+ /* Build the URI which libgdata would use to query contacts */
+ uri = g_strconcat (priv->use_ssl ? "https" : "http", URI_GET_CONTACTS, NULL);
+
+ /* use proxy if necessary */
+ if (e_proxy_require_proxy_for_uri (proxy, uri))
+ proxy_uri = e_proxy_peek_uri_for (proxy, uri);
+ gdata_service_set_proxy_uri (GDATA_SERVICE (priv->service), proxy_uri);
+
+ g_free (uri);
}
static EBookBackendSyncStatus
e_book_backend_google_authenticate_user (EBookBackendSync *backend, EDataBook *book, guint32 opid,
const gchar *username, const gchar *password, const gchar *auth_method)
{
- EBookBackendGooglePrivate *priv;
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_Success;
GError *error = NULL;
- gchar *book_username;
gboolean match;
__debug__ (G_STRFUNC);
- priv = GET_PRIVATE (backend);
if (priv->mode != GNOME_Evolution_Addressbook_MODE_REMOTE)
return GNOME_Evolution_Addressbook_Success;
- if (!username || username[0] == 0)
- return GNOME_Evolution_Addressbook_AuthenticationFailed;
+ if (priv->service) {
+ g_warning ("Connection to Google already established.");
+ e_book_backend_notify_writable (E_BOOK_BACKEND (backend), TRUE);
+ return GNOME_Evolution_Addressbook_Success;
+ }
- g_object_get (G_OBJECT (priv->book), "username", &book_username, NULL);
+ if (!username || username[0] == 0 || !password || password[0] == 0)
+ return GNOME_Evolution_Addressbook_AuthenticationFailed;
- match = (0 == strcmp (username, book_username));
- g_free (book_username);
- if (FALSE == match) {
+ match = (strcmp (username, priv->username) == 0);
+ if (!match) {
g_warning ("Username given when loading source and on authentication did not match!");
return GNOME_Evolution_Addressbook_AuthenticationRequired;
}
- google_book_connect_to_google (priv->book, password, &error);
+ /* Set up the service and proxy */
+ priv->service = GDATA_SERVICE (gdata_contacts_service_new ("evolution-client-0.1.0"));
+
+ priv->proxy = e_proxy_new ();
+ e_proxy_setup_proxy (priv->proxy);
+
+ proxy_settings_changed (priv->proxy, E_BOOK_BACKEND (backend));
+ g_signal_connect (priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), backend);
+
+ /* Authenticate with the server */
+ if (!gdata_service_authenticate (priv->service, priv->username, password, NULL, &error)) {
+ g_object_unref (priv->service);
+ priv->service = NULL;
+ g_object_unref (priv->proxy);
+ priv->proxy = NULL;
+
+ status = e_book_backend_status_from_gdata_error (error);
+ __debug__ ("Authentication failed: %s", error->message);
+ g_error_free (error);
+
+ return status;
+ }
+
+ /* Update the cache if neccessary */
+ cache_refresh_if_needed (E_BOOK_BACKEND (backend), &error);
+
if (error) {
- status = e_book_backend_status_from_google_book_error (error);
+ status = e_book_backend_status_from_gdata_error (error);
__debug__ ("Authentication failed: %s", error->message);
- g_clear_error (&error);
- } else {
- e_book_backend_notify_writable (E_BOOK_BACKEND (backend), TRUE);
+ g_error_free (error);
+
+ return status;
}
- return status;
+ e_book_backend_notify_writable (E_BOOK_BACKEND (backend), TRUE);
+
+ return GNOME_Evolution_Addressbook_Success;
}
static EBookBackendSyncStatus
e_book_backend_google_get_supported_auth_methods (EBookBackendSync *backend, EDataBook *book, guint32 opid, GList **methods)
{
- gchar *auth_method;
-
__debug__ (G_STRFUNC);
- auth_method = g_strdup_printf ("plain/password");
- *methods = g_list_append (NULL, auth_method);
+ *methods = g_list_prepend (NULL, g_strdup ("plain/password"));
return GNOME_Evolution_Addressbook_Success;
}
__debug__ (G_STRFUNC);
+ /* Add all the fields above to the list */
for (i = 0; i < G_N_ELEMENTS (supported_fields); i++) {
const gchar *field_name = e_contact_field_name (supported_fields[i]);
- fields = g_list_append (fields, g_strdup (field_name));
+ fields = g_list_prepend (fields, g_strdup (field_name));
}
*fields_out = fields;
}
static void
-on_google_book_auth_required (GoogleBook *book, gpointer user_data)
+set_offline_mode (EBookBackend *backend, gboolean offline)
{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
+
__debug__ (G_STRFUNC);
- e_book_backend_notify_auth_required (E_BOOK_BACKEND (user_data));
+
+ priv->offline = offline;
+
+ if (offline) {
+ /* Going offline, so we can free our service and proxy */
+ if (priv->service)
+ g_object_unref (priv->service);
+ priv->service = NULL;
+
+ if (priv->proxy)
+ g_object_unref (priv->proxy);
+ priv->proxy = NULL;
+ } else {
+ /* Going online, so we need to re-authenticate and re-create the service and proxy.
+ * This is done in e_book_backend_google_authenticate_user() when it gets the authentication data. */
+ e_book_backend_notify_auth_required (backend);
+ }
}
static GNOME_Evolution_Addressbook_CallStatus
e_book_backend_google_load_source (EBookBackend *backend, ESource *source, gboolean only_if_exists)
{
- EBookBackendGooglePrivate *priv = GET_PRIVATE (backend);
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
const gchar *refresh_interval_str, *use_ssl_str, *use_cache_str;
guint refresh_interval;
gboolean use_ssl, use_cache;
const gchar *username;
- if (priv->book) {
+ __debug__ (G_STRFUNC);
+
+ if (priv->username) {
g_warning ("Source already loaded!");
return GNOME_Evolution_Addressbook_OtherError;
}
+ /* Parse the username property */
username = e_source_get_property (source, "username");
if (!username || username[0] == '\0') {
return GNOME_Evolution_Addressbook_OtherError;
}
+ /* Parse various other properties */
refresh_interval_str = e_source_get_property (source, "refresh-interval");
use_ssl_str = e_source_get_property (source, "ssl");
use_cache_str = e_source_get_property (source, "offline_sync");
if (use_cache_str && (g_ascii_strcasecmp (use_cache_str, "false") == 0 || strcmp (use_cache_str, "0") == 0))
use_cache = FALSE;
- priv->book = google_book_new (username, use_cache);
-
- g_object_set (G_OBJECT (priv->book),
- "refresh-interval", refresh_interval,
- "use-ssl", use_ssl,
- NULL);
- g_object_connect (G_OBJECT (priv->book),
- "signal::contact-added", G_CALLBACK (on_google_book_contact_added), backend,
- "signal::contact-changed", G_CALLBACK (on_google_book_contact_changed), backend,
- "signal::contact-removed", G_CALLBACK (on_google_book_contact_removed), backend,
- "signal::sequence-complete", G_CALLBACK (on_google_book_sequence_complete), backend,
- "signal::auth-required", G_CALLBACK (on_google_book_auth_required), backend,
- NULL);
+ /* Set up our object */
+ priv->username = g_strdup (username);
+ cache_init (backend, use_cache);
+ priv->use_ssl = use_ssl;
+ priv->refresh_interval = refresh_interval;
- __debug__ (G_STRFUNC);
+ /* Remove and re-add the timeout */
+ if (priv->refresh_id != 0) {
+ g_source_remove (priv->refresh_id);
+ priv->refresh_id = g_timeout_add_seconds (priv->refresh_interval, (GSourceFunc) on_refresh_timeout, backend);
+ }
+ /* Set up ready to be interacted with */
e_book_backend_set_is_loaded (backend, TRUE);
e_book_backend_notify_connection_status (backend, TRUE);
e_book_backend_set_is_writable (backend, FALSE);
- google_book_set_offline_mode (priv->book, (priv->mode == GNOME_Evolution_Addressbook_MODE_LOCAL));
+ set_offline_mode (backend, (priv->mode == GNOME_Evolution_Addressbook_MODE_LOCAL));
return GNOME_Evolution_Addressbook_Success;
}
e_book_backend_google_get_static_capabilities (EBookBackend *backend)
{
__debug__ (G_STRFUNC);
- return g_strdup("net,do-initial-query,contact-lists");
+ return g_strdup ("net,do-initial-query,contact-lists");
}
static GNOME_Evolution_Addressbook_CallStatus
static void
e_book_backend_google_set_mode (EBookBackend *backend, GNOME_Evolution_Addressbook_BookMode mode)
{
- EBookBackendGooglePrivate *priv = GET_PRIVATE (backend);
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
gboolean online = (mode == GNOME_Evolution_Addressbook_MODE_REMOTE);
__debug__ (G_STRFUNC);
priv->mode = mode;
- if (priv->book)
- google_book_set_offline_mode (priv->book, !online);
+ set_offline_mode (backend, !online);
e_book_backend_notify_connection_status (backend, online);
/* Mark the book as unwriteable if we're going offline, but don't do the inverse when we go online;
static void
e_book_backend_google_dispose (GObject *object)
{
- EBookBackendGooglePrivate *priv = GET_PRIVATE (object);
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (object)->priv;
__debug__ (G_STRFUNC);
priv->bookviews = g_list_delete_link (priv->bookviews, priv->bookviews);
}
- if (priv->book) {
- g_object_unref (priv->book);
- priv->book = NULL;
+ if (priv->idle_id) {
+ g_source_remove (priv->idle_id);
+ priv->idle_id = 0;
}
+ if (priv->service)
+ g_object_unref (priv->service);
+ priv->service = NULL;
+
+ if (priv->proxy)
+ g_object_unref (priv->proxy);
+ priv->proxy = NULL;
+
+ cache_destroy (E_BOOK_BACKEND (object));
+
G_OBJECT_CLASS (e_book_backend_google_parent_class)->dispose (object);
}
static void
e_book_backend_google_finalize (GObject *object)
{
+ EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (object)->priv;
+
__debug__ (G_STRFUNC);
+ g_free (priv->username);
+
G_OBJECT_CLASS (e_book_backend_google_parent_class)->finalize (object);
}
e_book_backend_google_init (EBookBackendGoogle *backend)
{
__debug__ (G_STRFUNC);
+ backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (backend, E_TYPE_BOOK_BACKEND_GOOGLE, EBookBackendGooglePrivate);
}
EBookBackend *
}
static EBookBackendSyncStatus
-e_book_backend_status_from_google_book_error (GError *error)
+e_book_backend_status_from_gdata_error (GError *error)
{
if (!error)
return GNOME_Evolution_Addressbook_Success;
+++ /dev/null
-/* goggle-book.c - Google contact list abstraction with caching.
- *
- * Copyright (C) 2008 Joergen Scheibengruber
- * Copyright (C) 2010 Philip Withnall
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation; either version 2.1 of the License, or (at your
- * option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Joergen Scheibengruber <joergen.scheibengruber AT googlemail.com>,
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-#include <string.h>
-#include <libedata-book/e-book-backend-cache.h>
-#include <libedataserver/e-proxy.h>
-#include <gdata/gdata-service.h>
-#include <gdata/services/contacts/gdata-contacts-service.h>
-#include <gdata/services/contacts/gdata-contacts-query.h>
-#include <gdata/services/contacts/gdata-contacts-contact.h>
-
-#include "util.h"
-#include "google-book.h"
-
-G_DEFINE_TYPE (GoogleBook, google_book, G_TYPE_OBJECT)
-
-#define URI_GET_CONTACTS "://www.google.com/m8/feeds/contacts/default/full"
-
-#define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_GOOGLE_BOOK, GoogleBookPrivate))
-
-typedef struct _GoogleBookPrivate GoogleBookPrivate;
-
-enum {
- PROP_NONE,
-
- PROP_USERNAME,
- PROP_USE_CACHE,
- PROP_REFRESH_INTERVAL,
- PROP_USE_SSL
-};
-
-enum {
- CONTACT_ADDED,
- CONTACT_CHANGED,
- CONTACT_REMOVED,
- SEQUENCE_COMPLETE,
- AUTH_REQUIRED,
-
- LAST_SIGNAL
-};
-
-static guint google_book_signals [LAST_SIGNAL];
-
-typedef enum {
- NO_CACHE,
- ON_DISK_CACHE,
- IN_MEMORY_CACHE
-} CacheType;
-
-struct _GoogleBookPrivate {
- gchar *username;
- CacheType cache_type;
- union {
- EBookBackendCache *on_disk;
- struct {
- GHashTable *contacts;
- GHashTable *gdata_entries;
- GTimeVal last_updated;
- } in_memory;
- } cache;
-
- gboolean offline;
- GDataService *service;
- EProxy *proxy;
- guint refresh_interval;
- gboolean use_ssl;
-
- gboolean live_mode;
-
- /* In live mode we will send out signals in an idle_handler */
- guint idle_id;
-
- guint refresh_id;
-};
-
-static gboolean google_book_get_new_contacts_in_chunks (GoogleBook *book, gint chunk_size, GError **error);
-
-static void
-google_book_cache_init (GoogleBook *book, gboolean on_disk)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- if (on_disk) {
- priv->cache_type = ON_DISK_CACHE;
- priv->cache.on_disk = e_book_backend_cache_new (priv->username);
- } else {
- priv->cache_type = IN_MEMORY_CACHE;
- priv->cache.in_memory.contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
- priv->cache.in_memory.gdata_entries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
- memset (&priv->cache.in_memory.last_updated, 0, sizeof (GTimeVal));
- }
-}
-
-static EContact *
-google_book_cache_add_contact (GoogleBook *book, GDataEntry *entry)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
- EContact *contact;
- const gchar *uid;
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- contact = _e_contact_new_from_gdata_entry (entry);
- _e_contact_add_gdata_entry_xml (contact, entry);
- e_book_backend_cache_add_contact (priv->cache.on_disk, contact);
- _e_contact_remove_gdata_entry_xml (contact);
- return contact;
- case IN_MEMORY_CACHE:
- contact = _e_contact_new_from_gdata_entry (entry);
- uid = e_contact_get_const (contact, E_CONTACT_UID);
- g_hash_table_insert (priv->cache.in_memory.contacts, g_strdup (uid), g_object_ref (contact));
- g_hash_table_insert (priv->cache.in_memory.gdata_entries, g_strdup (uid), g_object_ref (entry));
- return contact;
- case NO_CACHE:
- default:
- break;
- }
-
- return NULL;
-}
-
-static gboolean
-google_book_cache_remove_contact (GoogleBook *book, const gchar *uid)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
- gboolean success = TRUE;
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- return e_book_backend_cache_remove_contact (priv->cache.on_disk, uid);
- case IN_MEMORY_CACHE:
- success = g_hash_table_remove (priv->cache.in_memory.contacts, uid);
- return success && g_hash_table_remove (priv->cache.in_memory.gdata_entries, uid);
- case NO_CACHE:
- default:
- break;
- }
-
- return FALSE;
-}
-
-static gboolean
-google_book_cache_has_contact (GoogleBook *book, const gchar *uid)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- return e_book_backend_cache_check_contact (priv->cache.on_disk, uid);
- case IN_MEMORY_CACHE:
- return g_hash_table_lookup (priv->cache.in_memory.contacts, uid) ? TRUE : FALSE;
- case NO_CACHE:
- default:
- break;
- }
-
- return FALSE;
-}
-
-static EContact *
-google_book_cache_get_contact (GoogleBook *book, const gchar *uid, GDataEntry **entry)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
- EContact *contact;
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- contact = e_book_backend_cache_get_contact (priv->cache.on_disk, uid);
- if (contact) {
- if (entry) {
- const gchar *entry_xml, *edit_link;
-
- entry_xml = _e_contact_get_gdata_entry_xml (contact, &edit_link);
- *entry = GDATA_ENTRY (gdata_parsable_new_from_xml (GDATA_TYPE_CONTACTS_CONTACT, entry_xml, -1, NULL));
-
- if (*entry) {
- GDataLink *link = gdata_link_new (edit_link, GDATA_LINK_EDIT);
- gdata_entry_add_link (*entry, link);
- g_object_unref (link);
- }
- }
-
- _e_contact_remove_gdata_entry_xml (contact);
- }
- return contact;
- case IN_MEMORY_CACHE:
- contact = g_hash_table_lookup (priv->cache.in_memory.contacts, uid);
- if (entry) {
- *entry = g_hash_table_lookup (priv->cache.in_memory.gdata_entries, uid);
- if (*entry)
- g_object_ref (*entry);
- }
-
- if (contact)
- g_object_ref (contact);
-
- return contact;
- case NO_CACHE:
- default:
- break;
- }
-
- return NULL;
-}
-
-static GList *
-_g_hash_table_to_list (GHashTable *ht)
-{
- GList *l = NULL;
- GHashTableIter iter;
- gpointer key, value;
-
- g_hash_table_iter_init (&iter, ht);
- while (g_hash_table_iter_next (&iter, &key, &value))
- l = g_list_prepend (l, g_object_ref (G_OBJECT (value)));
-
- l = g_list_reverse (l);
-
- return l;
-}
-
-static GList *
-google_book_cache_get_contacts (GoogleBook *book)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
- GList *contacts, *iter;
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- contacts = e_book_backend_cache_get_contacts (priv->cache.on_disk, "(contains \"x-evolution-any-field\" \"\")");
- for (iter = contacts; iter; iter = iter->next)
- _e_contact_remove_gdata_entry_xml (iter->data);
-
- return contacts;
- case IN_MEMORY_CACHE:
- return _g_hash_table_to_list (priv->cache.in_memory.contacts);
- case NO_CACHE:
- default:
- break;
- }
-
- return NULL;
-}
-
-static void
-google_book_cache_freeze (GoogleBook *book)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- if (priv->cache_type == ON_DISK_CACHE)
- e_file_cache_freeze_changes (E_FILE_CACHE (priv->cache.on_disk));
-}
-
-static void
-google_book_cache_thaw (GoogleBook *book)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- if (priv->cache_type == ON_DISK_CACHE)
- e_file_cache_thaw_changes (E_FILE_CACHE (priv->cache.on_disk));
-}
-
-static gchar *
-google_book_cache_get_last_update (GoogleBook *book)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- return e_book_backend_cache_get_time (priv->cache.on_disk);
- case IN_MEMORY_CACHE:
- if (priv->cache.in_memory.contacts)
- return g_time_val_to_iso8601 (&priv->cache.in_memory.last_updated);
- break;
- case NO_CACHE:
- default:
- break;
- }
-
- return NULL;
-}
-
-static gboolean
-google_book_cache_get_last_update_tv (GoogleBook *book, GTimeVal *tv)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
- gchar *last_update;
- gint rv;
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- last_update = e_book_backend_cache_get_time (priv->cache.on_disk);
- rv = last_update ? g_time_val_from_iso8601 (last_update, tv) : FALSE;
- g_free (last_update);
- return rv;
- case IN_MEMORY_CACHE:
- memcpy (tv, &priv->cache.in_memory.last_updated, sizeof (GTimeVal));
- return priv->cache.in_memory.contacts != NULL;
- case NO_CACHE:
- default:
- break;
- }
-
- return FALSE;
-}
-
-static void
-google_book_cache_set_last_update (GoogleBook *book, GTimeVal *tv)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
- gchar *_time;
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- _time = g_time_val_to_iso8601 (tv);
- /* Work around a bug in EBookBackendCache */
- e_file_cache_remove_object (E_FILE_CACHE (priv->cache.on_disk), "last_update_time");
- e_book_backend_cache_set_time (priv->cache.on_disk, _time);
- g_free (_time);
- return;
- case IN_MEMORY_CACHE:
- memcpy (&priv->cache.in_memory.last_updated, tv, sizeof (GTimeVal));
- case NO_CACHE:
- default:
- break;
- }
-}
-
-static gboolean
-google_book_cache_needs_update (GoogleBook *book, guint *remaining_secs)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
- GTimeVal last, current;
- guint diff;
- gboolean rv;
-
- if (remaining_secs)
- *remaining_secs = G_MAXUINT;
-
- /* We never want to update in offline mode */
- if (priv->offline)
- return FALSE;
-
- rv = google_book_cache_get_last_update_tv (book, &last);
-
- if (!rv)
- return TRUE;
-
- g_get_current_time (¤t);
- if (last.tv_sec > current.tv_sec) {
- g_warning ("last update is in the feature?");
-
- /* Do an update so we can fix this */
- return TRUE;
- }
- diff = current.tv_sec - last.tv_sec;
-
- if (diff >= priv->refresh_interval)
- return TRUE;
-
- if (remaining_secs)
- *remaining_secs = priv->refresh_interval - diff;
-
- __debug__ ("No update needed. Next update needed in %d secs", priv->refresh_interval - diff);
-
- return FALSE;
-}
-
-static gboolean on_refresh_timeout (gpointer user_data);
-
-static gboolean
-google_book_cache_refresh_if_needed (GoogleBook *book, GError **error)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
- guint remaining_secs;
- gint rv = TRUE;
- gboolean install_timeout;
-
- __debug__ (G_STRFUNC);
-
- if (priv->offline || !priv->service) {
- __debug__ ("We are not connected to Google%s.", priv->offline ? " (offline mode)" : "");
- return TRUE;
- }
-
- install_timeout = (priv->live_mode) && (priv->refresh_interval > 0) && (0 == priv->refresh_id);
-
- if (google_book_cache_needs_update (book, &remaining_secs)) {
- rv = google_book_get_new_contacts_in_chunks (book, 32, error);
- if (install_timeout)
- priv->refresh_id = g_timeout_add_seconds (priv->refresh_interval, on_refresh_timeout, book);
- } else {
- if (install_timeout) {
- __debug__ ("Installing timeout with %d seconds", remaining_secs);
- priv->refresh_id = g_timeout_add_seconds (remaining_secs, on_refresh_timeout, book);
- }
- }
- return rv;
-}
-
-static gboolean
-on_refresh_timeout (gpointer user_data)
-{
- GoogleBook *book = user_data;
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
-
- priv->refresh_id = 0;
- if (priv->live_mode)
- google_book_cache_refresh_if_needed (book, NULL);
-
- return FALSE;
-}
-
-static void
-google_book_cache_destroy (GoogleBook *book)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- switch (priv->cache_type) {
- case ON_DISK_CACHE:
- g_object_unref (priv->cache.on_disk);
- break;
- case IN_MEMORY_CACHE:
- g_hash_table_destroy (priv->cache.in_memory.contacts);
- g_hash_table_destroy (priv->cache.in_memory.gdata_entries);
- break;
- case NO_CACHE:
- default:
- break;
- }
-
- priv->cache_type = NO_CACHE;
-}
-
-static void
-google_book_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (object);
-
- switch (property_id) {
- case PROP_USERNAME:
- g_value_set_string (value, priv->username);
- break;
- case PROP_USE_CACHE:
- g_value_set_boolean (value, (priv->cache_type == ON_DISK_CACHE));
- break;
- case PROP_REFRESH_INTERVAL:
- g_value_set_uint (value, priv->refresh_interval);
- break;
- case PROP_USE_SSL:
- g_value_set_boolean (value, priv->use_ssl);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-static void
-google_book_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (object);
-
- switch (property_id) {
- case PROP_USERNAME:
- priv->username = g_value_dup_string (value);
- break;
- case PROP_USE_CACHE:
- google_book_cache_init (GOOGLE_BOOK (object), g_value_get_boolean (value));
- break;
- case PROP_REFRESH_INTERVAL:
- priv->refresh_interval = g_value_get_uint (value);
-
- /* Remove and re-add the timeout */
- if (priv->refresh_id != 0) {
- g_source_remove (priv->refresh_id);
- priv->refresh_id = g_timeout_add_seconds (priv->refresh_interval, on_refresh_timeout, GOOGLE_BOOK (object));
- }
- break;
- case PROP_USE_SSL:
- priv->use_ssl = g_value_get_boolean (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-static void
-google_book_dispose (GObject *object)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (object);
-
- if (priv->idle_id) {
- g_source_remove (priv->idle_id);
- priv->idle_id = 0;
- }
-
- if (priv->service)
- g_object_unref (priv->service);
- priv->service = NULL;
-
- if (priv->proxy)
- g_object_unref (priv->proxy);
- priv->proxy = NULL;
-
- google_book_cache_destroy (GOOGLE_BOOK (object));
-
- if (G_OBJECT_CLASS (google_book_parent_class)->dispose)
- G_OBJECT_CLASS (google_book_parent_class)->dispose (object);
-}
-
-static void
-google_book_finalize (GObject *object)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (object);
-
- g_free (priv->username);
-
- if (G_OBJECT_CLASS (google_book_parent_class)->finalize)
- G_OBJECT_CLASS (google_book_parent_class)->finalize (object);
-}
-
-static void
-google_book_emit_contact_added (GoogleBook *book, EContact *contact)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- if (priv->live_mode)
- g_signal_emit (book, google_book_signals [CONTACT_ADDED], 0, contact);
-}
-
-static void
-google_book_emit_contact_changed (GoogleBook *book, EContact *contact)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- if (priv->live_mode)
- g_signal_emit (book, google_book_signals [CONTACT_CHANGED], 0, contact);
-}
-
-static void
-google_book_emit_contact_removed (GoogleBook *book, const gchar *uid)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- if (priv->live_mode)
- g_signal_emit (book, google_book_signals [CONTACT_REMOVED], 0, uid);
-}
-
-static void
-google_book_emit_sequence_complete (GoogleBook *book, GError *error)
-{
- GoogleBookPrivate *priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- if (priv->live_mode)
- g_signal_emit (book, google_book_signals [SEQUENCE_COMPLETE], 0, error);
-}
-
-static void
-google_book_emit_auth_required (GoogleBook *book)
-{
- __debug__ (G_STRFUNC);
- g_signal_emit (book, google_book_signals [AUTH_REQUIRED], 0);
-}
-
-static void
-google_book_class_init (GoogleBookClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (GoogleBookPrivate));
-
- object_class->get_property = google_book_get_property;
- object_class->set_property = google_book_set_property;
- object_class->dispose = google_book_dispose;
- object_class->finalize = google_book_finalize;
-
- g_object_class_install_property (object_class, PROP_USERNAME,
- g_param_spec_string ("username",
- "Username",
- "The username.",
- NULL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
- g_object_class_install_property (object_class, PROP_USE_CACHE,
- g_param_spec_boolean ("use-cache",
- "Use Cache?",
- "Whether an on-disk cache should be used.",
- TRUE,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
- g_object_class_install_property (object_class, PROP_REFRESH_INTERVAL,
- g_param_spec_uint ("refresh-interval",
- "Refresh Interval",
- "Specifies the number of seconds until the local cache is updated from the "
- "server. 0 means no updates.",
- 0, G_MAXUINT, 3600,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (object_class, PROP_USE_SSL,
- g_param_spec_boolean ("use-ssl",
- "Use SSL?",
- "Whether SSL should be used.",
- TRUE,
- G_PARAM_READWRITE));
-
- google_book_signals [CONTACT_CHANGED] = g_signal_new ("contact-changed",
- G_OBJECT_CLASS_TYPE (object_class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (GoogleBookClass, contact_changed),
- NULL, NULL,
- g_cclosure_marshal_VOID__POINTER,
- G_TYPE_NONE, 1,
- G_TYPE_POINTER);
-
- google_book_signals [CONTACT_ADDED] = g_signal_new ("contact-added",
- G_OBJECT_CLASS_TYPE (object_class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (GoogleBookClass, contact_added),
- NULL, NULL,
- g_cclosure_marshal_VOID__POINTER,
- G_TYPE_NONE, 1,
- G_TYPE_POINTER);
-
- google_book_signals [CONTACT_REMOVED] = g_signal_new ("contact-removed",
- G_OBJECT_CLASS_TYPE (object_class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (GoogleBookClass, contact_removed),
- NULL, NULL,
- g_cclosure_marshal_VOID__POINTER,
- G_TYPE_NONE, 1,
- G_TYPE_POINTER);
-
- google_book_signals [SEQUENCE_COMPLETE] = g_signal_new ("sequence-complete",
- G_OBJECT_CLASS_TYPE (object_class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (GoogleBookClass, sequence_complete),
- NULL, NULL,
- g_cclosure_marshal_VOID__POINTER,
- G_TYPE_NONE, 1,
- G_TYPE_POINTER);
-
- google_book_signals [AUTH_REQUIRED] = g_signal_new ("auth-required",
- G_OBJECT_CLASS_TYPE (object_class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (GoogleBookClass, auth_required),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-}
-
-static void
-google_book_init (GoogleBook *self)
-{
- __debug__ (G_STRFUNC);
-}
-
-GoogleBook *
-google_book_new (const gchar *username, gboolean use_cache)
-{
- return g_object_new (TYPE_GOOGLE_BOOK,
- "username", username,
- "use-cache", use_cache,
- "use-ssl", TRUE,
- "refresh-interval", 3600,
- NULL);
-}
-
-static void
-proxy_settings_changed (EProxy *proxy, gpointer user_data)
-{
- SoupURI *proxy_uri = NULL;
- gchar *uri;
- GoogleBookPrivate *priv = (GoogleBookPrivate*) user_data;
-
- if (!priv || !priv->service)
- return;
-
- /* Build the URI which libgdata would use to query contacts */
- uri = g_strconcat (priv->use_ssl ? "https" : "http", URI_GET_CONTACTS, NULL);
-
- /* use proxy if necessary */
- if (e_proxy_require_proxy_for_uri (proxy, uri))
- proxy_uri = e_proxy_peek_uri_for (proxy, uri);
- gdata_service_set_proxy_uri (GDATA_SERVICE (priv->service), proxy_uri);
-
- g_free (uri);
-}
-
-gboolean
-google_book_connect_to_google (GoogleBook *book, const gchar *password, GError **error)
-{
- GoogleBookPrivate *priv;
-
- __debug__ (G_STRFUNC);
- g_return_val_if_fail (IS_GOOGLE_BOOK (book), FALSE);
- g_return_val_if_fail (password, FALSE);
-
- priv = GET_PRIVATE (book);
-
- if (priv->service) {
- g_warning ("Connection to Google already established.");
- return TRUE;
- }
-
- priv->service = GDATA_SERVICE (gdata_contacts_service_new ("evolution-client-0.0.1"));
- priv->proxy = e_proxy_new ();
- e_proxy_setup_proxy (priv->proxy);
- proxy_settings_changed (priv->proxy, priv);
-
- if (!gdata_service_authenticate (priv->service, priv->username, password, NULL, error)) {
- g_object_unref (priv->service);
- priv->service = NULL;
- g_object_unref (priv->proxy);
- priv->proxy = NULL;
- return FALSE;
- }
-
- g_signal_connect (priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), priv);
-
- return google_book_cache_refresh_if_needed (book, error);
-}
-
-void
-google_book_set_offline_mode (GoogleBook *book, gboolean offline)
-{
- GoogleBookPrivate *priv;
-
- __debug__ (G_STRFUNC);
- g_return_if_fail (IS_GOOGLE_BOOK (book));
-
- priv = GET_PRIVATE (book);
-
- priv->offline = offline;
- if (offline) {
- /* Going offline, so we can free our service and proxy */
- if (priv->service)
- g_object_unref (priv->service);
- priv->service = NULL;
-
- if (priv->proxy)
- g_object_unref (priv->proxy);
- priv->proxy = NULL;
- } else {
- /* Going online, so we need to re-authenticate and re-create the service and proxy.
- * This is done in google_book_connect_to_google(), which is called by EBookBackendGoogle when it gets the authentication data. */
- google_book_emit_auth_required (book);
- }
-}
-
-gboolean
-google_book_add_contact (GoogleBook *book, EContact *contact, EContact **out_contact, GError **error)
-{
- GoogleBookPrivate *priv;
- GDataEntry *entry, *new_entry;
- gchar *xml;
-
- *out_contact = NULL;
-
- __debug__ (G_STRFUNC);
- g_return_val_if_fail (IS_GOOGLE_BOOK (book), FALSE);
-
- priv = GET_PRIVATE (book);
-
- g_return_val_if_fail (priv->service, FALSE);
-
- entry = _gdata_entry_new_from_e_contact (contact);
- xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
- __debug__ ("new entry with xml: %s", xml);
- g_free (xml);
-
- new_entry = GDATA_ENTRY (gdata_contacts_service_insert_contact (GDATA_CONTACTS_SERVICE (priv->service), GDATA_CONTACTS_CONTACT (entry),
- NULL, error));
- g_object_unref (entry);
- if (!new_entry)
- return FALSE;
-
- *out_contact = google_book_cache_add_contact (book, new_entry);
- g_object_unref (new_entry);
-
- return TRUE;
-}
-
-gboolean
-google_book_update_contact (GoogleBook *book, EContact *contact, EContact **out_contact, GError **error)
-{
- GoogleBookPrivate *priv;
- GDataEntry *entry, *new_entry;
- EContact *cached_contact;
- gchar *xml;
- const gchar *uid;
-
- *out_contact = NULL;
-
- __debug__ (G_STRFUNC);
- g_return_val_if_fail (IS_GOOGLE_BOOK (book), FALSE);
-
- priv = GET_PRIVATE (book);
-
- g_return_val_if_fail (priv->service, FALSE);
-
- uid = e_contact_get (contact, E_CONTACT_UID);
-
- entry = NULL;
- cached_contact = google_book_cache_get_contact (book, uid, &entry);
- if (!cached_contact) {
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND, "Contact with uid %s not found in cache.", uid);
- return FALSE;
- }
- g_object_unref (cached_contact);
- _gdata_entry_update_from_e_contact (entry, contact);
-
- xml = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
- __debug__ ("Before:\n%s", xml);
- g_free (xml);
-
- new_entry = GDATA_ENTRY (gdata_contacts_service_update_contact (GDATA_CONTACTS_SERVICE (priv->service), GDATA_CONTACTS_CONTACT (entry),
- NULL, error));
- g_object_unref (entry);
-
- if (!new_entry)
- return FALSE;
-
- xml = NULL;
- if (new_entry)
- xml = gdata_parsable_get_xml (GDATA_PARSABLE (new_entry));
- __debug__ ("After:\n%s", xml);
- g_free (xml);
-
- *out_contact = google_book_cache_add_contact (book, new_entry);
- g_object_unref (new_entry);
-
- return TRUE;
-}
-
-gboolean
-google_book_remove_contact (GoogleBook *book, const gchar *uid, GError **error)
-{
- GoogleBookPrivate *priv;
- GDataEntry *entry = NULL;
- EContact *cached_contact;
- gboolean success;
-
- __debug__ (G_STRFUNC);
- g_return_val_if_fail (IS_GOOGLE_BOOK (book), FALSE);
-
- priv = GET_PRIVATE (book);
-
- g_return_val_if_fail (priv->service, FALSE);
-
- cached_contact = google_book_cache_get_contact (book, uid, &entry);
- if (!cached_contact) {
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND, "Contact with uid %s not found in cache.", uid);
- return FALSE;
- }
-
- google_book_cache_remove_contact (book, uid);
- success = gdata_service_delete_entry (GDATA_SERVICE (priv->service), entry, NULL, error);
- g_object_unref (entry);
- g_object_unref (cached_contact);
-
- return success;
-}
-
-static void
-process_subsequent_entry (gpointer list_data, gpointer user_data)
-{
- GoogleBookPrivate *priv;
- GoogleBook *book = user_data;
- GDataEntry *entry;
- gboolean is_deleted, is_cached;
- const gchar *uid;
-
- __debug__ (G_STRFUNC);
- priv = GET_PRIVATE (book);
- entry = GDATA_ENTRY (list_data);
- uid = gdata_entry_get_id (entry);
- is_deleted = gdata_contacts_contact_is_deleted (GDATA_CONTACTS_CONTACT (entry));
-
- is_cached = google_book_cache_has_contact (book, uid);
- if (is_deleted) {
- /* Do we have this item in our cache? */
- if (is_cached) {
- google_book_cache_remove_contact (book, uid);
- google_book_emit_contact_removed (book, uid);
- }
- } else {
- EContact *contact = google_book_cache_add_contact (book, entry);
-
- if (is_cached)
- google_book_emit_contact_changed (book, contact);
- else
- google_book_emit_contact_added (book, contact);
-
- g_object_unref (contact);
- }
-}
-
-static void
-process_initial_entry (gpointer list_data, gpointer user_data)
-{
- GoogleBookPrivate *priv;
- GoogleBook *book = user_data;
- GDataEntry *entry;
- EContact *contact;
-
- __debug__ (G_STRFUNC);
- priv = GET_PRIVATE (book);
- entry = GDATA_ENTRY (list_data);
-
- contact = google_book_cache_add_contact (book, entry);
-
- google_book_emit_contact_added (GOOGLE_BOOK (book), contact);
- g_object_unref (contact);
-}
-
-static gboolean
-google_book_get_new_contacts_in_chunks (GoogleBook *book, gint chunk_size, GError **error)
-{
- GoogleBookPrivate *priv;
- GDataFeed *feed;
- GDataQuery *query;
- gchar *last_updated;
- GError *our_error = NULL;
- gboolean rv = TRUE;
- GTimeVal current_time;
- int results;
-
- priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- g_return_val_if_fail (priv->service, FALSE);
-
- last_updated = google_book_cache_get_last_update (book);
-
- google_book_cache_freeze (book);
-
- /* Build our query */
- query = GDATA_QUERY (gdata_contacts_query_new_with_limits (NULL, 1, chunk_size));
- if (last_updated) {
- GTimeVal updated;
-
- g_assert (g_time_val_from_iso8601 (last_updated, &updated) == TRUE);
- gdata_query_set_updated_min (query, &updated);
- gdata_contacts_query_set_show_deleted (GDATA_CONTACTS_QUERY (query), TRUE);
- }
-
- /* Get the paginated results */
- do {
- GList *entries;
-
- /* Run the query */
- feed = gdata_contacts_service_query_contacts (GDATA_CONTACTS_SERVICE (priv->service), query, NULL, NULL, NULL, &our_error);
-
- if (our_error) {
- google_book_emit_sequence_complete (book, our_error);
- g_propagate_error (error, our_error);
-
- rv = FALSE;
- goto out;
- }
-
- entries = gdata_feed_get_entries (feed);
- results = entries ? g_list_length (entries) : 0;
- __debug__ ("Feed has %d entries", results);
-
- /* Process the entries from this page */
- if (last_updated)
- g_list_foreach (entries, process_subsequent_entry, book);
- else
- g_list_foreach (entries, process_initial_entry, book);
- g_object_unref (feed);
-
- /* Move to the next page */
- gdata_query_next_page (query);
- } while (results == chunk_size);
-
- /* Finish updating the cache */
- g_get_current_time (¤t_time);
- google_book_cache_set_last_update (book, ¤t_time);
- google_book_emit_sequence_complete (book, NULL);
-
-out:
- g_free (last_updated);
- google_book_cache_thaw (book);
-
- return rv;
-}
-
-EContact *
-google_book_get_contact (GoogleBook *book, const gchar *uid, GError **error)
-{
- GoogleBookPrivate *priv;
- EContact *contact;
- GError *child_error = NULL;
-
- priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- g_return_val_if_fail (IS_GOOGLE_BOOK (book), NULL);
-
- google_book_cache_refresh_if_needed (book, &child_error);
-
- contact = google_book_cache_get_contact (book, uid, NULL);
-
- if (contact) {
- /* We found the contact, so forget about errors during refresh */
- if (child_error)
- g_error_free (child_error);
-
- return contact;
- }
-
- if (!child_error)
- g_propagate_error (error, child_error);
- else
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND, "Contact with uid %s not found in cache.", uid);
-
- return NULL;
-}
-
-GList *
-google_book_get_all_contacts (GoogleBook *book, GError **error)
-{
- GoogleBookPrivate *priv;
- GList *contacts;
- GError *child_error = NULL;
-
- priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- g_return_val_if_fail (IS_GOOGLE_BOOK (book), NULL);
-
- google_book_cache_refresh_if_needed (book, &child_error);
-
- contacts = google_book_cache_get_contacts (book);
-
- if (contacts) {
- /* We found the contact, so forget about errors during refresh */
- if (child_error)
- g_error_free (child_error);
-
- return contacts;
- }
-
- g_propagate_error (error, child_error);
- return NULL;
-}
-
-static gboolean
-on_refresh_idle (gpointer user_data)
-{
- GoogleBook *book = user_data;
- GoogleBookPrivate *priv;
-
- priv = GET_PRIVATE (book);
-
- priv->idle_id = 0;
-
- google_book_cache_refresh_if_needed (book, NULL);
-
- return FALSE;
-}
-
-GList *
-google_book_get_all_contacts_in_live_mode (GoogleBook *book)
-{
- GoogleBookPrivate *priv;
- gboolean need_update;
- GList *contacts;
-
- priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- g_return_val_if_fail (IS_GOOGLE_BOOK (book), NULL);
-
- priv->live_mode = TRUE;
-
- need_update = google_book_cache_needs_update (book, NULL);
-
- if (need_update) {
- if (!priv->service) {
- /* We need authorization first */
- google_book_emit_auth_required (book);
- } else {
- priv->idle_id = g_idle_add (on_refresh_idle, book);
- }
- }
-
- contacts = google_book_cache_get_contacts (book);
- __debug__ ("%d contacts found in cache", g_list_length (contacts));
-
- return contacts;
-}
-
-void
-google_book_set_live_mode (GoogleBook *book, gboolean live_mode)
-{
- GoogleBookPrivate *priv;
-
- priv = GET_PRIVATE (book);
-
- __debug__ (G_STRFUNC);
- priv->live_mode = live_mode;
-
- if (FALSE == live_mode && priv->refresh_id > 0) {
- g_source_remove (priv->refresh_id);
- priv->refresh_id = 0;
- }
-
- if (priv->live_mode)
- google_book_cache_refresh_if_needed (book, NULL);
-}