evolution-data-server r9342 - in trunk/addressbook: . backends/google



Author: mfcn
Date: Wed Aug 13 19:51:47 2008
New Revision: 9342
URL: http://svn.gnome.org/viewvc/evolution-data-server?rev=9342&view=rev

Log:
	* backends/google/Makefile.am:
	* backends/google/e-book-backend-google.c
	(e_book_backend_google_create_contact),
	(e_book_backend_google_remove_contacts),
	(e_book_backend_google_modify_contact),
	(e_book_backend_google_get_contact),
	(e_book_backend_google_get_contact_list),
	(on_google_book_contact_added), (on_google_book_contact_removed),
	(on_google_book_contact_changed),
	(on_google_book_sequence_complete),
	(e_book_backend_google_start_book_view),
	(e_book_backend_google_stop_book_view),
	(e_book_backend_google_authenticate_user),
	(e_book_backend_google_remove), (on_google_book_auth_required),
	(e_book_backend_google_load_source),
	(e_book_backend_google_set_mode), (e_book_backend_google_dispose),
	(e_book_backend_google_finalize),
	(e_book_backend_status_from_google_book_error):
	* backends/google/google-book.c (google_book_cache_init),
	(google_book_cache_add_contact),
	(google_book_cache_remove_contact),
	(google_book_cache_get_contact), (_g_hash_table_to_list),
	(google_book_cache_get_contacts), (google_book_cache_freeze),
	(google_book_cache_thaw), (google_book_cache_get_last_update),
	(google_book_cache_get_last_update_tv),
	(google_book_cache_set_last_update),
	(google_book_cache_needs_update),
	(google_book_cache_refresh_if_needed), (on_refresh_timeout),
	(google_book_cache_destroy), (google_book_construct_base_uri),
	(google_book_get_property), (google_book_set_property),
	(google_book_dispose), (google_book_finalize),
	(google_book_emit_contact_added),
	(google_book_emit_contact_changed),
	(google_book_emit_contact_removed),
	(google_book_emit_sequence_complete),
	(google_book_emit_auth_required), (google_book_class_init),
	(google_book_init), (google_book_new),
	(google_book_connect_to_google), (google_book_set_offline_mode),
	(google_book_add_contact), (google_book_update_contact),
	(google_book_remove_contact), (process_subsequent_entry),
	(process_initial_entry), (google_book_get_new_contacts_in_chunks),
	(google_book_get_contact), (google_book_get_all_contacts),
	(on_refresh_idle), (google_book_get_all_contacts_in_live_mode),
	(google_book_set_live_mode), (google_book_error_from_soup_error):
	* backends/google/google-book.h:

	Created new GoogleBook object that internally implements a local (either
	in-memory or on-disk) cache of the Google contacts list and updates it in
	a specified interval.

	* backends/google/util.c (_gdata_entry_new_from_e_contact),
	(_gdata_entry_update_from_e_contact),
	(_e_contact_new_from_gdata_entry),
	(_e_contact_add_gdata_entry_xml),
	(_e_contact_remove_gdata_entry_xml),
	(_e_contact_get_gdata_entry_xml), (google_rel_label_from_type),
	(attribute_from_gdata_entry_email_address),
	(attribute_from_gdata_entry_im_address),
	(attribute_from_gdata_entry_phone_number),
	(attribute_from_gdata_entry_postal_address),
	(get_google_primary_and_type),
	(gdata_entry_email_address_from_attribute),
	(gdata_entry_im_address_from_attribute),
	(gdata_entry_phone_number_from_attribute),
	(gdata_entry_postal_address_from_attribute):
	* backends/google/util.h:

	Removed some no longer needed utility functions. Use
	e_contact_[get|_set]_name instead of custom fullname parsing. Try to
	ensure that primary properities again land in the same slot after editing.



Added:
   trunk/addressbook/backends/google/google-book.c
   trunk/addressbook/backends/google/google-book.h
Modified:
   trunk/addressbook/ChangeLog
   trunk/addressbook/backends/google/Makefile.am
   trunk/addressbook/backends/google/e-book-backend-google.c
   trunk/addressbook/backends/google/util.c
   trunk/addressbook/backends/google/util.h

Modified: trunk/addressbook/backends/google/Makefile.am
==============================================================================
--- trunk/addressbook/backends/google/Makefile.am	(original)
+++ trunk/addressbook/backends/google/Makefile.am	Wed Aug 13 19:51:47 2008
@@ -16,6 +16,8 @@
 libebookbackendgoogle_la_SOURCES = \
 	util.h \
 	util.c \
+	google-book.h \
+	google-book.c \
 	e-book-backend-google-factory.h \
 	e-book-backend-google-factory.c \
 	e-book-backend-google.h \

Modified: trunk/addressbook/backends/google/e-book-backend-google.c
==============================================================================
--- trunk/addressbook/backends/google/e-book-backend-google.c	(original)
+++ trunk/addressbook/backends/google/e-book-backend-google.c	Wed Aug 13 19:51:47 2008
@@ -21,16 +21,15 @@
 
 #include <config.h>
 #include <string.h>
+#include <errno.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 <gdata-service-iface.h>
-#include <gdata-google-service.h>
-
 #include "e-book-backend-google.h"
+#include "google-book.h"
 #include "util.h"
 
 G_DEFINE_TYPE (EBookBackendGoogle, e_book_backend_google, E_TYPE_BOOK_BACKEND_SYNC);
@@ -38,19 +37,8 @@
 struct _EBookBackendGooglePrivate
 {
     gint mode;
-    char *base_uri;
-
-    GDataGoogleService *service;
-    GHashTable *gdata_entries;
-
-    guint refresh_interval;
-    guint refresh_feed_id;
-    char *feed_last_updated;
-
-    gboolean authorized;
-
+    GoogleBook *book;
     GList *bookviews;
-    GList *pending_auth_bookviews;
 };
 
 #define GET_PRIVATE(obj)      \
@@ -60,84 +48,41 @@
 
 gboolean __e_book_backend_google_debug__;
 
-static EBookBackendSyncStatus
-e_book_backend_google_initial_query (EBookBackendGoogle *backend);
-
-static EBookBackendSyncStatus
-e_book_backend_google_update_query  (EBookBackendGoogle *backend,
-                                     GList             **added,
-                                     GList             **modified,
-                                     GList             **deleted);
-
-static EBookBackendSyncStatus
-ebookbackend_status_from_soup_error (int http_error);
-
-static gboolean
-e_book_backend_google_ensure_auth (EBookBackendGoogle *backend)
-{
-    EBookBackendGooglePrivate *priv;
-    gboolean is_authorized;
-
-    priv = GET_PRIVATE (backend);
-
-    is_authorized = (priv->authorized && priv->base_uri && priv->service);
-
-    if (FALSE == is_authorized) {
-        e_book_backend_notify_auth_required (E_BOOK_BACKEND (backend));
-        __debug__ ("auth required");
-    }
-
-    return is_authorized;
-}
+static EBookBackendSyncStatus e_book_backend_status_from_google_book_error (GoogleBookError error_code);
 
 static EBookBackendSyncStatus
 e_book_backend_google_create_contact (EBookBackendSync *backend,
                                       EDataBook        *book,
                                       guint32           opid,
                                       const char       *vcard_str,
-                                      EContact        **contact)
+                                      EContact        **out_contact)
 {
     EBookBackendGooglePrivate *priv;
     EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
-    GDataEntry *entry, *new_entry;
+    EContact *contact;
     GError *error = NULL;
 
     __debug__ (G_STRFUNC);
     priv = GET_PRIVATE (backend);
 
     __debug__ ("Creating: %s", vcard_str);
-    *contact = NULL;
+    *out_contact = NULL;
 
     if (priv->mode != GNOME_Evolution_Addressbook_MODE_REMOTE) {
         return GNOME_Evolution_Addressbook_OfflineUnavailable;
     }
 
-    if (FALSE == e_book_backend_google_ensure_auth (E_BOOK_BACKEND_GOOGLE (backend))) {
-        return GNOME_Evolution_Addressbook_AuthenticationRequired;
-    }
-
-    entry = gdata_entry_create_from_vcard (vcard_str);
-    __debug__ ("new entry with xml: %s", gdata_entry_generate_xml (entry));
-    new_entry = gdata_service_insert_entry (GDATA_SERVICE (priv->service),
-                                            priv->base_uri, entry, &error);
-    g_object_unref (entry);
-
-    if (new_entry) {
-        const char *uid;
-
-        uid = gdata_entry_get_id (new_entry);
-        *contact = e_contact_from_gdata_entry (new_entry);
-        g_hash_table_insert (priv->gdata_entries, g_strdup (uid), new_entry);
-    }
+    contact = e_contact_new_from_vcard (vcard_str);
+    google_book_add_contact (priv->book, contact, out_contact, &error);
+    g_object_unref (contact);
     if (error) {
-        status = ebookbackend_status_from_soup_error (error->code);
-        __debug__ ("Creating contact failed (HTTP %d): %s", error->code, error->message);
+        status = e_book_backend_status_from_google_book_error (error->code);
+        __debug__ ("Creating contact failed: %s", error->message);
         g_clear_error (&error);
-    }
-
-    if (NULL == *contact) {
+        *out_contact = NULL;
         return status;
     }
+
     return GNOME_Evolution_Addressbook_Success;
 }
 
@@ -161,30 +106,19 @@
         return GNOME_Evolution_Addressbook_OfflineUnavailable;
     }
 
-    if (FALSE == e_book_backend_google_ensure_auth (E_BOOK_BACKEND_GOOGLE (backend))) {
-        return GNOME_Evolution_Addressbook_AuthenticationRequired;
-    }
-
     for (id_iter = id_list; id_iter; id_iter = id_iter->next) {
-        GDataEntry *entry;
         GError *error = NULL;
-        char *uid;
+        const char *uid;
 
         uid = id_iter->data;
-        entry = g_hash_table_lookup (priv->gdata_entries, uid);
-        if (NULL == entry) {
-            continue;
-        }
-
-        if (gdata_service_delete_entry (GDATA_SERVICE (priv->service), entry, &error)) {
-            g_hash_table_remove (priv->gdata_entries, uid);
-            *ids = g_list_append (*ids, g_strdup (uid));
-        }
+        google_book_remove_contact (priv->book, uid, &error);
         if (error) {
             /* Only last error will be reported */
-            status = ebookbackend_status_from_soup_error (error->code);
-            __debug__ ("Deleting contact %s failed (HTTP %d): %s", uid, error->code, error->message);
+            status = e_book_backend_status_from_google_book_error (error->code);
+            __debug__ ("Deleting contact %s failed: %s", uid, error->message);
             g_clear_error (&error);
+        } else {
+            *ids = g_list_append (*ids, g_strdup (uid));
         }
     }
 
@@ -199,73 +133,34 @@
                                       EDataBook        *book,
                                       guint32           opid,
                                       const char       *vcard_str,
-                                      EContact        **contact)
+                                      EContact        **out_contact)
 {
     EBookBackendGooglePrivate *priv;
     EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
-    EVCardAttribute *uid_attr;
-    EVCard *vcard;
-    GDataEntry *entry, *copy, *new_entry;
+    EContact *contact;
     GError *error = NULL;
-    char *uid, *xml;
 
     __debug__ (G_STRFUNC);
     priv = GET_PRIVATE (backend);
 
     __debug__ ("Updating: %s", vcard_str);
-    *contact = NULL;
+    *out_contact = NULL;
 
     if (priv->mode != GNOME_Evolution_Addressbook_MODE_REMOTE) {
         return GNOME_Evolution_Addressbook_OfflineUnavailable;
     }
 
-    if (FALSE == e_book_backend_google_ensure_auth (E_BOOK_BACKEND_GOOGLE (backend))) {
-        return GNOME_Evolution_Addressbook_AuthenticationRequired;
-    }
-
-    vcard = e_vcard_new_from_string (vcard_str);
-    if (NULL == vcard)
-        return GNOME_Evolution_Addressbook_OtherError;
-
-    uid_attr = e_vcard_get_attribute (vcard, EVC_UID);
-    if (NULL == uid_attr)
-        return GNOME_Evolution_Addressbook_ContactNotFound;
-
-    uid = e_vcard_attribute_get_value (uid_attr);
-
-    entry = g_hash_table_lookup (priv->gdata_entries, uid ? uid : "");
-
-    if (NULL == entry)
-        return GNOME_Evolution_Addressbook_ContactNotFound;
-
-    xml = gdata_entry_generate_xml (entry);
-    /* We operate on a copy; if we cannot commit our changes we should also
-     * leave the local entry as it was.
-     * There is a warning from libxml2 as result form this, because libgdata
-     * caches the xml from which it was created. However, if it is created
-     * from a feed, this xml will miss all namespace information, thus the warning */
-    copy = gdata_entry_new_from_xml (xml);
-
-    gdata_entry_update_from_e_vcard (copy, vcard);
-    new_entry = gdata_service_update_entry (GDATA_SERVICE (priv->service), copy, &error);
-
-    g_object_unref (copy);
-    g_object_unref (vcard);
-
-    if (new_entry) {
-        *contact = e_contact_from_gdata_entry (new_entry);
-        /* Will unref/free the old entry */
-        g_hash_table_replace (priv->gdata_entries, uid, new_entry);
-    }
+    contact = e_contact_new_from_vcard (vcard_str);
+    google_book_update_contact (priv->book, contact, out_contact, &error);
+    g_object_unref (contact);
     if (error) {
-        status = ebookbackend_status_from_soup_error (error->code);
-        __debug__ ("Updating contact failed (HTTP %d): %s", error->code, error->message);
+        status = e_book_backend_status_from_google_book_error (error->code);
+        __debug__ ("Modifying contact failed: %s", error->message);
         g_clear_error (&error);
-    }
-
-    if (NULL == *contact) {
+        *out_contact = NULL;
         return status;
     }
+
     return GNOME_Evolution_Addressbook_Success;
 }
 
@@ -277,23 +172,22 @@
                                    char            **vcard_str)
 {
     EBookBackendGooglePrivate *priv;
-    GDataEntry *entry;
+    EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
+    EContact *contact;
+    GError *error = NULL;
 
     __debug__ (G_STRFUNC);
     priv = GET_PRIVATE (backend);
 
-    if (FALSE == e_book_backend_google_ensure_auth (E_BOOK_BACKEND_GOOGLE (backend))) {
-        return GNOME_Evolution_Addressbook_AuthenticationRequired;
-    }
-
-    entry = g_hash_table_lookup (priv->gdata_entries, uid);
-
-    if (NULL == entry) {
-        *vcard_str = NULL;
-        return GNOME_Evolution_Addressbook_ContactNotFound;
+    contact = google_book_get_contact (priv->book, uid, &error);
+    if (error) {
+        status = e_book_backend_status_from_google_book_error (error->code);
+        __debug__ ("Getting contact with uid %s failed: %s", uid, error->message);
+        g_clear_error (&error);
+        return status;
     }
-
-    *vcard_str = vcard_from_gdata_entry (GDATA_ENTRY (entry));
+    *vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+    g_object_unref (contact);
 
     return GNOME_Evolution_Addressbook_Success;
 }
@@ -306,129 +200,96 @@
                                         GList           **contacts)
 {
     EBookBackendGooglePrivate *priv;
+    EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_OtherError;
     EBookBackendSExp *sexp;
-    GHashTableIter iter;
-    GDataEntry *entry;
-    char *uid;
+    GError *error = NULL;
+    GList *all_contacts;
 
     __debug__ (G_STRFUNC);
     priv = GET_PRIVATE (backend);
 
     *contacts = NULL;
-    if (FALSE == e_book_backend_google_ensure_auth (E_BOOK_BACKEND_GOOGLE (backend))) {
-        return GNOME_Evolution_Addressbook_AuthenticationRequired;
+
+    all_contacts = google_book_get_all_contacts (priv->book, &error);
+    if (error) {
+        status = e_book_backend_status_from_google_book_error (error->code);
+        __debug__ ("Getting all contacts failed: %s", error->message);
+        g_clear_error (&error);
+        return status;
     }
 
     sexp = e_book_backend_sexp_new (query);
+    while (all_contacts) {
+        EContact *contact;
 
-    g_hash_table_iter_init (&iter, priv->gdata_entries);
-    while (g_hash_table_iter_next (&iter, (gpointer)&uid, (gpointer)&entry)) {
-        char *vcard_str;
-
-        vcard_str = vcard_from_gdata_entry (GDATA_ENTRY (entry));
-        if (vcard_str && e_book_backend_sexp_match_vcard (sexp, vcard_str)) {
+        contact = all_contacts->data;
+        if (e_book_backend_sexp_match_contact (sexp, contact)) {
+            char *vcard_str;
+            vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
             *contacts = g_list_append (*contacts, vcard_str);
-        } else {
-            g_free (vcard_str);
         }
+        g_object_unref (contact);
+        all_contacts = g_list_delete_link (all_contacts, all_contacts);
     }
     g_object_unref (sexp);
 
-
     return GNOME_Evolution_Addressbook_Success;
 }
 
-static gboolean
-refresh_feed (gpointer data)
+static void
+on_google_book_contact_added (GoogleBook *book, EContact *contact, gpointer user_data)
 {
     EBookBackendGooglePrivate *priv;
-    GList *added, *modified, *deleted;
     GList *iter;
 
-    g_return_val_if_fail (E_IS_BOOK_BACKEND_GOOGLE (data), FALSE);
-
-    priv = GET_PRIVATE (data);
-
-    if (NULL == priv->bookviews) {
-        priv->refresh_feed_id = 0;
-
-        return FALSE;
+    priv = GET_PRIVATE (user_data);
+    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);
     }
-    e_book_backend_google_update_query (E_BOOK_BACKEND_GOOGLE (data),
-                                        &added, &modified, &deleted);
+}
 
-    if (FALSE == (added || modified || deleted))
-        return TRUE;
-
-    modified = g_list_concat (added, modified);
-    while (modified) {
-        char *vcard_str;
-
-        vcard_str = vcard_from_gdata_entry (GDATA_ENTRY (modified->data));
-        __debug__ ("Update or new entry: %s", vcard_str);
-        if (vcard_str) {
-            for (iter = priv->bookviews; iter; iter = iter->next) {
-                e_data_book_view_notify_update_vcard (iter->data, g_strdup (vcard_str));
-            }
-            g_free (vcard_str);
-        }
+static void
+on_google_book_contact_removed (GoogleBook *book, const char *uid, gpointer user_data)
+{
+    EBookBackendGooglePrivate *priv;
+    GList *iter;
 
-        g_object_unref (modified->data);
-        modified = g_list_delete_link (modified, modified);
+    priv = GET_PRIVATE (user_data);
+    for (iter = priv->bookviews; iter; iter = iter->next) {
+        e_data_book_view_notify_remove (E_DATA_BOOK_VIEW (iter->data), g_strdup (uid));
     }
+}
 
-    while (deleted) {
-        const char *uid;
-
-        uid = gdata_entry_get_id (GDATA_ENTRY (deleted->data));
-        __debug__ ("Deleted entry: %s", uid);
-        if (uid) {
-            for (iter = priv->bookviews; iter; iter = iter->next) {
-                e_data_book_view_notify_remove (iter->data, uid);
-            }
-        }
-
-        g_object_unref (deleted->data);
-        deleted = g_list_delete_link (deleted, deleted);
-    }
+static void
+on_google_book_contact_changed (GoogleBook *book, EContact *contact, gpointer user_data)
+{
+    EBookBackendGooglePrivate *priv;
+    GList *iter;
 
+    priv = GET_PRIVATE (user_data);
     for (iter = priv->bookviews; iter; iter = iter->next) {
-        e_data_book_view_notify_complete (iter->data, GNOME_Evolution_Addressbook_Success);
+        g_object_ref (contact);
+        e_data_book_view_notify_update (E_DATA_BOOK_VIEW (iter->data), contact);
     }
-
-    return TRUE;
 }
 
 static void
-do_initial_bookview_population (EBookBackendGoogle *backend,
-                                EDataBookView      *bookview)
+on_google_book_sequence_complete (GoogleBook *book, GError *error, gpointer user_data)
 {
     EBookBackendGooglePrivate *priv;
-    GHashTableIter iter;
-    GDataEntry *entry;
-    char *uid;
-
-    priv = GET_PRIVATE (backend);
-
-    if (NULL == priv->gdata_entries)
-        return;
-
-    g_hash_table_iter_init (&iter, priv->gdata_entries);
-    while (g_hash_table_iter_next (&iter, (gpointer)&uid, (gpointer)&entry)) {
-        char *vcard_str;
-
-        vcard_str = vcard_from_gdata_entry (GDATA_ENTRY (entry));
-        //__debug__ ("%s", vcard_str);
-        e_data_book_view_notify_update_vcard (bookview, vcard_str);
+    EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_Success;
+    GList *iter;
 
+    priv = GET_PRIVATE (user_data);
+    if (error) {
+        status = e_book_backend_status_from_google_book_error (error->code);
+        __debug__ ("Book-view query failed: %s", error->message);
+        status = e_book_backend_status_from_google_book_error (error->code);
+        g_clear_error (&error);
     }
-
-    e_data_book_view_notify_complete (bookview, GNOME_Evolution_Addressbook_Success);
-
-    if ((0 == priv->refresh_feed_id) && priv->refresh_interval) {
-        priv->refresh_feed_id = g_timeout_add_seconds (priv->refresh_interval,
-                                                       refresh_feed,
-                                                       backend);
+    for (iter = priv->bookviews; iter; iter = iter->next) {
+        e_data_book_view_notify_complete (E_DATA_BOOK_VIEW (iter->data), GNOME_Evolution_Addressbook_Success);
     }
 }
 
@@ -437,7 +298,7 @@
                                        EDataBookView *bookview)
 {
     EBookBackendGooglePrivate *priv;
-    gboolean have_auth;
+    GList *cached_contacts;
 
     g_return_if_fail (E_IS_BOOK_BACKEND_GOOGLE (backend));
     g_return_if_fail (E_IS_DATA_BOOK_VIEW (bookview));
@@ -450,14 +311,16 @@
     bonobo_object_ref (bookview);
     e_data_book_view_notify_status_message (bookview, "Loading...");
 
-    have_auth = e_book_backend_google_ensure_auth (E_BOOK_BACKEND_GOOGLE (backend));
-    if (have_auth) {
-        do_initial_bookview_population (E_BOOK_BACKEND_GOOGLE (backend),
-                                        bookview);
-    } else {
-        priv->pending_auth_bookviews = g_list_append (priv->pending_auth_bookviews,
-                                                      bookview);
+    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;
+
+        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
@@ -470,169 +333,14 @@
     priv = GET_PRIVATE (backend);
 
     priv->bookviews = g_list_remove (priv->bookviews, bookview);
-    priv->pending_auth_bookviews = g_list_remove (priv->pending_auth_bookviews, bookview);
     bonobo_object_unref (bookview);
 
-    if ((NULL == priv->bookviews) && priv->refresh_feed_id) {
-        g_source_remove (priv->refresh_feed_id);
-        priv->refresh_feed_id = 0;
-    }
-}
-
-static EBookBackendSyncStatus
-e_book_backend_google_update_query  (EBookBackendGoogle *backend,
-                                     GList             **added,
-                                     GList             **modified,
-                                     GList             **deleted)
-{
-    EBookBackendGooglePrivate *priv;
-    GDataFeed *feed;
-    GSList *iter, *entries;
-    GError *error = NULL;
-    char *uri, *updated_min = NULL;
-
-    __debug__ (G_STRFUNC);
-    priv = GET_PRIVATE (backend);
-
-    *added = *modified = *deleted = NULL;
-
-    if (NULL == priv->service ||
-        NULL == priv->base_uri) {
-        return GNOME_Evolution_Addressbook_AuthenticationFailed;
-    }
-
-    if (priv->feed_last_updated) {
-        updated_min = g_strdup_printf ("updated-min=%s", priv->feed_last_updated);
-    }
-
-    uri = build_uri (priv->base_uri, "max-results=100", "showdeleted=true", updated_min,  NULL);
-    __debug__ ("URI is '%s'", uri);
-    feed = gdata_service_get_feed (GDATA_SERVICE (priv->service), uri, &error);
-    g_free (uri);
-
-    if (error) {
-        EBookBackendSyncStatus status;
-
-        __debug__ ("Update query failed (HTTP %d): %s", error->code, error->message);
-        status = ebookbackend_status_from_soup_error (error->code);
-        g_clear_error (&error);
-
-        return status;
-    }
-    if (NULL == feed) {
-        return GNOME_Evolution_Addressbook_OtherError;
-    }
-
-    g_free (priv->feed_last_updated);
-    priv->feed_last_updated = g_strdup (gdata_feed_get_updated (GDATA_FEED (feed)));
-
-    entries = gdata_feed_get_entries (feed);
-    __debug__ ("Update-feed has %d entries", entries ? g_slist_length (entries) : -1);
-
-    for (iter = entries; iter; iter = iter->next) {
-        GDataEntry *entry, *old_entry;
-        const char *edit_link, *old_edit_link;
-        gboolean is_deleted;
-        const char *uid;
-
-        entry = GDATA_ENTRY (iter->data);
-        uid = gdata_entry_get_id (entry);
-        is_deleted = gdata_entry_is_deleted (entry);
-
-        old_entry = g_hash_table_lookup (priv->gdata_entries, uid);
-        if (is_deleted) {
-            /* Do we have this item in our list? */
-            if (NULL == old_entry) {
-                continue;
-            } else {
-                *deleted = g_list_append (*deleted, g_object_ref (entry));
-                g_hash_table_remove (priv->gdata_entries, uid);
-            }
-        } else {
-            /* Is this a new entry */
-            if (NULL == old_entry) {
-                *added = g_list_append (*added, g_object_ref (entry));
-                g_hash_table_insert (priv->gdata_entries,
-                                     g_strdup (uid),
-                                     g_object_ref (entry));
-            }
-        }
-
-        edit_link = gdata_entry_get_edit_link (entry);
-        old_edit_link = gdata_entry_get_edit_link (old_entry);
-        if (0 == strcmp (edit_link ? edit_link : "", old_edit_link ? old_edit_link : ""))
-            continue;
-
-        g_hash_table_replace (priv->gdata_entries, g_strdup (uid), g_object_ref (entry));
-        *modified = g_list_append (*modified, g_object_ref (entry));
+    if (NULL == priv->bookviews) {
+        google_book_set_live_mode (priv->book, FALSE);
     }
-    g_object_unref (feed);
-
-    return GNOME_Evolution_Addressbook_Success;
 }
 
 static EBookBackendSyncStatus
-e_book_backend_google_initial_query (EBookBackendGoogle *backend)
-{
-    EBookBackendGooglePrivate *priv;
-    GDataFeed *feed;
-    GSList *iter, *entries;
-    GError *error = NULL;
-    char *uri;
-
-    __debug__ (G_STRFUNC);
-    priv = GET_PRIVATE (backend);
-
-    if (NULL == priv->service ||
-        NULL == priv->base_uri) {
-        return GNOME_Evolution_Addressbook_AuthenticationFailed;
-    }
-
-    uri = build_uri (priv->base_uri, "max-results=100", NULL);
-    __debug__ ("URI is '%s'", uri);
-    feed = gdata_service_get_feed (GDATA_SERVICE (priv->service), uri, &error);
-    g_free (uri);
-
-    if (error) {
-        EBookBackendSyncStatus status;
-
-        __debug__ ("Initial query failed (HTTP %d): %s", error->code, error->message);
-        status = ebookbackend_status_from_soup_error (error->code);
-        g_clear_error (&error);
-
-        return status;
-    }
-    if (NULL == feed) {
-        return GNOME_Evolution_Addressbook_OtherError;
-    }
-
-    g_free (priv->feed_last_updated);
-    priv->feed_last_updated = g_strdup (gdata_feed_get_updated (GDATA_FEED (feed)));
-    if (priv->gdata_entries) {
-        g_hash_table_destroy (priv->gdata_entries);
-    }
-    priv->gdata_entries = g_hash_table_new_full (g_str_hash,
-                                                 g_str_equal,
-                                                 g_free,
-                                                 g_object_unref);
-
-    entries = gdata_feed_get_entries (feed);
-    __debug__ ("Feed has %d entries", entries ? g_slist_length (entries) : -1);
-    for (iter = entries; iter; iter = iter->next) {
-        GDataEntry *entry;
-        const char* uid;
-
-        entry = GDATA_ENTRY (iter->data);
-        uid = gdata_entry_get_id (entry);
-        g_hash_table_insert (priv->gdata_entries, g_strdup (uid), g_object_ref (entry));
-    }
-    g_object_unref (feed);
-
-    return GNOME_Evolution_Addressbook_Success;
-}
-
-
-static EBookBackendSyncStatus
 e_book_backend_google_authenticate_user (EBookBackendSync *backend,
                                          EDataBook        *book,
                                          guint32           opid,
@@ -641,46 +349,41 @@
                                          const char       *auth_method)
 {
     EBookBackendGooglePrivate *priv;
-    EBookBackendSyncStatus status;
+    EBookBackendSyncStatus status = GNOME_Evolution_Addressbook_Success;
+    GError *error = NULL;
+    char *book_username;
+    gboolean match;
 
     __debug__ (G_STRFUNC);
     priv = GET_PRIVATE (backend);
 
     if (priv->mode != GNOME_Evolution_Addressbook_MODE_REMOTE) {
-        g_warning ("Offline mode not mplemented...");
-        return GNOME_Evolution_Addressbook_OfflineUnavailable;
-    }
-    if (NULL == priv->service) {
-        g_warning ("Book not open; could not authenticate");
-        return GNOME_Evolution_Addressbook_RepositoryOffline;
+        return GNOME_Evolution_Addressbook_Success;
     }
 
-    g_free (priv->base_uri);
-    priv->base_uri = NULL;
-
-    if (NULL == username || username[0] == 0 ||
-        NULL == password || password[0] == 0) {
+    if (NULL == username || username[0] == 0) {
         return GNOME_Evolution_Addressbook_AuthenticationFailed;
     }
 
-    priv->base_uri = build_base_uri (username);
-    gdata_service_set_credentials (GDATA_SERVICE (priv->service), username, password);
-
-    status = e_book_backend_google_initial_query (E_BOOK_BACKEND_GOOGLE (backend));
-    if (status == GNOME_Evolution_Addressbook_Success) {
-        GList *iter;
-
-        priv->authorized = TRUE;
-        e_book_backend_notify_writable (E_BOOK_BACKEND (backend), TRUE);
+    g_object_get (G_OBJECT (priv->book),
+                  "username", &book_username,
+                  NULL);
+
+    match = (0 == strcmp (username, book_username));
+    g_free (book_username);
+    if (FALSE == match) {
+        g_warning ("Username given when loading source and on authentication did not match!");
+        return GNOME_Evolution_Addressbook_OtherError;
+    }
 
-        for (iter = priv->pending_auth_bookviews; iter; iter = iter->next) {
-            do_initial_bookview_population (E_BOOK_BACKEND_GOOGLE (backend),
-                                            iter->data);
-        }
+    google_book_connect_to_google (priv->book, password, &error);
+    if (error) {
+        status = e_book_backend_status_from_google_book_error (error->code);
+        __debug__ ("Authentication failed: %s", error->message);
+        status = e_book_backend_status_from_google_book_error (error->code);
+        g_clear_error (&error);
     } else {
-        g_free (priv->base_uri);
-        priv->base_uri = NULL;
-        priv->authorized = FALSE;
+        e_book_backend_notify_writable (E_BOOK_BACKEND (backend), TRUE);
     }
 
     return status;
@@ -778,7 +481,14 @@
                               guint32           opid)
 {
     __debug__ (G_STRFUNC);
-    return GNOME_Evolution_Addressbook_PermissionDenied;
+    return GNOME_Evolution_Addressbook_Success;
+}
+
+static void
+on_google_book_auth_required (GoogleBook *book, gpointer user_data)
+{
+    __debug__ (G_STRFUNC);
+    e_book_backend_notify_auth_required (E_BOOK_BACKEND (user_data));
 }
 
 static GNOME_Evolution_Addressbook_CallStatus
@@ -787,43 +497,68 @@
                                    gboolean      only_if_exists)
 {
     EBookBackendGooglePrivate *priv = GET_PRIVATE (backend);
-    const char *refresh_interval;
+    const char *refresh_interval_str, *use_ssl_str, *use_cache_str;
+    guint refresh_interval;
+    gboolean use_ssl, use_cache;
+    const char *username;
 
-    if (priv->service) {
+    if (priv->book) {
         g_warning ("Source already loaded!");
         return GNOME_Evolution_Addressbook_OtherError;
     }
 
-    refresh_interval = e_source_get_property (source, "refresh-interval");
-    if (refresh_interval) {
-        guint val;
+    username = e_source_get_property (source, "username");
+
+    if (NULL == username || username[0] == '\0') {
+        g_warning ("No or empty username!");
+        return GNOME_Evolution_Addressbook_OtherError;
+    }
 
-        if (1 == sscanf (refresh_interval, "%u", &val)) {
-            priv->refresh_interval = val;
+    refresh_interval_str = e_source_get_property (source, "refresh-interval");
+    use_ssl_str = e_source_get_property (source, "ssl");
+    use_cache_str = e_source_get_property (source, "offline_sync");
+
+    if (refresh_interval_str) {
+        if (1 != sscanf (refresh_interval_str, "%u", &refresh_interval)) {
+            g_warning ("Could not parse refresh-interval!");
+            refresh_interval = 3600;
         }
     }
 
-    __debug__ (G_STRFUNC);
-    if (priv->mode == GNOME_Evolution_Addressbook_MODE_REMOTE) {
-        gboolean status;
+    use_ssl = TRUE;
+    if (use_ssl_str) {
+        if (0 == g_ascii_strcasecmp (use_ssl_str, "false") || 0 == strcmp (use_ssl_str, "0"))
+            use_ssl = FALSE;
+    }
 
-        status = test_repository_availability ();
-        if (FALSE == status) {
-            e_book_backend_notify_connection_status (backend, FALSE);
+    use_cache = TRUE;
+    if (use_cache_str) {
+        if (0 == g_ascii_strcasecmp (use_cache_str, "false") || 0 == strcmp (use_cache_str, "0"))
+            use_cache = FALSE;
+    }
 
-            return GNOME_Evolution_Addressbook_RepositoryOffline;
-        }
+    priv->book = google_book_new (username, use_cache);
 
-        priv->service = gdata_google_service_new ("cp", "evolution-client-0.0.1");
+    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);
 
-        e_book_backend_set_is_loaded (backend, TRUE);
-        e_book_backend_notify_connection_status (backend, TRUE);
+    __debug__ (G_STRFUNC);
 
-        return GNOME_Evolution_Addressbook_Success;
-    } else {
-        g_warning ("Offline mode not yet implemented...");
-        return GNOME_Evolution_Addressbook_OfflineUnavailable;
-    }
+    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));
+
+    return GNOME_Evolution_Addressbook_Success;
 }
 
 static char *
@@ -850,29 +585,17 @@
     if (mode == priv->mode) {
         return;
     }
-    priv->mode = mode;
 
-    if (mode == GNOME_Evolution_Addressbook_MODE_REMOTE) {
-        if (e_book_backend_is_loaded (backend)) {
-            gboolean status;
+    priv->mode = mode;
 
-            status = test_repository_availability ();
+    if (NULL == priv->book) {
+        return;
+    }
 
-            if (FALSE == status) {
-                e_book_backend_notify_writable (backend, FALSE);
-                e_book_backend_notify_connection_status (backend, FALSE);
-            } else {
-                status = e_book_backend_google_ensure_auth (E_BOOK_BACKEND_GOOGLE (backend));
-                e_book_backend_notify_writable (backend, status);
-                e_book_backend_notify_connection_status (backend, TRUE);
-            }
-        } else {
-            e_book_backend_set_is_writable (backend, FALSE);
-        }
+    if (mode == GNOME_Evolution_Addressbook_MODE_REMOTE) {
+        google_book_set_offline_mode (priv->book, FALSE);
     } else {
-        e_book_backend_notify_writable (backend, FALSE);
-        e_book_backend_notify_connection_status (backend, FALSE);
-        g_warning ("Offline mode not implemented yet...");
+        google_book_set_offline_mode (priv->book, TRUE);
     }
 }
 
@@ -883,24 +606,14 @@
 
     __debug__ (G_STRFUNC);
 
-    if (priv->refresh_feed_id) {
-        g_source_remove (priv->refresh_feed_id);
-        priv->refresh_feed_id = 0;
-    }
-
-    while (priv->pending_auth_bookviews) {
-        priv->pending_auth_bookviews =
-                g_list_delete_link (priv->pending_auth_bookviews,
-                                    priv->pending_auth_bookviews);
-    }
     while (priv->bookviews) {
         bonobo_object_unref (priv->bookviews->data);
         priv->bookviews = g_list_delete_link (priv->bookviews,
                                               priv->bookviews);
     }
-    if (priv->service) {
-        g_object_unref (priv->service);
-        priv->service = NULL;
+    if (priv->book) {
+        g_object_unref (priv->book);
+        priv->book = NULL;
     }
 
     G_OBJECT_CLASS (e_book_backend_google_parent_class)->dispose (object);
@@ -909,15 +622,10 @@
 static void
 e_book_backend_google_finalize (GObject *object)
 {
-    EBookBackendGooglePrivate *priv = GET_PRIVATE (object);
+    //EBookBackendGooglePrivate *priv = GET_PRIVATE (object);
 
     __debug__ (G_STRFUNC);
 
-    g_free (priv->base_uri);
-    g_free (priv->feed_last_updated);
-    if (priv->gdata_entries) {
-        g_hash_table_destroy (priv->gdata_entries);
-    }
     G_OBJECT_CLASS (e_book_backend_google_parent_class)->finalize (object);
 }
 
@@ -975,18 +683,8 @@
 }
 
 static EBookBackendSyncStatus
-ebookbackend_status_from_soup_error (int http_error)
+e_book_backend_status_from_google_book_error (GoogleBookError error_code)
 {
-    if (http_error < 200) {
-        return GNOME_Evolution_Addressbook_RepositoryOffline;
-    } else
-    if (http_error == 401) {
-        return GNOME_Evolution_Addressbook_AuthenticationRequired;
-    } else
-    if (http_error == 403) {
-        return GNOME_Evolution_Addressbook_AuthenticationFailed;
-    } else {
-        return GNOME_Evolution_Addressbook_OtherError;
-    }
+    return GNOME_Evolution_Addressbook_OtherError;
 }
 

Added: trunk/addressbook/backends/google/google-book.c
==============================================================================
--- (empty file)
+++ trunk/addressbook/backends/google/google-book.c	Wed Aug 13 19:51:47 2008
@@ -0,0 +1,1197 @@
+/* goggle-book.c - 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>
+ */
+
+#include <string.h>
+#include <libedata-book/e-book-backend-cache.h>
+#include <gdata-service-iface.h>
+#include <gdata-google-service.h>
+
+#include "util.h"
+#include "google-book.h"
+
+G_DEFINE_TYPE (GoogleBook, google_book, G_TYPE_OBJECT)
+
+#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
+{
+    char *username;
+    CacheType cache_type;
+    union {
+        EBookBackendCache *on_disk;
+        struct {
+            GHashTable *contacts;
+            GHashTable *gdata_entries;
+            GTimeVal last_updated;
+        } in_memory;
+    } cache;
+
+    gboolean offline;
+    GDataService *service;
+    guint refresh_interval;
+    char *base_uri;
+
+    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,
+                                        int         chunk_size,
+                                        GError    **error);
+
+static void
+google_book_error_from_soup_error      (GError *soup_error,
+                                        GError **error,
+                                        const char *message);
+
+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 char *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:
+        break;
+    }
+    return NULL;
+}
+
+static gboolean
+google_book_cache_remove_contact (GoogleBook *book, const char *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:
+    break;
+    }
+    return FALSE;
+}
+
+static EContact*
+google_book_cache_get_contact (GoogleBook *book, const char *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 char *entry_xml;
+                entry_xml = _e_contact_get_gdata_entry_xml (contact);
+                *entry = gdata_entry_new_from_xml (entry_xml);
+            }
+            _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:
+        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:
+        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 char*
+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:
+        break;
+    }
+    return NULL;
+}
+
+static gboolean
+google_book_cache_get_last_update_tv (GoogleBook *book, GTimeVal *tv)
+{
+    GoogleBookPrivate *priv = GET_PRIVATE (book);
+    char *last_update;
+    int 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:
+        break;
+    }
+    return FALSE;
+}
+
+static void
+google_book_cache_set_last_update (GoogleBook *book, GTimeVal *tv)
+{
+    GoogleBookPrivate *priv = GET_PRIVATE (book);
+    char *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:
+        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 (FALSE == 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;
+    int rv = TRUE;
+    gboolean install_timeout;
+
+    __debug__ (G_STRFUNC);
+
+    if (priv->offline || NULL == 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:
+        break;
+    }
+    priv->cache_type = NO_CACHE;
+}
+
+static void
+google_book_construct_base_uri (GoogleBook *book, gboolean use_ssl)
+{
+    const char *format = "%swww.google.com/m8/feeds/contacts/%s/base";
+    char *esc_username;
+    GoogleBookPrivate *priv = GET_PRIVATE (book);
+
+    __debug__ (G_STRFUNC);
+    if (priv->base_uri) {
+        g_free (priv->base_uri);
+    }
+
+    esc_username = g_uri_escape_string (priv->username, NULL, FALSE);
+    priv->base_uri = g_strdup_printf (format, use_ssl ? "https://"; : "http://";, esc_username);
+    g_free (esc_username);
+
+    g_debug ("base_uri is now %s", priv->base_uri);
+}
+
+static void
+google_book_get_property (GObject *object, guint property_id,
+                          GValue *value,   GParamSpec *pspec)
+{
+    GoogleBookPrivate *priv = GET_PRIVATE (object);
+    gboolean use_ssl = FALSE;
+
+    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:
+        if (priv->base_uri) {
+            if (strstr (priv->base_uri, "https://";)) {
+                use_ssl = TRUE;
+            }
+        }
+        g_value_set_boolean (value, 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);
+        /* FIXME - actually apply this */
+        break;
+    case PROP_USE_SSL:
+        google_book_construct_base_uri (GOOGLE_BOOK (object), 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;
+    }
+    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->base_uri);
+    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;
+
+    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;
+
+    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 char *uid)
+{
+    GoogleBookPrivate *priv;
+
+    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;
+
+    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)
+{
+    GoogleBookPrivate *priv;
+
+    priv = GET_PRIVATE (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",
+                                                           "UseCache",
+                                                           "Whether a 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",
+                                                        "RefreshInterval",
+                                                        "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",
+                                                           "UseSSL",
+                                                           "Whether SSL should be used or not",
+                                                           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 char *username, gboolean use_cache)
+{
+    return g_object_new (TYPE_GOOGLE_BOOK,
+                         "username", username,
+                         "use-cache", use_cache,
+                         "use-ssl", TRUE,
+                         "refresh-interval", 3600,
+                         NULL);
+}
+
+gboolean
+google_book_connect_to_google (GoogleBook *book, const char *password, GError **error)
+{
+    GoogleBookPrivate *priv;
+    GDataService *service;
+    GError *soup_error = NULL;
+
+    __debug__ (G_STRFUNC);
+    g_return_val_if_fail (IS_GOOGLE_BOOK (book), FALSE);
+    g_return_val_if_fail (NULL != password,      FALSE);
+
+    priv = GET_PRIVATE (book);
+
+    if (priv->service) {
+        g_warning ("Connection to google already established.");
+        return TRUE;
+    }
+
+    service = (GDataService*)gdata_google_service_new ("cp", "evolution-client-0.0.1");
+    gdata_service_set_credentials (GDATA_SERVICE (service), priv->username, password);
+    gdata_google_service_authenticate (GDATA_GOOGLE_SERVICE (service), &soup_error);
+
+    if (soup_error) {
+        google_book_error_from_soup_error (soup_error, error,
+                                           "Connecting to google failed");
+        priv->service = NULL;
+        return FALSE;
+    }
+    priv->service = service;
+
+    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 && priv->service) {
+        g_object_unref (priv->service);
+        priv->service = NULL;
+    }
+    if (offline == FALSE) {
+        if (priv->service) {
+            google_book_cache_refresh_if_needed (book, NULL);
+        } else {
+            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;
+    GError *soup_error = NULL;
+
+    *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);
+    __debug__ ("new entry with xml: %s", gdata_entry_generate_xml (entry));
+    new_entry = gdata_service_insert_entry (GDATA_SERVICE (priv->service),
+                                            priv->base_uri, entry, &soup_error);
+    g_object_unref (entry);
+    if (soup_error) {
+        google_book_error_from_soup_error (soup_error, error,
+                                           "Adding entry failed");
+        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;
+    GError *soup_error = NULL;
+    EContact *cached_contact;
+    const char *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 (NULL == cached_contact) {
+        g_set_error (error,
+                    GOOGLE_BOOK_ERROR,
+                    GOOGLE_BOOK_ERROR_CONTACT_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);
+
+    new_entry = gdata_service_update_entry (GDATA_SERVICE (priv->service), entry, &soup_error);
+    g_object_unref (entry);
+
+    if (soup_error) {
+        google_book_error_from_soup_error (soup_error, error,
+                                           "Updating entry failed");
+        return FALSE;
+    }
+
+    *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 char *uid, GError **error)
+{
+    GoogleBookPrivate *priv;
+    GDataEntry *entry = NULL;
+    GError *soup_error = NULL;
+    EContact *cached_contact;
+
+    __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 (NULL == cached_contact) {
+        g_set_error (error,
+                    GOOGLE_BOOK_ERROR,
+                    GOOGLE_BOOK_ERROR_CONTACT_NOT_FOUND,
+                    "Contact with uid %s not found in cache.", uid);
+        return FALSE;
+    }
+
+    google_book_cache_remove_contact (book, uid);
+    gdata_service_delete_entry (GDATA_SERVICE (priv->service), entry, &soup_error);
+    g_object_unref (entry);
+    g_object_unref (cached_contact);
+
+    if (soup_error) {
+        google_book_error_from_soup_error (soup_error, error,
+                                           "Removing entry failed");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void
+process_subsequent_entry (gpointer list_data, gpointer user_data)
+{
+    GoogleBookPrivate *priv;
+    GoogleBook *book = user_data;
+    GDataEntry *entry;
+    EContact *cached_contact;
+    gboolean is_deleted;
+    const char *uid;
+
+    __debug__ (G_STRFUNC);
+    priv = GET_PRIVATE (book);
+    entry = GDATA_ENTRY (list_data);
+    uid = gdata_entry_get_id (entry);
+    is_deleted = gdata_entry_is_deleted (entry);
+
+    cached_contact = google_book_cache_get_contact (book, uid, NULL);
+    if (is_deleted) {
+        /* Do we have this item in our cache? */
+        if (NULL != cached_contact) {
+            google_book_cache_remove_contact (book, uid);
+            google_book_emit_contact_removed (book, uid);
+        }
+    } else {
+        EContact *contact;
+
+        contact = google_book_cache_add_contact (book, entry);
+
+        if (cached_contact) {
+            google_book_emit_contact_changed (book, contact);
+        } else {
+            google_book_emit_contact_added (book, contact);
+        }
+        g_object_unref (contact);
+    }
+    if (cached_contact) {
+        g_object_unref (cached_contact);
+    }
+}
+
+static void
+process_initial_entry (gpointer list_data, gpointer user_data)
+{
+    GoogleBookPrivate *priv;
+    GoogleBook *book = user_data;
+    GDataEntry *entry;
+    const char* uid;
+    EContact *contact;
+
+    __debug__ (G_STRFUNC);
+    priv = GET_PRIVATE (book);
+    entry = GDATA_ENTRY (list_data);
+    uid = gdata_entry_get_id (entry);
+
+    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,
+                                        int         chunk_size,
+                                        GError    **error)
+{
+    GoogleBookPrivate *priv;
+    int start_index = 1;
+    char *last_updated;
+    GError *our_error = NULL;
+    gboolean rv = TRUE;
+
+    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);
+
+    while (start_index > 0) {
+        GDataFeed *feed;
+        GSList *entries;
+        GString *uri;
+        int results;
+        GError *soup_error = NULL;
+
+        uri = g_string_new (priv->base_uri);
+        g_string_append_printf (uri, "?max-results=%d&start-index=%d",
+                                chunk_size, start_index);
+        if (last_updated) {
+            g_string_append_printf (uri, "&updated-min=%s&showdeleted=true",
+                                    last_updated);
+        }
+
+        __debug__ ("URI is '%s'", uri->str);
+        feed = gdata_service_get_feed (priv->service, uri->str, &soup_error);
+        g_string_free (uri, TRUE);
+
+        if (soup_error) {
+            google_book_error_from_soup_error (soup_error, &our_error,
+                                               "Downloading feed failed");
+            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_slist_length (entries) : 0;
+        __debug__ ("Feed has %d entries", results);
+
+        if (last_updated) {
+            g_slist_foreach (entries, process_subsequent_entry, book);
+        } else {
+            g_slist_foreach (entries, process_initial_entry, book);
+        }
+
+        if (results == chunk_size) {
+            start_index += results;
+        } else {
+            GTimeVal current_time;
+
+            start_index = -1;
+            g_get_current_time (&current_time);
+            google_book_cache_set_last_update (book, &current_time);
+            google_book_emit_sequence_complete (book, NULL);
+        }
+        g_object_unref (feed);
+    }
+out:
+    g_free (last_updated);
+    google_book_cache_thaw (book);
+
+    return rv;
+}
+
+
+EContact*
+google_book_get_contact (GoogleBook *book, const char* uid, GError **error)
+{
+    GoogleBookPrivate *priv;
+    EContact *contact;
+
+    priv = GET_PRIVATE (book);
+
+    __debug__ (G_STRFUNC);
+    g_return_val_if_fail (IS_GOOGLE_BOOK (book), FALSE);
+
+    google_book_cache_refresh_if_needed (book, error);
+
+    contact = google_book_cache_get_contact (book, uid, NULL);
+
+    if (contact) {
+        if (*error) {
+            /* We found the contact, so forget about errors during refresh */
+            g_clear_error (error);
+        }
+        return contact;
+    } else {
+        if (NULL == *error) {
+            g_set_error (error,
+                        GOOGLE_BOOK_ERROR,
+                        GOOGLE_BOOK_ERROR_CONTACT_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;
+
+    priv = GET_PRIVATE (book);
+
+    __debug__ (G_STRFUNC);
+    g_return_val_if_fail (IS_GOOGLE_BOOK (book), FALSE);
+
+    google_book_cache_refresh_if_needed (book, error);
+
+    contacts = google_book_cache_get_contacts (book);
+
+    if (contacts) {
+        if (*error) {
+            /* We found the contact, so forget about errors during refresh */
+            g_clear_error (error);
+        }
+        return contacts;
+    }
+    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), FALSE);
+
+    priv->live_mode = TRUE;
+
+    need_update = google_book_cache_needs_update (book, NULL);
+
+    if (need_update) {
+        if (NULL == 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);
+    }
+}
+
+static void
+google_book_error_from_soup_error (GError *soup_error, GError **error, const char *message)
+{
+    GoogleBookError code;
+
+    g_assert (soup_error);
+
+    if (soup_error->code < 100) {
+        code = GOOGLE_BOOK_ERROR_NETWORK_ERROR;
+    } else
+    if (soup_error->code == 200) {
+        code = GOOGLE_BOOK_ERROR_NONE;
+    } else
+    if (soup_error->code == 400) {
+        code = GOOGLE_BOOK_ERROR_INVALID_CONTACT;
+    } else
+    if (soup_error->code == 401) {
+        code = GOOGLE_BOOK_ERROR_AUTH_REQUIRED;
+    } else
+    if (soup_error->code == 403) {
+        code = GOOGLE_BOOK_ERROR_AUTH_FAILED;
+    } else
+    if (soup_error->code == 404) {
+        code = GOOGLE_BOOK_ERROR_CONTACT_NOT_FOUND;
+    } else {
+        code = GOOGLE_BOOK_ERROR_HTTP_ERROR;
+    }
+    g_set_error (error,
+                GOOGLE_BOOK_ERROR,
+                GOOGLE_BOOK_ERROR_HTTP_ERROR,
+                "%s (HTTP %d): %s",
+                message ? message : "Action failed",
+                soup_error->code,
+                soup_error->message);
+    g_clear_error (&soup_error);
+}
+

Added: trunk/addressbook/backends/google/google-book.h
==============================================================================
--- (empty file)
+++ trunk/addressbook/backends/google/google-book.h	Wed Aug 13 19:51:47 2008
@@ -0,0 +1,101 @@
+/* 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;
+typedef enum   _GoogleBookError GoogleBookError;
+
+struct _GoogleBook
+{
+    GObject parent;
+};
+
+struct _GoogleBookClass
+{
+    GObjectClass parent_class;
+
+    void (*contact_added) (EContact* contact);
+    void (*contact_changed) (EContact* contact);
+    void (*contact_removed) (const char *uid);
+    void (*sequence_complete) (GError *error);
+
+    void (*auth_required) (void);
+};
+
+enum _GoogleBookError
+{
+    GOOGLE_BOOK_ERROR_NONE,
+    GOOGLE_BOOK_ERROR_CONTACT_NOT_FOUND,
+    GOOGLE_BOOK_ERROR_INVALID_CONTACT,
+    GOOGLE_BOOK_ERROR_AUTH_FAILED,
+    GOOGLE_BOOK_ERROR_AUTH_REQUIRED,
+    GOOGLE_BOOK_ERROR_NETWORK_ERROR,
+    GOOGLE_BOOK_ERROR_HTTP_ERROR
+};
+
+#define GOOGLE_BOOK_ERROR (g_quark_from_string ("GoogleBookError"))
+
+typedef void (*GoogleBookContactRetrievedCallback) (EContact *contact, gpointer user_data);
+
+GType google_book_get_type (void);
+
+GoogleBook* google_book_new (const char *username, gboolean use_cache);
+
+gboolean google_book_connect_to_google (GoogleBook *book, const char *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 char *uid, GError **error);
+
+EContact *google_book_get_contact                   (GoogleBook *book, const char* 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 */

Modified: trunk/addressbook/backends/google/util.c
==============================================================================
--- trunk/addressbook/backends/google/util.c	(original)
+++ trunk/addressbook/backends/google/util.c	Wed Aug 13 19:51:47 2008
@@ -23,6 +23,8 @@
 #include <libsoup/soup.h>
 #include "util.h"
 
+#define GOOGLE_PRIMARY_PARAM "X-GOOGLE-PRIMARY"
+
 static EVCardAttribute*
 attribute_from_gdata_entry_email_address  (GDataEntryEmailAddress  *email);
 
@@ -37,45 +39,33 @@
 
 static GDataEntryEmailAddress*
 gdata_entry_email_address_from_attribute  (EVCardAttribute         *attr,
-                                           gboolean                 primary);
+                                           gboolean                *primary);
 
 static GDataEntryIMAddress*
 gdata_entry_im_address_from_attribute     (EVCardAttribute         *attr,
-                                           gboolean                 primary);
+                                           gboolean                *primary);
 
 static GDataEntryPhoneNumber*
 gdata_entry_phone_number_from_attribute   (EVCardAttribute         *attr,
-                                           gboolean                 primary);
+                                           gboolean                *primary);
 
 static GDataEntryPostalAddress*
 gdata_entry_postal_address_from_attribute (EVCardAttribute         *attr,
-                                           gboolean                 primary);
+                                           gboolean                *primary);
 
+#if 0
 static GList*
 name_values_from_fullname   (const char *fullname);
 
 static char*
 fullname_from_name_values   (GList      *values);
+#endif
 
 static gboolean
 is_known_google_im_protocol (const char *protocol);
 
-GDataEntry* gdata_entry_create_from_vcard (const char *vcard_str)
-{
-    GDataEntry *entry;
-    EVCard *vcard;
-
-    vcard = e_vcard_new_from_string (vcard_str);
-    if (NULL == vcard)
-        return NULL;
-    entry = gdata_entry_create_from_e_vcard (vcard);
-
-    g_object_unref (vcard);
-
-    return entry;
-}
-
-GDataEntry* gdata_entry_create_from_e_vcard (EVCard *vcard)
+GDataEntry*
+_gdata_entry_new_from_e_contact (EContact *contact)
 {
     GDataEntry *entry;
     GDataEntryCategory *category;
@@ -87,7 +77,7 @@
     category->term = g_strdup ("http://schemas.google.com/contact/2008#contact";); 
     gdata_entry_set_categories (entry, g_slist_append (NULL, category));
 
-    if (gdata_entry_update_from_e_vcard (entry, vcard))
+    if (_gdata_entry_update_from_e_contact (entry, contact))
         return entry;
 
     g_object_unref (entry);
@@ -95,23 +85,9 @@
     return NULL;
 }
 
-gboolean gdata_entry_update_from_vcard (GDataEntry *entry,
-                                        const char *vcard_str)
-{
-    EVCard *vcard;
-
-    vcard = e_vcard_new_from_string (vcard_str);
-    if (NULL == vcard)
-        return FALSE;
-    gdata_entry_update_from_e_vcard (entry, vcard);
-
-    g_object_unref (vcard);
-
-    return TRUE;
-}
-
-gboolean gdata_entry_update_from_e_vcard (GDataEntry *entry,
-                                          EVCard     *vcard)
+gboolean
+_gdata_entry_update_from_e_contact (GDataEntry *entry,
+                                    EContact   *contact)
 {
     GList *attributes, *iter;
     char *fullname = NULL;
@@ -119,45 +95,45 @@
     GSList *im_addresses = NULL;
     GSList *phone_numbers = NULL;
     GSList *postal_addresses = NULL;
-    
-    attributes = e_vcard_get_attributes (vcard);
+    gboolean have_email_primary = FALSE;
+    gboolean have_im_primary = FALSE;
+    gboolean have_phone_primary = FALSE;
+    gboolean have_postal_primary = FALSE;
+
+    attributes = e_vcard_get_attributes (E_VCARD (contact));
+
+    fullname = g_strdup (e_contact_get (contact, E_CONTACT_FULL_NAME));
+    if (NULL == fullname) {
+        EContactName *name = e_contact_get (contact, E_CONTACT_NAME);
 
-    for (iter = attributes; iter; iter = iter->next) {
+        fullname = e_contact_name_to_string (name);
+        e_contact_name_free (name);
+    }
+
+    /* We walk them in reverse order, so we can find
+     * the correct primaries */
+    iter = g_list_last (attributes);
+    for (; iter; iter = iter->prev) {
         EVCardAttribute *attr;
         const char *name;
 
         attr = iter->data;
         name = e_vcard_attribute_get_name (attr);
 
-        /* N */
-        if (0 == strcmp (name, EVC_N)) {
-            GList *values;
-
-            if (fullname) {
-                continue;
-            }
-
-            values = e_vcard_attribute_get_values (attr);
-            fullname = fullname_from_name_values (values);
-        } else
-
-        /* FN */
-        if (0 == strcmp (name, EVC_FN)) {
-            GList *values;
+        /* EMAIL */
 
-            values = e_vcard_attribute_get_values (attr);
-            if (values) {
-                g_free (fullname);
-                fullname = g_strdup (values->data);
-            }
+        /* Ignore UID, VERSION, X-EVOLUTION-FILE-AS, N, FN */
+        if (0 == g_ascii_strcasecmp (name, EVC_UID) ||
+            0 == g_ascii_strcasecmp (name, EVC_N) ||
+            0 == g_ascii_strcasecmp (name, EVC_FN) ||
+            0 == g_ascii_strcasecmp (name, EVC_VERSION) ||
+            0 == g_ascii_strcasecmp (name, EVC_X_FILE_AS)) {
         } else
-
-        /* EMAIL */
-        if (0 == strcmp (name, EVC_EMAIL)) {
+        if (0 == g_ascii_strcasecmp (name, EVC_EMAIL)) {
             GDataEntryEmailAddress *email;
 
             email = gdata_entry_email_address_from_attribute
-                        (attr, email_addresses == NULL);
+                        (attr, &have_email_primary);
             if (email) {
                 email_addresses = g_slist_append (email_addresses,
                                                   email);
@@ -165,11 +141,11 @@
         } else
 
         /* TEL */
-        if (0 == strcmp (name, EVC_TEL)) {
+        if (0 == g_ascii_strcasecmp (name, EVC_TEL)) {
             GDataEntryPhoneNumber *number;
 
             number = gdata_entry_phone_number_from_attribute
-                        (attr, phone_numbers == NULL);
+                        (attr, &have_phone_primary);
             if (number) {
                 phone_numbers = g_slist_append (phone_numbers,
                                                 number);
@@ -177,11 +153,11 @@
         } else
         
         /* LABEL */
-        if (0 == strcmp (name, EVC_LABEL)) {
+        if (0 == g_ascii_strcasecmp (name, EVC_LABEL)) {
             GDataEntryPostalAddress *address;
 
             address = gdata_entry_postal_address_from_attribute
-                        (attr, postal_addresses == NULL);
+                        (attr, &have_postal_primary);
             if (address) {
                 postal_addresses = g_slist_append (postal_addresses,
                                                    address);
@@ -189,12 +165,12 @@
         } else
     
         /* X-IM */
-        if (strncmp (name, "X-", MAX (2, strlen (name))) &&
+        if (g_ascii_strncasecmp (name, "X-", 2) &&
             is_known_google_im_protocol (name + 2)) {
             GDataEntryIMAddress *im;
 
             im = gdata_entry_im_address_from_attribute
-                        (attr, im_addresses == NULL);
+                        (attr, &have_im_primary);
             if (im) {
                 im_addresses = g_slist_append (im_addresses,
                                                im);
@@ -204,7 +180,7 @@
 
             values = e_vcard_attribute_get_values (attr);
             if (values && values->data && ((char*)values->data)[0]) {
-                g_warning ("unsupported vcard field: %s: %s", name, (char*)values->data);
+                __debug__ ("unsupported vcard field: %s: %s", name, (char*)values->data);
             }
         }
     }
@@ -217,8 +193,8 @@
     return TRUE;
 }
 
-EVCard*
-e_vcard_from_gdata_entry (GDataEntry *entry)
+EContact*
+_e_contact_new_from_gdata_entry (GDataEntry *entry)
 {
     EVCard *vcard;
     EVCardAttribute *attr;
@@ -236,7 +212,7 @@
         return NULL;
     }
 
-    vcard = e_vcard_new ();
+    vcard = E_VCARD (e_contact_new ());
 
     /* UID */
     attr = e_vcard_attribute_new (NULL, EVC_UID);
@@ -245,20 +221,7 @@
     /* FN - TODO: get title */
     name = gdata_entry_get_title (entry);
     if (name) {
-        GList *name_values;
-
-        attr = e_vcard_attribute_new (NULL, EVC_FN);
-        e_vcard_add_attribute_with_value (vcard, attr, name);
-
-    /* N */
-        attr = e_vcard_attribute_new (NULL, EVC_N);
-        name_values = name_values_from_fullname (name);
-        while (name_values) {
-            e_vcard_attribute_add_value (attr, name_values->data);
-            g_free (name_values->data);
-            name_values = g_list_delete_link (name_values, name_values);
-        }
-        e_vcard_add_attribute (vcard, attr);
+        e_contact_set (E_CONTACT (vcard), E_CONTACT_FULL_NAME, (const gpointer)name);
     }
 
     /* EMAIL - primary first */
@@ -330,35 +293,10 @@
         }
     }
 
-    return vcard;
-}
-
-EContact*
-e_contact_from_gdata_entry (GDataEntry *entry)
-{
-    EContact *contact;
-    char *vcard_str;
-
-    vcard_str = vcard_from_gdata_entry (entry);
-    contact = e_contact_new_from_vcard (vcard_str);
-    g_free (vcard_str);
-
-    return contact;    
-}
-
-char*
-vcard_from_gdata_entry (GDataEntry *entry)
-{
-    EVCard *vcard;
-    char *vcard_str;
-
-    vcard =  e_vcard_from_gdata_entry (entry);
-    vcard_str = e_vcard_to_string (vcard, EVC_FORMAT_VCARD_30);
-    g_object_unref (vcard);
-
-    return vcard_str;
+    return E_CONTACT (vcard);
 }
 
+#if 0
 static GList*
 name_values_from_fullname (const char *fullname)
 {
@@ -406,7 +344,9 @@
 
     return name_values;
 }
+#endif
 
+#if 0
 static GString*
 string_prepend_with_space (GString *string, const char *val)
 {
@@ -419,7 +359,6 @@
     return string;
 }
 
-#if 0
 static GString*
 string_append_with_space (GString *string, const char *val)
 {
@@ -433,6 +372,7 @@
 }
 #endif
 
+#if 0
 static char*
 fullname_from_name_values (GList *values)
 {
@@ -483,44 +423,42 @@
 out:
     return g_string_free (name, FALSE);
 }
+#endif
+
+#define GDATA_ENTRY_XML_ATTR "X-GDATA-ENTRY-XML"
 
-char* build_uri (const char *base_uri, ...)
+void
+_e_contact_add_gdata_entry_xml (EContact *contact, GDataEntry *entry)
 {
-    va_list params;
-    const char *param;
-    GString *query;
-    const char *separator = "?";
+    EVCardAttribute *attr;
+    const char* entry_xml;
 
-    query = g_string_new (base_uri);
-    va_start (params, base_uri);
-    while (TRUE) {
-        param = va_arg (params, const char*);
-        if (NULL == param) {
-            break;
-        }
-        g_string_append (query, separator);
-        g_string_append (query, param);
-        separator = "&";
-    }
-    va_end (params);
+    entry_xml = gdata_entry_generate_xml (entry);
 
-    return g_string_free (query, FALSE);
+    attr = e_vcard_attribute_new ("", GDATA_ENTRY_XML_ATTR);
+    e_vcard_attribute_add_value (attr, entry_xml);
+    e_vcard_add_attribute (E_VCARD (contact), attr);
+}
 
+void
+_e_contact_remove_gdata_entry_xml (EContact *contact)
+{
+    e_vcard_remove_attributes (E_VCARD (contact), NULL, GDATA_ENTRY_XML_ATTR);
 }
 
-char* build_base_uri (const char* username)
+const char*
+_e_contact_get_gdata_entry_xml (EContact *contact)
 {
-    const char *format = "http://www.google.com/m8/feeds/contacts/%s/base";;
-    char *esc_username;
-    char *uri;
+    EVCardAttribute *attr;
+    GList *values;
 
-    esc_username = g_uri_escape_string (username, NULL, FALSE);
-    uri = g_strdup_printf (format, esc_username);
-    g_free (esc_username);
+    attr = e_vcard_get_attribute (E_VCARD (contact), GDATA_ENTRY_XML_ATTR);
+    values = e_vcard_attribute_get_values (attr);
 
-    return uri;
+    return values ? values->data : NULL;
 }
 
+#if 0
 gboolean test_repository_availability (void)
 {
     SoupSession *session;
@@ -542,6 +480,7 @@
     /* Everything below 100 means we can't reach www.google.com */
     return (http_status >= 100);
 }
+#endif
 
 static char*
 type_from_google_rel_label (const char* rel, const char *label)
@@ -580,7 +519,7 @@
         return;
     }
 
-    if (0 == strncmp (type, "X-", MIN (2, strlen (type)))) {
+    if (0 == strncmp (type, "X-", 2)) {
         *label = g_strdup (type + 2);
         return;
     }
@@ -646,6 +585,14 @@
 
     attr = e_vcard_attribute_new (NULL, EVC_EMAIL);
     type = type_from_google_rel_label (email->rel, email->label);
+    if (email->primary) {
+        param = e_vcard_attribute_param_new (GOOGLE_PRIMARY_PARAM);
+        e_vcard_attribute_add_param (attr, param);
+        if (NULL == type) {
+            param = e_vcard_attribute_param_new ("TYPE");
+            e_vcard_attribute_add_param_with_value (attr, param, "PREF");
+        }
+    }
     if (type) {
         param = e_vcard_attribute_param_new ("TYPE");
         e_vcard_attribute_add_param_with_value (attr, param, type);
@@ -672,6 +619,14 @@
 
     attr = e_vcard_attribute_new (NULL, field_name);
     type = type_from_google_rel_label (im->rel, im->label);
+    if (im->primary) {
+        param = e_vcard_attribute_param_new (GOOGLE_PRIMARY_PARAM);
+        e_vcard_attribute_add_param (attr, param);
+        if (NULL == type) {
+            param = e_vcard_attribute_param_new ("TYPE");
+            e_vcard_attribute_add_param_with_value (attr, param, "PREF");
+        }
+    }
     if (type) {
         param = e_vcard_attribute_param_new ("TYPE");
         e_vcard_attribute_add_param_with_value (attr, param, type);
@@ -694,6 +649,14 @@
     attr = e_vcard_attribute_new (NULL, EVC_TEL);
     /* TODO: This needs more work */
     type = type_from_google_rel_label (number->rel, number->label);
+    if (number->primary) {
+        param = e_vcard_attribute_param_new (GOOGLE_PRIMARY_PARAM);
+        e_vcard_attribute_add_param (attr, param);
+        if (NULL == type) {
+            param = e_vcard_attribute_param_new ("TYPE");
+            e_vcard_attribute_add_param_with_value (attr, param, "PREF");
+        }
+    }
     if (type) {
         param = e_vcard_attribute_param_new ("TYPE");
         e_vcard_attribute_add_param_with_value (attr, param, type);
@@ -716,6 +679,14 @@
     attr = e_vcard_attribute_new (NULL, EVC_LABEL);
     /* TODO: This needs more work */
     type = type_from_google_rel_label (address->rel, address->label);
+    if (address->primary) {
+        param = e_vcard_attribute_param_new (GOOGLE_PRIMARY_PARAM);
+        e_vcard_attribute_add_param (attr, param);
+        if (NULL == type) {
+            param = e_vcard_attribute_param_new ("TYPE");
+            e_vcard_attribute_add_param_with_value (attr, param, "PREF");
+        }
+    }
     if (type) {
         param = e_vcard_attribute_param_new ("TYPE");
         e_vcard_attribute_add_param_with_value (attr, param, type);
@@ -725,19 +696,49 @@
     return attr;
 }
 
+static void
+get_google_primary_and_type (EVCardAttribute *attr, gboolean *primary, const char **type)
+{
+    GList *params;
+
+    *primary = FALSE;
+    *type = NULL;
+    params = e_vcard_attribute_get_params (attr);
+    while (params) {
+        const char *name;
+
+        name = e_vcard_attribute_get_name (params->data);
+        if (0 == g_ascii_strcasecmp (name, GOOGLE_PRIMARY_PARAM)) {
+            *primary = TRUE;
+        }
+        if (0 == g_ascii_strcasecmp (name, "TYPE")) {
+            GList *values;
+
+            values = e_vcard_attribute_param_get_values (params->data);
+            *type = values ? values->data : NULL;
+        }
+        params = params->next;
+    }
+}
+
 static GDataEntryEmailAddress*
-gdata_entry_email_address_from_attribute (EVCardAttribute *attr, gboolean primary)
+gdata_entry_email_address_from_attribute (EVCardAttribute *attr, gboolean *have_primary)
 {
     GDataEntryEmailAddress *email = NULL;
     GList *values;
 
     values = e_vcard_attribute_get_values (attr);
     if (values) {
-        GList *param_values;
         const char *type;
+        gboolean primary;
+
+        get_google_primary_and_type (attr, &primary, &type);
+        if (FALSE == *have_primary) {
+            *have_primary = primary;
+        } else {
+            primary = FALSE;
+        }
 
-        param_values = e_vcard_attribute_get_param (attr, "TYPE");
-        type = param_values ? param_values->data : NULL;
         email = g_new0 (GDataEntryEmailAddress, 1);
         email->address = g_strdup (values->data);
         google_rel_label_from_type (type, &email->rel, &email->label);
@@ -748,7 +749,7 @@
 }
 
 static GDataEntryIMAddress*
-gdata_entry_im_address_from_attribute (EVCardAttribute *attr, gboolean primary)
+gdata_entry_im_address_from_attribute (EVCardAttribute *attr, gboolean *have_primary)
 {
     GDataEntryIMAddress *im = NULL;
     GList *values;
@@ -758,11 +759,16 @@
 
     values = e_vcard_attribute_get_values (attr);
     if (values) {
-        GList *param_values;
         const char *type;
+        gboolean primary;
+
+        get_google_primary_and_type (attr, &primary, &type);
+        if (FALSE == *have_primary) {
+            *have_primary = primary;
+        } else {
+            primary = FALSE;
+        }
 
-        param_values = e_vcard_attribute_get_param (attr, "TYPE");
-        type = param_values ? param_values->data : NULL;
         im = g_new0 (GDataEntryIMAddress, 1);
         im->address = g_strdup (values->data);
         google_rel_label_from_type (type, &im->rel, &im->label);
@@ -774,18 +780,23 @@
 }
 
 static GDataEntryPhoneNumber*
-gdata_entry_phone_number_from_attribute (EVCardAttribute *attr, gboolean primary)
+gdata_entry_phone_number_from_attribute (EVCardAttribute *attr, gboolean *have_primary)
 {
     GDataEntryPhoneNumber *number = NULL;
     GList *values;
 
     values = e_vcard_attribute_get_values (attr);
     if (values) {
-        GList *param_values;
         const char *type;
+        gboolean primary;
+
+        get_google_primary_and_type (attr, &primary, &type);
+        if (FALSE == *have_primary) {
+            *have_primary = primary;
+        } else {
+            primary = FALSE;
+        }
 
-        param_values = e_vcard_attribute_get_param (attr, "TYPE");
-        type = param_values ? param_values->data : NULL;
         number = g_new0 (GDataEntryPhoneNumber, 1);
         number->number = g_strdup (values->data);
         /* TODO: this needs more work */
@@ -797,18 +808,23 @@
 }
 
 static GDataEntryPostalAddress*
-gdata_entry_postal_address_from_attribute (EVCardAttribute *attr, gboolean primary)
+gdata_entry_postal_address_from_attribute (EVCardAttribute *attr, gboolean *have_primary)
 {
     GDataEntryPostalAddress *address = NULL;
     GList *values;
 
     values = e_vcard_attribute_get_values (attr);
     if (values) {
-        GList *param_values;
         const char *type;
+        gboolean primary;
+
+        get_google_primary_and_type (attr, &primary, &type);
+        if (FALSE == *have_primary) {
+            *have_primary = primary;
+        } else {
+            primary = FALSE;
+        }
 
-        param_values = e_vcard_attribute_get_param (attr, "TYPE");
-        type = param_values ? param_values->data : NULL;
         address = g_new0 (GDataEntryPostalAddress, 1);
         address->address = g_strdup (values->data);
         google_rel_label_from_type (type, &address->rel, &address->label);

Modified: trunk/addressbook/backends/google/util.h
==============================================================================
--- trunk/addressbook/backends/google/util.h	(original)
+++ trunk/addressbook/backends/google/util.h	Wed Aug 13 19:51:47 2008
@@ -25,28 +25,21 @@
 #include <libebook/e-contact.h>
 #include <servers/google/libgdata/gdata-entry.h>
 
-char*       vcard_from_gdata_entry          (GDataEntry *entry);
-EVCard*     e_vcard_from_gdata_entry        (GDataEntry *entry);
-EContact*   e_contact_from_gdata_entry      (GDataEntry *entry);
-
-GDataEntry* gdata_entry_create_from_vcard   (const char *vcard_str);
-GDataEntry* gdata_entry_create_from_e_vcard (EVCard *vcard);
-gboolean    gdata_entry_update_from_vcard   (GDataEntry *entry,
-                                             const char *vcard_str);
-gboolean    gdata_entry_update_from_e_vcard (GDataEntry *entry,
-                                             EVCard     *vcard);
-
-
-char*       build_uri                       (const char *base_uri, ...);
-char*       build_base_uri                  (const char *username);
-
-gboolean    test_repository_availability    (void);
-
 extern gboolean __e_book_backend_google_debug__;
 
 #define __debug__(...) (__e_book_backend_google_debug__ ? \
                        g_log (G_LOG_DOMAIN,         \
                               G_LOG_LEVEL_DEBUG,    \
-                              __VA_ARGS__) : (void) 0 )
+                              __VA_ARGS__) : 0 )
+
+GDataEntry* _gdata_entry_new_from_e_contact    (EContact   *contact);
+gboolean    _gdata_entry_update_from_e_contact (GDataEntry *entry,
+                                                EContact   *contact);
+
+EContact*   _e_contact_new_from_gdata_entry    (GDataEntry *entry);
+void        _e_contact_add_gdata_entry_xml     (EContact   *contact,
+                                                GDataEntry *entry);
+void        _e_contact_remove_gdata_entry_xml  (EContact   *contact);
+const char* _e_contact_get_gdata_entry_xml     (EContact *contact);
 
 #endif



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]