[evolution-data-server] Added Direct Read Access support to EBookClient



commit 87657065a524d9374eb2ca0cea65d94480dca2e8
Author: Tristan Van Berkom <tristanvb openismus com>
Date:   Fri Feb 15 18:15:18 2013 +0900

    Added Direct Read Access support to EBookClient
    
    Currently the only added api is e_book_client_connect_direct_sync().
    
    If the backend associated to the given ESource does not support direct
    read access, or the backend fails to open for any reason, then the
    client falls back to regular read access.
    
    EBookClientViews created from an EBookClient that is in direct read
    access mode differ in functionality, they only receive UID notifications
    over D-Bus and fetch the contact data directly.

 addressbook/libebook/e-book-client-view.c |  170 +++++++++++++++++++++++-
 addressbook/libebook/e-book-client.c      |  208 ++++++++++++++++++++++++++++-
 addressbook/libebook/e-book-client.h      |    5 +
 3 files changed, 379 insertions(+), 4 deletions(-)
---
diff --git a/addressbook/libebook/e-book-client-view.c b/addressbook/libebook/e-book-client-view.c
index 53a18e9..5982295 100644
--- a/addressbook/libebook/e-book-client-view.c
+++ b/addressbook/libebook/e-book-client-view.c
@@ -27,6 +27,7 @@
 #include <glib/gi18n-lib.h>
 
 #include <libedataserver/libedataserver.h>
+#include <libedata-book/libedata-book.h>
 
 #include "e-book-client.h"
 #include "e-book-client-view.h"
@@ -42,7 +43,10 @@ struct _EBookClientViewPrivate {
        GDBusProxy *dbus_proxy;
        GDBusConnection *connection;
        gchar *object_path;
-       gboolean running;
+       guint running : 1;
+       guint complete : 1;
+
+       EDataBook *direct_book;
 
        gulong objects_added_handler_id;
        gulong objects_modified_handler_id;
@@ -55,7 +59,8 @@ enum {
        PROP_0,
        PROP_CLIENT,
        PROP_CONNECTION,
-       PROP_OBJECT_PATH
+       PROP_OBJECT_PATH,
+       PROP_DIRECT_BOOK
 };
 
 enum {
@@ -81,6 +86,95 @@ G_DEFINE_TYPE_WITH_CODE (
                G_TYPE_INITABLE,
                e_book_client_view_initable_init))
 
+typedef struct {
+       EBookClientView *view;
+       guint signum;
+} NotificationData;
+
+static gchar *
+direct_contacts_query (const gchar * const *uids)
+{
+       EBookQuery *query, **qs;
+       gchar *sexp;
+       gint i, len;
+
+       len = g_strv_length ((gchar **)uids);
+       qs = g_new0 (EBookQuery *, len);
+
+       for (i = 0; uids[i] != NULL; i++) {
+               const gchar *uid = uids[i];
+
+               qs[i] = e_book_query_field_test (E_CONTACT_UID, E_BOOK_QUERY_IS, uid);
+       }
+
+       query = e_book_query_or (len, qs, TRUE);
+       sexp = e_book_query_to_string (query);
+       e_book_query_unref (query);
+
+       return sexp;
+}
+
+static void
+direct_contacts_ready (GObject *source_object,
+                      GAsyncResult *res,
+                      gpointer user_data)
+{
+       NotificationData *data = (NotificationData *)user_data;
+       GSList *contacts = NULL;
+       GError *error = NULL;
+
+       if (!e_data_book_get_contacts_finish (E_DATA_BOOK (source_object),
+                                             res, &contacts, &error)) {
+               g_warning ("Error fetching contacts directly: %s\n", error->message);
+               g_error_free (error);
+       } else {
+               g_signal_emit (data->view, data->signum, 0, contacts);
+       }
+
+       g_slist_free_full (contacts, (GDestroyNotify) g_object_unref);
+       g_object_unref (data->view);
+       g_slice_free (NotificationData, data);
+}
+
+static void
+direct_contacts_fetch (EBookClientView *view,
+                      const gchar * const *uids,
+                      guint signum)
+{
+       NotificationData *data;
+       gchar *sexp = direct_contacts_query (uids);
+
+       /* Until the view has completely loaded, we need to make
+        * sync calls to the backend
+        */
+       if (!view->priv->complete) {
+               GSList *contacts = NULL;
+               GError *error = NULL;
+
+               if (!e_data_book_get_contacts_sync (view->priv->direct_book,
+                                                   sexp, &contacts, NULL, &error)) {
+                       g_warning ("Error fetching contacts directly: %s\n", error->message);
+                       g_error_free (error);
+               } else {
+                       g_signal_emit (view, signum, 0, contacts);
+                       g_slist_free_full (contacts, (GDestroyNotify) g_object_unref);
+               }
+
+       } else {
+               /* Make async calls, avoid blocking the thread owning the view
+                * as much as possible
+                */
+               data = g_slice_new (NotificationData);
+               data->view = g_object_ref (view);
+               data->signum = signum;
+
+               e_data_book_get_contacts (view->priv->direct_book,
+                                         sexp, NULL, direct_contacts_ready, data);
+       }
+
+       g_free (sexp);
+}
+
 static void
 book_client_view_objects_added_cb (EGdbusBookView *object,
                                    const gchar * const *vcards,
@@ -92,6 +186,12 @@ book_client_view_objects_added_cb (EGdbusBookView *object,
        if (!view->priv->running)
                return;
 
+       /* array contains UIDs only */
+       if (view->priv->direct_book) {
+               direct_contacts_fetch (view, vcards, signals[OBJECTS_ADDED]);
+               return;
+       }
+
        /* array contains both UID and vcard */
        for (p = vcards; p[0] && p[1]; p += 2) {
                EContact *contact;
@@ -120,6 +220,12 @@ book_client_view_objects_modified_cb (EGdbusBookView *object,
        if (!view->priv->running)
                return;
 
+       /* array contains UIDs only */
+       if (view->priv->direct_book) {
+               direct_contacts_fetch (view, vcards, signals[OBJECTS_MODIFIED]);
+               return;
+       }
+
        /* array contains both UID and vcard */
        for (p = vcards; p[0] && p[1]; p += 2) {
                EContact *contact;
@@ -181,6 +287,8 @@ book_client_view_complete_cb (EGdbusBookView *object,
        if (!view->priv->running)
                return;
 
+       view->priv->complete = TRUE;
+
        g_return_if_fail (e_gdbus_templates_decode_error (in_error_strv, &error));
 
        g_signal_emit (view, signals[COMPLETE], 0, error);
@@ -220,6 +328,18 @@ book_client_view_set_object_path (EBookClientView *view,
 }
 
 static void
+book_client_view_set_direct_book (EBookClientView *view,
+                                 EDataBook       *book)
+{
+       g_return_if_fail (book == NULL ||
+                         E_IS_DATA_BOOK (book));
+       g_return_if_fail (view->priv->direct_book == NULL);
+
+       if (book)
+               view->priv->direct_book = g_object_ref (book);
+}
+
+static void
 book_client_view_set_property (GObject *object,
                                guint property_id,
                                const GValue *value,
@@ -243,6 +363,12 @@ book_client_view_set_property (GObject *object,
                                E_BOOK_CLIENT_VIEW (object),
                                g_value_get_string (value));
                        return;
+
+               case PROP_DIRECT_BOOK:
+                       book_client_view_set_direct_book (
+                               E_BOOK_CLIENT_VIEW (object),
+                               g_value_get_object (value));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -297,6 +423,11 @@ book_client_view_dispose (GObject *object)
                priv->connection = NULL;
        }
 
+       if (priv->direct_book != NULL) {
+               g_object_unref (priv->direct_book);
+               priv->direct_book = NULL;
+       }
+
        if (priv->dbus_proxy != NULL) {
                GError *error = NULL;
 
@@ -396,6 +527,12 @@ book_client_view_initable_init (GInitable *initable,
                G_CALLBACK (book_client_view_complete_cb), initable);
        priv->complete_handler_id = handler_id;
 
+       /* When in direct read access mode, we add a special field
+        * to fields-of-interest indicating we only want uids sent
+        */
+       if (priv->direct_book)
+               e_book_client_view_set_fields_of_interest (E_BOOK_CLIENT_VIEW (initable), NULL, NULL);
+
        return TRUE;
 }
 
@@ -450,6 +587,20 @@ e_book_client_view_class_init (EBookClientViewClass *class)
                        G_PARAM_CONSTRUCT_ONLY |
                        G_PARAM_STATIC_STRINGS));
 
+       g_object_class_install_property (
+               object_class,
+               PROP_DIRECT_BOOK,
+               g_param_spec_object (
+                       "direct-book",
+                       "Direct Book",
+                       "The EDataBook to fetch contact "
+                       "data from, if direct read access "
+                       "is enabled",
+                       E_TYPE_DATA_BOOK,
+                       G_PARAM_WRITABLE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
        signals[OBJECTS_ADDED] = g_signal_new (
                "objects-added",
                G_OBJECT_CLASS_TYPE (object_class),
@@ -671,7 +822,20 @@ e_book_client_view_set_fields_of_interest (EBookClientView *view,
 
        g_return_if_fail (E_IS_BOOK_CLIENT_VIEW (view));
 
-       strv = e_client_util_slist_to_strv (fields_of_interest);
+       /* When in direct read access mode, ensure that the
+        * backend is configured to only send us UIDs for everything,
+        *
+        * Just ignore the fields_of_interest and use them locally
+        * when filtering cards to be returned in direct reads.
+        */
+       if (view->priv->direct_book) {
+               GSList uid_field = { 0, };
+
+               uid_field.data = (gpointer)"x-evolution-uids-only";
+               strv = e_client_util_slist_to_strv (&uid_field);
+       } else
+               strv = e_client_util_slist_to_strv (fields_of_interest);
+
        e_gdbus_book_view_call_set_fields_of_interest_sync (
                view->priv->dbus_proxy,
                (const gchar * const *) strv,
diff --git a/addressbook/libebook/e-book-client.c b/addressbook/libebook/e-book-client.c
index cc35453..4841447 100644
--- a/addressbook/libebook/e-book-client.c
+++ b/addressbook/libebook/e-book-client.c
@@ -29,10 +29,14 @@
 /* Private D-Bus classes. */
 #include <e-dbus-address-book.h>
 #include <e-dbus-address-book-factory.h>
+#include <e-dbus-direct-book.h>
 
 #include <libedataserver/libedataserver.h>
 #include <libedataserver/e-client-private.h>
 
+#include <libebackend/libebackend.h>
+#include <libedata-book/libedata-book.h>
+
 #include "e-book-client.h"
 
 #define E_BOOK_CLIENT_GET_PRIVATE(obj) \
@@ -51,6 +55,7 @@ typedef struct _RunInThreadClosure RunInThreadClosure;
 struct _EBookClientPrivate {
        GMainContext *main_context;
        EDBusAddressBook *dbus_proxy;
+       EDataBook *direct_book;
        guint name_watcher_id;
 
        gulong dbus_proxy_error_handler_id;
@@ -231,6 +236,61 @@ static GRecMutex book_factory_lock;
 #define LOCK_FACTORY()   g_rec_mutex_lock (&book_factory_lock)
 #define UNLOCK_FACTORY() g_rec_mutex_unlock (&book_factory_lock)
 
+
+typedef struct {
+       EBookClient         *client;
+       GAsyncReadyCallback  callback;
+       gpointer             user_data;
+       gpointer             source_tag;
+} PropagateReadyData;
+
+static PropagateReadyData *
+propagate_ready_data_new (EBookClient         *client,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data,
+                         gpointer             source_tag)
+{
+       PropagateReadyData *data = g_slice_new0 (PropagateReadyData);
+
+       data->client = g_object_ref (client);
+       data->callback = callback;
+       data->user_data = user_data;
+       data->source_tag = source_tag;
+
+       return data;
+}
+
+static void
+propagate_ready_data_free (PropagateReadyData *data)
+{
+       if (data) {
+               g_object_unref (data->client);
+               g_slice_free (PropagateReadyData, data);
+       }
+}
+
+static void
+propagate_direct_book_async_ready (GObject *source_object,
+                                  GAsyncResult *res,
+                                  gpointer user_data)
+{
+       GSimpleAsyncResult *result;
+       PropagateReadyData *data = (PropagateReadyData *)user_data;
+
+       result = g_simple_async_result_new (G_OBJECT (data->client),
+                                           data->callback,
+                                           data->user_data,
+                                           data->source_tag);
+
+       g_object_ref (res);
+       g_simple_async_result_set_op_res_gpointer (result, res, g_object_unref);
+
+       g_simple_async_result_complete (result);
+       g_object_unref (result);
+
+       propagate_ready_data_free (data);
+}
+
 static void
 book_factory_disconnect (void)
 {
@@ -582,8 +642,10 @@ static void
 book_client_dispose (GObject *object)
 {
        EBookClientPrivate *priv;
+       EBookClient *book_client;
 
-       priv = E_BOOK_CLIENT_GET_PRIVATE (object);
+       book_client = E_BOOK_CLIENT (object);
+       priv = book_client->priv;
 
        if (priv->dbus_proxy_error_handler_id > 0) {
                g_signal_handler_disconnect (
@@ -614,6 +676,12 @@ book_client_dispose (GObject *object)
                priv->main_context = NULL;
        }
 
+       if (book_client->priv->direct_book) {
+               e_data_book_close_sync (book_client->priv->direct_book, NULL, NULL);
+               g_object_unref (book_client->priv->direct_book);
+               book_client->priv->direct_book = NULL;
+       }
+
        /* Chain up to parent's dispose() method. */
        G_OBJECT_CLASS (e_book_client_parent_class)->dispose (object);
 }
@@ -1248,6 +1316,81 @@ e_book_client_new (ESource *source,
                "source", source, NULL);
 }
 
+/**
+ * e_book_client_connect_direct_sync:
+ * @source: An #ESource pointer
+ * @error: A #GError pointer
+ *
+ * Like e_book_client_connect_sync(), except creates the book client for
+ * direct read access to the underlying addressbook.
+ *
+ * Returns: a new but unopened #EBookClient.
+ *
+ * Since: 3.8
+ **/
+EClient *
+e_book_client_connect_direct_sync (ESourceRegistry *registry,
+                                  ESource *source,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       EClient *client;
+       EBookClientPrivate *priv;
+       EDBusDirectBook *direct_config;
+       const gchar *backend_name, *backend_path, *config;
+       GError *local_error = NULL;
+
+       client = e_book_client_connect_sync (source, cancellable, error);
+
+       if (!client)
+               return NULL;
+
+       priv = E_BOOK_CLIENT_GET_PRIVATE (client);
+
+       direct_config = e_dbus_direct_book_proxy_new_sync (
+                g_dbus_proxy_get_connection (G_DBUS_PROXY (priv->dbus_proxy)),
+               G_DBUS_PROXY_FLAGS_NONE,
+               ADDRESS_BOOK_DBUS_SERVICE_NAME,
+               g_dbus_proxy_get_object_path (G_DBUS_PROXY (priv->dbus_proxy)),
+               NULL, NULL);
+
+       backend_path = e_dbus_direct_book_get_backend_path (direct_config);
+       backend_name = e_dbus_direct_book_get_backend_name (direct_config);
+       config = e_dbus_direct_book_get_backend_config (direct_config);
+
+       if (backend_path && backend_path[0] &&
+           backend_name && backend_name[0]) {
+               priv->direct_book =
+                       e_data_book_new_direct (registry, source,
+                                               backend_path,
+                                               backend_name,
+                                               config, &local_error);
+               if (!priv->direct_book) {
+                       g_warning ("Failed to open addressbook in direct read access mode, "
+                                  "falling back to normal read access mode. Reason: %s",
+                                  local_error->message);
+                       g_error_free (local_error);
+               }
+
+       } else
+               g_warning ("Direct read access mode not supported by the given backend, falling back to 
normal read access mode");
+
+       g_object_unref (direct_config);
+
+       /* We have to perform the opeining of the direct book separately
+        * from the EClient->open() implementation, because the direct
+        * book does not exist yet
+        */
+       if (priv->direct_book &&
+           !e_data_book_open_sync (priv->direct_book, cancellable, error)) {
+               g_warning ("Unable to open direct read access book, falling back to normal read access mode");
+               g_clear_object (&priv->direct_book);
+       }
+
+       return client;
+
+}
+
 #define SELF_UID_PATH_ID "org.gnome.evolution-data-server.addressbook"
 #define SELF_UID_KEY "self-contact-uid"
 
@@ -2521,6 +2664,15 @@ e_book_client_get_contact (EBookClient *client,
        g_return_if_fail (E_IS_BOOK_CLIENT (client));
        g_return_if_fail (uid != NULL);
 
+       if (client->priv->direct_book) {
+               PropagateReadyData *data = propagate_ready_data_new (client, callback, user_data,
+                                                                    e_book_client_get_contact);
+
+               e_data_book_get_contact (client->priv->direct_book, uid, cancellable,
+                                        propagate_direct_book_async_ready, data);
+               return;
+       }
+
        async_context = g_slice_new0 (AsyncContext);
        async_context->uid  = g_strdup (uid);
 
@@ -2569,6 +2721,12 @@ e_book_client_get_contact_finish (EBookClient *client,
                result, G_OBJECT (client),
                e_book_client_get_contact), FALSE);
 
+       if (client->priv->direct_book) {
+               GAsyncResult *res = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT 
(result));
+
+               return e_data_book_get_contact_finish (client->priv->direct_book, res, out_contact, error);
+       }
+
        simple = G_SIMPLE_ASYNC_RESULT (result);
        async_context = g_simple_async_result_get_op_res_gpointer (simple);
 
@@ -2614,6 +2772,9 @@ e_book_client_get_contact_sync (EBookClient *client,
        g_return_val_if_fail (uid != NULL, FALSE);
        g_return_val_if_fail (out_contact != NULL, FALSE);
 
+       if (client->priv->direct_book)
+               return e_data_book_get_contact_sync (client->priv->direct_book, uid, out_contact, 
cancellable, error);
+
        utf8_uid = e_util_utf8_make_valid (uid);
 
        success = e_dbus_address_book_call_get_contact_sync (
@@ -2687,6 +2848,15 @@ e_book_client_get_contacts (EBookClient *client,
        g_return_if_fail (E_IS_BOOK_CLIENT (client));
        g_return_if_fail (sexp != NULL);
 
+       if (client->priv->direct_book) {
+               PropagateReadyData *data = propagate_ready_data_new (client, callback, user_data,
+                                                                    e_book_client_get_contacts);
+
+               e_data_book_get_contacts (client->priv->direct_book, sexp, cancellable, 
+                                         propagate_direct_book_async_ready, data);
+               return;
+       }
+
        async_context = g_slice_new0 (AsyncContext);
        async_context->sexp = g_strdup (sexp);
 
@@ -2731,6 +2901,14 @@ e_book_client_get_contacts_finish (EBookClient *client,
        GSimpleAsyncResult *simple;
        AsyncContext *async_context;
 
+       if (client->priv->direct_book) {
+               GAsyncResult *direct_res =
+                       g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+
+               return e_data_book_get_contacts_finish (client->priv->direct_book,
+                                                       direct_res, out_contacts, error);
+       }
+
        g_return_val_if_fail (
                g_simple_async_result_is_valid (
                result, G_OBJECT (client),
@@ -2785,6 +2963,10 @@ e_book_client_get_contacts_sync (EBookClient *client,
        g_return_val_if_fail (sexp != NULL, FALSE);
        g_return_val_if_fail (out_contacts != NULL, FALSE);
 
+       if (client->priv->direct_book)
+               return e_data_book_get_contacts_sync (client->priv->direct_book,
+                                                     sexp, out_contacts, cancellable, error);
+
        utf8_sexp = e_util_utf8_make_valid (sexp);
 
        success = e_dbus_address_book_call_get_contact_list_sync (
@@ -2867,6 +3049,15 @@ e_book_client_get_contacts_uids (EBookClient *client,
        g_return_if_fail (E_IS_BOOK_CLIENT (client));
        g_return_if_fail (sexp != NULL);
 
+       if (client->priv->direct_book) {
+               PropagateReadyData *data = propagate_ready_data_new (client, callback, user_data,
+                                                                    e_book_client_get_contacts_uids);
+
+               e_data_book_get_contacts_uids (client->priv->direct_book, sexp, cancellable, 
+                                              propagate_direct_book_async_ready, data);
+               return;
+       }
+
        async_context = g_slice_new0 (AsyncContext);
        async_context->sexp = g_strdup (sexp);
 
@@ -2911,6 +3102,15 @@ e_book_client_get_contacts_uids_finish (EBookClient *client,
        GSimpleAsyncResult *simple;
        AsyncContext *async_context;
 
+       if (client->priv->direct_book) {
+               GAsyncResult *direct_res =
+                       g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+
+               return e_data_book_get_contacts_uids_finish (
+                              client->priv->direct_book,
+                              direct_res, out_contact_uids, error);
+       }
+
        g_return_val_if_fail (
                g_simple_async_result_is_valid (
                result, G_OBJECT (client),
@@ -2965,6 +3165,11 @@ e_book_client_get_contacts_uids_sync (EBookClient *client,
        g_return_val_if_fail (sexp != NULL, FALSE);
        g_return_val_if_fail (out_contact_uids != NULL, FALSE);
 
+       if (client->priv->direct_book)
+               return e_data_book_get_contacts_uids_sync (
+                              client->priv->direct_book, sexp, 
+                              out_contact_uids, cancellable, error);
+
        utf8_sexp = e_util_utf8_make_valid (sexp);
 
        success = e_dbus_address_book_call_get_contact_list_uids_sync (
@@ -3172,6 +3377,7 @@ e_book_client_get_view_sync (EBookClient *client,
                        "client", client,
                        "connection", connection,
                        "object-path", object_path,
+                       "direct-book", client->priv->direct_book,
                        NULL);
 
                /* XXX Would have been easier to return the
diff --git a/addressbook/libebook/e-book-client.h b/addressbook/libebook/e-book-client.h
index 7caaa61..6da0dfe 100644
--- a/addressbook/libebook/e-book-client.h
+++ b/addressbook/libebook/e-book-client.h
@@ -110,6 +110,11 @@ void               e_book_client_connect           (ESource *source,
                                                 gpointer user_data);
 EClient *      e_book_client_connect_finish    (GAsyncResult *result,
                                                 GError **error);
+EClient *       e_book_client_connect_direct_sync
+                                                (ESourceRegistry *registry,
+                                                ESource *source,
+                                                GCancellable *cancellable,
+                                                GError **error);
 gboolean       e_book_client_get_self          (ESourceRegistry *registry,
                                                 EContact **out_contact,
                                                 EBookClient **out_client,


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