Clean up the Google Contacts addressbook backend
authorPhilip Withnall <philip@tecnocode.co.uk>
Mon, 19 Apr 2010 23:31:55 +0000 (00:31 +0100)
committerPhilip Withnall <philip@tecnocode.co.uk>
Tue, 20 Apr 2010 19:34:34 +0000 (20:34 +0100)
Merge the entirely redundant GoogleBook into EBookBackendGoogle and simplify
things a bit.

addressbook/backends/google/Makefile.am
addressbook/backends/google/e-book-backend-google.c
addressbook/backends/google/google-book.c [deleted file]
addressbook/backends/google/google-book.h [deleted file]
po/POTFILES.in

index a54ceb9..c7912de 100644 (file)
@@ -2,31 +2,29 @@ extension_LTLIBRARIES = libebookbackendgoogle.la
 
 libebookbackendgoogle_la_CPPFLAGS = \
        $(AM_CPPFLAGS)                                  \
-       -DG_LOG_DOMAIN=\"libebookbackendgoogle\"        \
-       -I$(top_srcdir)                                 \
-       -I$(top_builddir)                               \
-       -I$(top_srcdir)/addressbook                     \
-       -I$(top_builddir)/addressbook                   \
-       $(SOUP_CFLAGS)                                  \
+       -DG_LOG_DOMAIN=\"libebookbackendgoogle\"        \
+       -I$(top_srcdir)                                 \
+       -I$(top_builddir)                               \
+       -I$(top_srcdir)/addressbook                     \
+       -I$(top_builddir)/addressbook                   \
+       $(SOUP_CFLAGS)                                  \
        $(EVOLUTION_ADDRESSBOOK_CFLAGS)                 \
        $(GDATA_CFLAGS)
 
 libebookbackendgoogle_la_SOURCES = \
-       util.h \
-       util.c \
-       google-book.h \
-       google-book.c \
-       e-book-backend-google-factory.c \
-       e-book-backend-google.h \
+       util.h                          \
+       util.c                          \
+       e-book-backend-google-factory.c \
+       e-book-backend-google.h         \
        e-book-backend-google.c
 
 libebookbackendgoogle_la_LIBADD = \
-       $(top_builddir)/addressbook/libebook/libebook-1.2.la \
-       $(top_builddir)/addressbook/libedata-book/libedata-book-1.2.la \
-       $(top_builddir)/libedataserver/libedataserver-1.2.la \
-       $(top_builddir)/libebackend/libebackend-1.2.la \
-       $(SOUP_LIBS) \
-       $(EVOLUTION_ADDRESSBOOK_LIBS) \
+       $(top_builddir)/addressbook/libebook/libebook-1.2.la            \
+       $(top_builddir)/addressbook/libedata-book/libedata-book-1.2.la  \
+       $(top_builddir)/libedataserver/libedataserver-1.2.la            \
+       $(top_builddir)/libebackend/libebackend-1.2.la                  \
+       $(SOUP_LIBS)                                                    \
+       $(EVOLUTION_ADDRESSBOOK_LIBS)                                   \
        $(GDATA_LIBS)
 
 libebookbackendgoogle_la_LDFLAGS = \
index 6f498a1..a5d5442 100644 (file)
 #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 (&current);
+       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 (&current_time);
+       cache_set_last_update (backend, &current_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;
@@ -65,53 +628,92 @@ e_book_backend_google_create_contact (EBookBackendSync *backend, EDataBook *book
        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;
 
@@ -121,13 +723,15 @@ e_book_backend_google_remove_contacts (EBookBackendSync *backend, EDataBook *boo
 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;
@@ -135,41 +739,89 @@ e_book_backend_google_modify_contact (EBookBackendSync *backend, EDataBook *book
        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);
 
@@ -179,188 +831,230 @@ e_book_backend_google_get_contact (EBookBackendSync *backend, EDataBook *book, g
 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;
 }
 
@@ -476,9 +1170,10 @@ e_book_backend_google_get_supported_fields (EBookBackendSync *backend, EDataBook
 
        __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;
@@ -500,26 +1195,47 @@ e_book_backend_google_remove (EBookBackendSync *backend, EDataBook *book, guint3
 }
 
 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') {
@@ -527,6 +1243,7 @@ e_book_backend_google_load_source (EBookBackend *backend, ESource *source, gbool
                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");
@@ -544,26 +1261,23 @@ e_book_backend_google_load_source (EBookBackend *backend, ESource *source, gbool
        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;
 }
@@ -572,7 +1286,7 @@ static gchar *
 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
@@ -585,7 +1299,7 @@ e_book_backend_google_cancel_operation (EBookBackend *backend, EDataBook *book)
 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);
@@ -595,8 +1309,7 @@ e_book_backend_google_set_mode (EBookBackend *backend, GNOME_Evolution_Addressbo
 
        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;
@@ -608,7 +1321,7 @@ e_book_backend_google_set_mode (EBookBackend *backend, GNOME_Evolution_Addressbo
 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);
 
@@ -617,19 +1330,33 @@ e_book_backend_google_dispose (GObject *object)
                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);
 }
 
@@ -673,6 +1400,7 @@ static void
 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 *
@@ -687,7 +1415,7 @@ e_book_backend_google_new (void)
 }
 
 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;
diff --git a/addressbook/backends/google/google-book.c b/addressbook/backends/google/google-book.c
deleted file mode 100644 (file)
index cc11ad6..0000000
+++ /dev/null
@@ -1,1138 +0,0 @@
-/* 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 (&current);
-       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 (&current_time);
-       google_book_cache_set_last_update (book, &current_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);
-}
diff --git a/addressbook/backends/google/google-book.h b/addressbook/backends/google/google-book.h
deleted file mode 100644 (file)
index 3fd4d21..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/* goggle-book.h - Google contact list abstraction with caching.
- *
- * Copyright (C) 2008 Joergen Scheibengruber
- *
- * 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>
- */
-
-#ifndef _GOOGLE_BOOK
-#define _GOOGLE_BOOK
-
-#include <libebook/e-contact.h>
-
-G_BEGIN_DECLS
-
-#define TYPE_GOOGLE_BOOK google_book_get_type()
-#define GOOGLE_BOOK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_GOOGLE_BOOK, GoogleBook))
-#define GOOGLE_BOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_GOOGLE_BOOK, GoogleBookClass))
-#define IS_GOOGLE_BOOK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_GOOGLE_BOOK))
-#define IS_GOOGLE_BOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_GOOGLE_BOOK))
-#define GOOGLE_BOOK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_GOOGLE_BOOK, GoogleBookClass))
-
-typedef struct _GoogleBook      GoogleBook;
-typedef struct _GoogleBookClass GoogleBookClass;
-
-struct _GoogleBook {
-       GObject parent;
-};
-
-struct _GoogleBookClass {
-       GObjectClass parent_class;
-
-       void (*contact_added) (EContact* contact);
-       void (*contact_changed) (EContact* contact);
-       void (*contact_removed) (const gchar *uid);
-       void (*sequence_complete) (GError *error);
-
-       void (*auth_required) (void);
-};
-
-typedef void (*GoogleBookContactRetrievedCallback) (EContact *contact, gpointer user_data);
-
-GType google_book_get_type (void);
-
-GoogleBook* google_book_new (const gchar *username, gboolean use_cache);
-
-gboolean google_book_connect_to_google (GoogleBook *book, const gchar *password, GError **error);
-
-void google_book_set_offline_mode (GoogleBook *book, gboolean offline);
-void google_book_set_live_mode (GoogleBook *book, gboolean live_mode);
-
-gboolean google_book_add_contact (GoogleBook *book, EContact *contact, EContact **out_contact, GError **error);
-gboolean google_book_update_contact (GoogleBook *book, EContact *contact, EContact **out_contact, GError **error);
-gboolean google_book_remove_contact (GoogleBook *book, const gchar *uid, GError **error);
-
-EContact *google_book_get_contact (GoogleBook *book, const gchar * uid, GError **error);
-GList *google_book_get_all_contacts (GoogleBook *book, GError **error);
-GList *google_book_get_all_contacts_in_live_mode (GoogleBook *book);
-
-G_END_DECLS
-
-#endif /* _GOOGLE_BOOK */
index 360c5aa..b97c86b 100644 (file)
@@ -3,7 +3,6 @@
 [encoding: UTF-8]
 addressbook/backends/file/e-book-backend-file.c
 addressbook/backends/google/e-book-backend-google.c
-addressbook/backends/google/google-book.c
 addressbook/backends/google/util.c
 addressbook/backends/groupwise/e-book-backend-groupwise.c
 addressbook/backends/ldap/e-book-backend-ldap.c