[evolution-data-server/openismus-work-master: 64/73] Added EDataBookCursor



commit 2f4b6beb38d9b387ffd5664287641bf408b080e4
Author: Tristan Van Berkom <tristanvb openismus com>
Date:   Tue Jul 2 17:41:41 2013 +0900

    Added EDataBookCursor
    
    EDataBookCursor is an abstract class with the mid-level cursor APIs, it
    takes care of responding to D-Bus APIs and provides an API which can
    be used in Direct Read Access mode but does not implement the actual
    cursor state and navigation directly.

 addressbook/libedata-book/e-data-book-cursor.c |  994 ++++++++++++++++++++++++
 addressbook/libedata-book/e-data-book-cursor.h |  294 +++++++
 2 files changed, 1288 insertions(+), 0 deletions(-)
---
diff --git a/addressbook/libedata-book/e-data-book-cursor.c b/addressbook/libedata-book/e-data-book-cursor.c
new file mode 100644
index 0000000..6ca1979
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor.c
@@ -0,0 +1,994 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * 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 of the License, or (at your option) version 3.
+ *
+ * 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-data-book-cursor.h"
+#include "e-book-backend.h"
+
+/* Private D-Bus class. */
+#include <e-dbus-address-book-cursor.h>
+
+/* GObjectClass */
+static void e_data_book_cursor_constructed (GObject *object);
+static void e_data_book_cursor_dispose (GObject *object);
+static void e_data_book_cursor_finalize (GObject *object);
+static void e_data_book_cursor_get_property (GObject *object,
+                                            guint property_id,
+                                            GValue *value,
+                                            GParamSpec *pspec);
+static void e_data_book_cursor_set_property (GObject *object,
+                                            guint property_id,
+                                            const GValue *value,
+                                            GParamSpec *pspec);
+
+/* Private Functions */
+static void     data_book_cursor_set_values   (EDataBookCursor  *cursor,
+                                              gint              total,
+                                              gint              position);
+static gboolean data_book_cursor_compare      (EDataBookCursor  *cursor,
+                                              EContact         *contact,
+                                              gint             *result,
+                                              gboolean         *matches_sexp);
+static void     calculate_move_by_position    (EDataBookCursor  *cursor,
+                                              EBookCursorOrigin origin,
+                                              gint              count,
+                                              gint              results);
+
+/* D-Bus callbacks */
+static gboolean data_book_cursor_handle_move_by              (EDBusAddressBookCursor *dbus_object,
+                                                             GDBusMethodInvocation  *invocation,
+                                                             EBookCursorOrigin       origin,
+                                                             gint                    count,
+                                                             gboolean                fetch_results,
+                                                             EDataBookCursor        *cursor);
+static gboolean data_book_cursor_handle_set_alphabetic_index (EDBusAddressBookCursor *dbus_object,
+                                                             GDBusMethodInvocation  *invocation,
+                                                             gint                    index,
+                                                             const gchar            *locale,
+                                                             EDataBookCursor        *cursor);
+static gboolean data_book_cursor_handle_set_query            (EDBusAddressBookCursor *dbus_object,
+                                                             GDBusMethodInvocation  *invocation,
+                                                             const gchar            *query,
+                                                             EDataBookCursor        *cursor);
+static gboolean data_book_cursor_handle_dispose              (EDBusAddressBookCursor *dbus_object,
+                                                             GDBusMethodInvocation  *invocation,
+                                                             EDataBookCursor        *cursor);
+
+struct _EDataBookCursorPrivate {
+       EDBusAddressBookCursor *dbus_object;
+       EBookBackend *backend;
+
+       gchar *locale;
+       gint total;
+       gint position;
+};
+
+enum {
+       PROP_0,
+       PROP_BACKEND,
+       PROP_TOTAL,
+       PROP_POSITION,
+};
+
+G_DEFINE_ABSTRACT_TYPE (
+       EDataBookCursor,
+       e_data_book_cursor,
+       G_TYPE_OBJECT);
+
+/************************************************
+ *                  GObjectClass                *
+ ************************************************/
+static void
+e_data_book_cursor_class_init (EDataBookCursorClass *class)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+       object_class->constructed = e_data_book_cursor_constructed;
+       object_class->finalize = e_data_book_cursor_finalize;
+       object_class->dispose = e_data_book_cursor_dispose;
+       object_class->get_property = e_data_book_cursor_get_property;
+       object_class->set_property = e_data_book_cursor_set_property;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_BACKEND,
+               g_param_spec_object (
+                       "backend",
+                       "Backend",
+                       "The backend which created this cursor",
+                       E_TYPE_BOOK_BACKEND,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_TOTAL,
+               g_param_spec_int (
+                       "total", "Total",
+                       "The total results for this cursor",
+                       0, G_MAXINT, 0,
+                       G_PARAM_READABLE));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_POSITION,
+               g_param_spec_int (
+                       "position", "Position",
+                       "The current position of this cursor",
+                       0, G_MAXINT, 0,
+                       G_PARAM_READABLE));
+
+       g_type_class_add_private (class, sizeof (EDataBookCursorPrivate));
+}
+
+static void
+e_data_book_cursor_init (EDataBookCursor *cursor)
+{
+       cursor->priv = 
+         G_TYPE_INSTANCE_GET_PRIVATE (cursor,
+                                      E_TYPE_DATA_BOOK_CURSOR,
+                                      EDataBookCursorPrivate);
+}
+
+static void
+e_data_book_cursor_constructed (GObject *object)
+{
+  EDataBookCursor *cursor = E_DATA_BOOK_CURSOR (object);
+  GError *error = NULL;
+
+  /* Get the initial cursor values */
+  if (!e_data_book_cursor_recalculate (cursor, &error)) {
+         g_warning ("Failed to calculate initial cursor position: %s", error->message);
+         g_clear_error (&error);
+  }
+
+  G_OBJECT_CLASS (e_data_book_cursor_parent_class)->constructed (object);
+}
+
+static void
+e_data_book_cursor_finalize (GObject *object)
+{
+  EDataBookCursor        *cursor = E_DATA_BOOK_CURSOR (object);
+  EDataBookCursorPrivate *priv = cursor->priv;
+
+  g_free (priv->locale);
+
+  G_OBJECT_CLASS (e_data_book_cursor_parent_class)->finalize (object);
+}
+
+static void
+e_data_book_cursor_dispose (GObject *object)
+{
+  EDataBookCursor        *cursor = E_DATA_BOOK_CURSOR (object);
+  EDataBookCursorPrivate *priv = cursor->priv;
+
+  g_clear_object (&(priv->dbus_object));
+  g_clear_object (&(priv->backend));
+
+  G_OBJECT_CLASS (e_data_book_cursor_parent_class)->dispose (object);
+}
+
+static void
+e_data_book_cursor_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+       EDataBookCursor        *cursor = E_DATA_BOOK_CURSOR (object);
+       EDataBookCursorPrivate *priv = cursor->priv;
+
+       switch (property_id) {
+       case PROP_BACKEND:
+               g_value_set_object (value, priv->backend);
+               break;
+       case PROP_TOTAL:
+               g_value_set_int (value, priv->total);
+               break;
+       case PROP_POSITION:
+               g_value_set_int (value, priv->position);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+static void
+e_data_book_cursor_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+       EDataBookCursor        *cursor = E_DATA_BOOK_CURSOR (object);
+       EDataBookCursorPrivate *priv = cursor->priv;
+
+       switch (property_id) {
+       case PROP_BACKEND:
+               /* Construct-only, can only be set once */
+               priv->backend = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+/************************************************
+ *                Private Functions             *
+ ************************************************/
+static void
+data_book_cursor_set_values (EDataBookCursor *cursor,
+                            gint             total,
+                            gint             position)
+{
+       EDataBookCursorPrivate *priv;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (E_IS_DATA_BOOK_CURSOR (cursor));
+
+       priv = cursor->priv;
+
+       g_object_freeze_notify (G_OBJECT (cursor));
+
+       if (priv->total != total) {
+               priv->total = total;
+               g_object_notify (G_OBJECT (cursor), "total");
+               changed = TRUE;
+       }
+
+       if (priv->position != position) {
+               priv->position = position;
+               g_object_notify (G_OBJECT (cursor), "position");
+               changed = TRUE;
+       }
+
+       g_object_thaw_notify (G_OBJECT (cursor));
+
+       if (changed && priv->dbus_object) {
+               e_dbus_address_book_cursor_set_total (priv->dbus_object, priv->total);
+               e_dbus_address_book_cursor_set_position (priv->dbus_object, priv->position);
+       }
+}
+
+static gboolean
+data_book_cursor_compare (EDataBookCursor     *cursor,
+                         EContact            *contact,
+                         gint                *result,
+                         gboolean            *matches_sexp)
+{
+       if (!E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->compare)
+               return FALSE;
+
+       g_object_ref (cursor);
+       *result = (* E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->compare) (cursor,
+                                                                     contact,
+                                                                     matches_sexp);
+       g_object_unref (cursor);
+
+       return TRUE;
+}
+
+static void
+calculate_move_by_position (EDataBookCursor     *cursor,
+                           EBookCursorOrigin    origin,
+                           gint                 count,
+                           gint                 results)
+{
+       EDataBookCursorPrivate *priv = cursor->priv;
+       GError *error = NULL;
+       gint new_position;
+
+       switch (origin) {
+       case E_BOOK_CURSOR_ORIGIN_CURRENT:
+
+               if (count < 0 && priv->position == 0)
+                       new_position = (priv->total + 1) + count;
+               else
+                       new_position = priv->position + count;
+
+               /* If we ran off the end of the total query, reset to 0 */
+               if (new_position < 0 || new_position > priv->total)
+                       new_position = 0;
+
+               data_book_cursor_set_values (cursor, priv->total, new_position);
+               break;
+
+       case E_BOOK_CURSOR_ORIGIN_PREVIOUS:
+               /* We don't manually track the previous position, so for now
+                * we need to recalculate the position entirely
+                */
+               if (!e_data_book_cursor_recalculate (cursor, &error)) {
+                       g_warning ("Failed to recalculate the cursor value "
+                                  "after moving the cursor: %s",
+                                  error->message);
+                       g_clear_error (&error);
+               }
+               break;
+       case E_BOOK_CURSOR_ORIGIN_RESET:
+
+               if (count < 0)
+                       new_position = (priv->total + 1) + count;
+               else
+                       new_position = count;
+
+               /* If we ran off the end of the total query, reset to 0 */
+               if (new_position < 0 || new_position > priv->total)
+                       new_position = 0;
+
+               data_book_cursor_set_values (cursor, priv->total, new_position);
+               break;
+
+       }
+}
+
+/************************************************
+ *                D-Bus Callbacks               *
+ ************************************************/
+static gboolean
+data_book_cursor_handle_move_by (EDBusAddressBookCursor *dbus_object,
+                                GDBusMethodInvocation  *invocation,
+                                EBookCursorOrigin       origin,
+                                gint                    count,
+                                gboolean                fetch_results,
+                                EDataBookCursor        *cursor)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+
+       if (!e_data_book_cursor_move_by (cursor, NULL, origin, count,
+                                        fetch_results ? &results : NULL,
+                                        &error)) {
+               g_dbus_method_invocation_return_gerror (invocation, error);
+               g_clear_error (&error);
+       } else {
+               gchar **strv = NULL;
+
+               if (results) {
+                       GSList *l;
+                       gint i = 0;
+
+                       strv = g_new0 (gchar *, g_slist_length (results) + 1);
+
+                       for (l = results; l; l = l->next) {
+                               gchar *vcard = l->data;
+
+                               strv[i++] = e_util_utf8_make_valid (vcard);
+                       }
+
+               }
+
+               e_dbus_address_book_cursor_complete_move_by (dbus_object,
+                                                            invocation,
+                                                            (const gchar *const *)strv,
+                                                            cursor->priv->total,
+                                                            cursor->priv->position);
+
+
+               g_strfreev (strv);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+data_book_cursor_handle_set_alphabetic_index (EDBusAddressBookCursor *dbus_object,
+                                             GDBusMethodInvocation  *invocation,
+                                             gint                    index,
+                                             const gchar            *locale,
+                                             EDataBookCursor        *cursor)
+{
+       GError *error = NULL;
+
+       if (!e_data_book_cursor_set_alphabetic_index (cursor,
+                                                     index,
+                                                     locale,
+                                                     &error)) {
+               g_dbus_method_invocation_return_gerror (invocation, error);
+               g_clear_error (&error);
+       } else {
+               e_dbus_address_book_cursor_complete_set_alphabetic_index (dbus_object,
+                                                                         invocation,
+                                                                         cursor->priv->total,
+                                                                         cursor->priv->position);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+data_book_cursor_handle_set_query (EDBusAddressBookCursor *dbus_object,
+                                  GDBusMethodInvocation  *invocation,
+                                  const gchar            *query,
+                                  EDataBookCursor        *cursor)
+{
+       GError *error = NULL;
+
+       if (!e_data_book_cursor_set_sexp (cursor, query, &error)) {
+               g_dbus_method_invocation_return_gerror (invocation, error);
+               g_clear_error (&error);
+       } else {
+               e_dbus_address_book_cursor_complete_set_query (dbus_object,
+                                                              invocation,
+                                                              cursor->priv->total,
+                                                              cursor->priv->position);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+data_book_cursor_handle_dispose (EDBusAddressBookCursor *dbus_object,
+                                GDBusMethodInvocation  *invocation,
+                                EDataBookCursor        *cursor)
+{
+       EDataBookCursorPrivate *priv = cursor->priv;
+
+       /* The backend will release the cursor, just make sure that
+        * we survive long enough to complete this method call
+        */
+       g_object_ref (cursor);
+       e_book_backend_delete_cursor (priv->backend, cursor);
+       e_dbus_address_book_cursor_complete_dispose (dbus_object, invocation);
+       g_object_unref (cursor);
+
+       return TRUE;
+}
+
+/************************************************
+ *                       API                    *
+ ************************************************/
+
+/**
+ * e_data_book_cursor_get_backend:
+ * @cursor: an #EDataBookCursor
+ *
+ * Gets the backend which created and owns @cursor.
+ *
+ * Returns: (transfer none): The #EBookBackend owning @cursor.
+ *
+ * Since: 3.10
+ */
+struct _EBookBackend *
+e_data_book_cursor_get_backend (EDataBookCursor *cursor)
+{
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), NULL);
+
+       return cursor->priv->backend;
+}
+
+
+/**
+ * e_data_book_cursor_get_total:
+ * @cursor: an #EDataBookCursor
+ *
+ * Fetch the total number of contacts which match @cursor's query expression.
+ *
+ * Returns: the total contacts for @cursor
+ *
+ * Since: 3.10
+ */
+gint
+e_data_book_cursor_get_total (EDataBookCursor *cursor)
+{
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), -1);
+
+       return cursor->priv->total;
+}
+
+/**
+ * e_data_book_cursor_get_position:
+ * @cursor: an #EDataBookCursor
+ *
+ * Fetch the current position of @cursor in it's result list.
+ *
+ * Returns: the current position of @cursor
+ *
+ * Since: 3.10
+ */
+gint
+e_data_book_cursor_get_position (EDataBookCursor *cursor)
+{
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), -1);
+
+       return cursor->priv->position;
+}
+
+
+/**
+ * e_data_book_cursor_set_sexp:
+ * @cursor: an #EDataBookCursor
+ * @sexp: (allow-none): the search expression to set
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Sets the search expression for the cursor
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+gboolean
+e_data_book_cursor_set_sexp (EDataBookCursor     *cursor,
+                            const gchar         *sexp,
+                            GError             **error)
+{
+       GError *local_error = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
+
+       g_object_ref (cursor);
+
+       if (E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->set_sexp) {
+               success = (* E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->set_sexp) (cursor,
+                                                                              sexp,
+                                                                              error);
+
+               /* We already set the new search expression, we can't fail anymore so just fire a warning */
+               if (!e_data_book_cursor_recalculate (cursor, &local_error)) {
+                       g_warning ("Failed to recalculate the cursor value "
+                                  "after setting the search expression: %s",
+                                  local_error->message);
+                       g_clear_error (&local_error);
+               }
+
+       } else {
+               g_set_error (error,
+                            E_CLIENT_ERROR,
+                            E_CLIENT_ERROR_NOT_SUPPORTED,
+                            "Cursor does not support setting the search expression");
+               success = FALSE;
+       }
+
+       g_object_unref (cursor);
+
+       return success;
+}
+
+/**
+ * e_data_book_cursor_move_by:
+ * @cursor: an #EDataBookCursor
+ * @revision_guard: (allow-none): The expected current addressbook revision, or %NULL
+ * @origin: the #EBookCursorOrigin for this move
+ * @count: a positive or negative amount of contacts to try and fetch
+ * @results: (out) (allow-none) (element-type utf8) (transfer full):
+ *   A return location to store the results, or %NULL to move the cursor without retrieving any results.
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Moves @cursor through the results by @count and fetch a maximum of @count contacts.
+ *
+ * If @count is negative, then the cursor will move backwards.
+ *
+ * If @cursor is in an empty state, or @origin is %E_BOOK_CURSOR_ORIGIN_RESET,
+ * then @count contacts will be fetched from the beginning of the cursor's query
+ * results, or from the ending of the query results for a negative value of @count.
+ *
+ * If @cursor reaches the beginning or end of the query results, then the
+ * returned list might not contain the amount of desired contacts, or might
+ * return no results if the cursor currently points to the last contact.
+ * This is not considered an error condition.
+ *
+ * If @results is specified, it should be a pointer to a %NULL #GSList,
+ * the result list will be stored to @results and should be freed with g_slist_free()
+ * and all elements freed with g_free().
+ *
+ * If @revision_guard is specified, then the cursor implementation will
+ * issue an %E_CLIENT_ERROR_OUT_OF_SYNC error if the @revision_guard does
+ * not match the current addressbook revision. This is a special protection
+ * for cursors operating in Direct Read Access mode.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+gboolean
+e_data_book_cursor_move_by (EDataBookCursor     *cursor,
+                           const gchar         *revision_guard,
+                           EBookCursorOrigin    origin,
+                           gint                 count,
+                           GSList             **results,
+                           GError             **error)
+{
+       GSList *local_results = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
+
+       if (!E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->move_by) {
+               g_set_error (error,
+                            E_CLIENT_ERROR,
+                            E_CLIENT_ERROR_NOT_SUPPORTED,
+                            "Cursor does not support moves");
+               return FALSE;
+       }
+
+       g_object_ref (cursor);
+       success = (* E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->move_by) (cursor,
+                                                                     revision_guard,
+                                                                     origin,
+                                                                     count,
+                                                                     &local_results,
+                                                                     error);
+       g_object_unref (cursor);
+
+       if (success) {
+
+               /* Calculate new cursor position and notify change */
+               calculate_move_by_position (cursor, origin, count,
+                                           g_slist_length (local_results));
+
+               if (results)
+                       *results = local_results;
+               else
+                       g_slist_free_full (local_results, (GDestroyNotify)g_free);
+       }
+
+       return success;
+}
+
+/**
+ * e_data_book_cursor_set_alphabetic_index:
+ * @cursor: an #EDataBookCursor
+ * @index: the alphabetic index
+ * @locale: the locale in which @index is expected to be a valid alphabetic index
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Sets the current cursor position to point to an index into the
+ * alphabet active in @locale.
+ *
+ * After setting the target to an alphabetic index, for example the
+ * index for letter 'E', then further calls to e_data_book_cursor_move_by()
+ * will return results starting with the letter 'E' (or results starting
+ * with the last result in 'D', if moving in a negative direction).
+ *
+ * The passed index must be a valid index in @locale, if by some chance
+ * the addressbook backend has changed into a new locale after this
+ * call has been issued, an %E_CLIENT_ERROR_OUT_OF_SYNC error will be
+ * issued indicating that there was a locale mismatch.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+gboolean
+e_data_book_cursor_set_alphabetic_index (EDataBookCursor     *cursor,
+                                        gint                 index,
+                                        const gchar         *locale,
+                                        GError             **error)
+{
+       GError *local_error = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
+
+       g_object_ref (cursor);
+
+       if (E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->set_alphabetic_index) {
+               success = (* E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->set_alphabetic_index) (cursor,
+                                                                                          index,
+                                                                                          locale,
+                                                                                          error);
+
+               /* We already set the new cursor value, we can't fail anymore so just fire a warning */
+               if (!e_data_book_cursor_recalculate (cursor, &local_error)) {
+                       g_warning ("Failed to recalculate the cursor value "
+                                  "after setting the alphabetic index: %s",
+                                  local_error->message);
+                       g_clear_error (&local_error);
+               }
+
+       } else {
+               g_set_error (error,
+                            E_CLIENT_ERROR,
+                            E_CLIENT_ERROR_NOT_SUPPORTED,
+                            "Cursor does not support alphabetic indexes");
+               success = FALSE;
+       }
+
+       g_object_unref (cursor);
+
+       return success;
+}
+
+/**
+ * e_data_book_cursor_recalculate:
+ * @cursor: an #EDataBookCursor
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Recalculates the cursor's total and position, this is meant
+ * for cursor created in Direct Read Access mode to synchronously
+ * recalculate the position and total values when the addressbook
+ * revision has changed.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+gboolean
+e_data_book_cursor_recalculate (EDataBookCursor     *cursor,
+                               GError             **error)
+{
+       gint total = 0;
+       gint position = 0;
+       gboolean success = FALSE;
+
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
+
+       /* Bad programming error */
+       if (!E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->get_position) {
+               g_critical ("EDataBookCursor.get_position() unimplemented on type '%s'",
+                           G_OBJECT_TYPE_NAME (cursor));
+
+               return FALSE;
+       }
+
+       g_object_ref (cursor);
+       success =  (* E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->get_position) (cursor,
+                                                                           &total,
+                                                                           &position,
+                                                                           error);
+       g_object_unref (cursor);
+
+       if (success)
+               data_book_cursor_set_values (cursor, total, position);
+
+       return success;
+}
+
+/**
+ * e_data_book_cursor_load_locale:
+ * @cursor: an #EDataBookCursor
+ * @locale: (out) (allow-none): return location for the locale
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Load the current locale setting from the cursor's underlying database.
+ *
+ * Addressbook backends implementing cursors should call this function on all active
+ * cursor when the locale setting changes.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+gboolean
+e_data_book_cursor_load_locale (EDataBookCursor     *cursor,
+                               gchar              **locale,
+                               GError             **error)
+{
+       EDataBookCursorPrivate *priv;
+       gboolean success;
+       gchar *local_locale = NULL;
+
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
+
+       priv = cursor->priv;
+
+       if (!E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->load_locale) {
+               g_critical ("EDataBookCursor.load_locale() unimplemented on type '%s'",
+                           G_OBJECT_TYPE_NAME (cursor));
+               return FALSE;
+       }
+
+       g_object_ref (cursor);
+       success = (* E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->load_locale) (cursor, &local_locale, error);
+       g_object_unref (cursor);
+
+       /* Changed ! Reset the thing */
+       if (g_strcmp0 (priv->locale, local_locale) != 0) {
+               GError *local_error = NULL;
+
+               g_free (priv->locale);
+               priv->locale = g_strdup (local_locale);
+
+               if (!e_data_book_cursor_move_by (cursor, NULL,
+                                                E_BOOK_CURSOR_ORIGIN_RESET,
+                                                0, NULL, &local_error)) {
+                       g_warning ("Error resetting cursor position after locale change: %s",
+                                  local_error->message);
+                       g_clear_error (&local_error);
+               } else if (!e_data_book_cursor_recalculate (E_DATA_BOOK_CURSOR (cursor),
+                                                           error)) {
+                       g_warning ("Error recalculating cursor position after locale change: %s",
+                                  local_error->message);
+                       g_clear_object (&cursor);
+               }
+       }
+
+       if (locale)
+               *locale = local_locale;
+       else
+               g_free (local_locale);
+
+       return success;
+}
+
+/**
+ * e_data_book_cursor_contact_added:
+ * @cursor: an #EDataBookCursor
+ * @contact: the #EContact which was added to the addressbook
+ *
+ * Should be called by addressbook backends whenever a contact
+ * is added.
+ *
+ * Since: 3.10
+ */
+void
+e_data_book_cursor_contact_added (EDataBookCursor     *cursor,
+                                 EContact            *contact)
+{
+       EDataBookCursorPrivate *priv;
+       gint comparison = 0;
+       gboolean matches_sexp = FALSE;
+       gint new_total, new_position;
+
+       g_return_if_fail (E_IS_DATA_BOOK_CURSOR (cursor));
+       g_return_if_fail (E_IS_CONTACT (contact));
+
+       priv = cursor->priv;
+
+       if (!data_book_cursor_compare (cursor, contact, &comparison, &matches_sexp)) {
+               GError *error = NULL;
+
+               /* Comparisons not supported, must recalculate entirely */
+               if (!e_data_book_cursor_recalculate (cursor, &error)) {
+                       g_warning ("Failed to recalculate the cursor value "
+                                  "after a contact was added: %s",
+                                  error->message);
+                       g_clear_error (&error);
+               }
+
+               return;
+       }
+
+       /* The added contact doesnt match the cursor search expression, no need
+        * to change the position & total values
+        */
+       if (!matches_sexp)
+               return;
+
+       new_total = priv->total;
+       new_position = priv->position;
+
+       /* One new contact */
+       new_total++;
+
+       /* New contact was added at cursor position or before cursor position */
+       if (comparison <= 0)
+               new_position++;
+
+       /* Notify total & position change */
+       data_book_cursor_set_values (cursor, new_total, new_position);
+}
+
+/**
+ * e_data_book_cursor_contact_removed:
+ * @cursor: an #EDataBookCursor
+ * @contact: the #EContact which was removed from the addressbook
+ *
+ * Should be called by addressbook backends whenever a contact
+ * is removed.
+ *
+ * Since: 3.10
+ */
+void
+e_data_book_cursor_contact_removed (EDataBookCursor     *cursor,
+                                   EContact            *contact)
+{
+       EDataBookCursorPrivate *priv;
+       gint comparison = 0;
+       gboolean matches_sexp = FALSE;
+       gint new_total, new_position;
+
+       g_return_if_fail (E_IS_DATA_BOOK_CURSOR (cursor));
+       g_return_if_fail (E_IS_CONTACT (contact));
+
+       priv = cursor->priv;
+
+       if (!data_book_cursor_compare (cursor, contact, &comparison, &matches_sexp)) {
+               GError *error = NULL;
+
+               /* Comparisons not supported, must recalculate entirely */
+               if (!e_data_book_cursor_recalculate (cursor, &error)) {
+                       g_warning ("Failed to recalculate the cursor value "
+                                  "after a contact was added: %s",
+                                  error->message);
+                       g_clear_error (&error);
+               }
+
+               return;
+       }
+
+       /* The removed contact did not match the cursor search expression, no need
+        * to change the position & total values
+        */
+       if (!matches_sexp)
+               return;
+
+       new_total = priv->total;
+       new_position = priv->position;
+
+       /* One less contact */
+       new_total--;
+
+       /* Removed contact was the exact cursor position or before cursor position */
+       if (comparison <= 0)
+               new_position--;
+
+       /* Notify total & position change */
+       data_book_cursor_set_values (cursor, new_total, new_position);
+}
+
+/**
+ * e_data_book_cursor_register_gdbus_object:
+ * @cursor: an #EDataBookCursor
+ * @connection: the #GDBusConnection to register with
+ * @object_path: the object path to place the direct access configuration data
+ * @error: (out) (allow-none): a location to store any error which might occur while registering
+ *
+ * Places @cursor on the @connection at @object_path
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+gboolean
+e_data_book_cursor_register_gdbus_object (EDataBookCursor     *cursor,
+                                         GDBusConnection     *connection,
+                                         const gchar         *object_path,
+                                         GError             **error)
+{
+       EDataBookCursorPrivate *priv;
+
+       g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
+       g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+       g_return_val_if_fail (object_path != NULL, FALSE);
+
+       priv = cursor->priv;
+
+       if (!priv->dbus_object) {
+               priv->dbus_object = e_dbus_address_book_cursor_skeleton_new ();
+
+               g_signal_connect (priv->dbus_object, "handle-move-by",
+                                 G_CALLBACK (data_book_cursor_handle_move_by), cursor);
+               g_signal_connect (priv->dbus_object, "handle-set-alphabetic-index",
+                                 G_CALLBACK (data_book_cursor_handle_set_alphabetic_index), cursor);
+               g_signal_connect (priv->dbus_object, "handle-set-query",
+                                 G_CALLBACK (data_book_cursor_handle_set_query), cursor);
+               g_signal_connect (priv->dbus_object, "handle-dispose",
+                                 G_CALLBACK (data_book_cursor_handle_dispose), cursor);
+
+
+               /* Set initial total / position */
+               e_dbus_address_book_cursor_set_total (priv->dbus_object, priv->total);
+               e_dbus_address_book_cursor_set_position (priv->dbus_object, priv->position);
+       }
+
+       return g_dbus_interface_skeleton_export (
+               G_DBUS_INTERFACE_SKELETON (priv->dbus_object),
+               connection, object_path, error);
+}
diff --git a/addressbook/libedata-book/e-data-book-cursor.h b/addressbook/libedata-book/e-data-book-cursor.h
new file mode 100644
index 0000000..1c00c00
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor.h
@@ -0,0 +1,294 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * 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 of the License, or (at your option) version 3.
+ *
+ * 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+
+#if !defined (__LIBEDATA_BOOK_H_INSIDE__) && !defined (LIBEDATA_BOOK_COMPILATION)
+#error "Only <libedata-book/libedata-book.h> should be included directly."
+#endif
+
+#ifndef E_DATA_BOOK_CURSOR_H
+#define E_DATA_BOOK_CURSOR_H
+
+#include <gio/gio.h>
+#include <libebook-contacts/libebook-contacts.h>
+
+#define E_TYPE_DATA_BOOK_CURSOR        (e_data_book_cursor_get_type ())
+#define E_DATA_BOOK_CURSOR(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_DATA_BOOK_CURSOR, 
EDataBookCursor))
+#define E_DATA_BOOK_CURSOR_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), E_TYPE_DATA_BOOK_CURSOR, 
EDataBookCursorClass))
+#define E_IS_DATA_BOOK_CURSOR(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_DATA_BOOK_CURSOR))
+#define E_IS_DATA_BOOK_CURSOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_DATA_BOOK_CURSOR))
+#define E_DATA_BOOK_CURSOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), E_TYPE_DATA_BOOK_CURSOR, 
EDataBookCursorClass))
+
+G_BEGIN_DECLS
+
+struct _EBookBackend;
+
+typedef struct _EDataBookCursor EDataBookCursor;
+typedef struct _EDataBookCursorClass EDataBookCursorClass;
+typedef struct _EDataBookCursorPrivate EDataBookCursorPrivate;
+
+
+/*
+ * The following virtual methods have typedefs in order to provide richer 
+ * documentation about how to implement the EDataBookCursorClass.
+ */
+
+/**
+ * EDataBookCursorSetSexpFunc:
+ * @cursor: an #EDataBookCursor
+ * @sexp: (allow-none): the search expression to set, or %NULL for unfiltered results
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Method type for #EDataBookCursorClass.set_sexp()
+ *
+ * A cursor implementation must implement this in order to modify the search
+ * expression for @cursor, the implementation need not worry about notifying
+ * any cursor state change.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+typedef gboolean (*EDataBookCursorSetSexpFunc) (EDataBookCursor     *cursor,
+                                               const gchar         *sexp,
+                                               GError             **error);
+
+/**
+ * EDataBookCursorMoveByFunc:
+ * @cursor: an #EDataBookCursor
+ * @revision_guard: (allow-none): The expected current addressbook revision, or %NULL
+ * @origin: the #EBookCursorOrigin for this move
+ * @count: a positive or negative amount of contacts to try and fetch
+ * @results: (out) (allow-none) (element-type utf8) (transfer full):
+ *   A return location to store the results, or %NULL to move the cursor without retrieving any results.
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Method type for #EDataBookCursorClass.set_sexp()
+ *
+ * As all cursor methods may be called either by the addressbook service or
+ * directly by a client in Direct Read Access mode, it is important that the
+ * operation be an atomic transaction with the underlying database.
+ *
+ * The @revision_guard, if specified, should be checked against the addressbook
+ * revision as the first operation in an atomic transaction with the addressbook
+ * underlying database.
+ *
+ * See e_data_book_cursor_move_by() for more details on the expected behaviour of this method.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+typedef gboolean (*EDataBookCursorMoveByFunc) (EDataBookCursor     *cursor,
+                                              const gchar         *revision_guard,
+                                              EBookCursorOrigin    origin,
+                                              gint                 count,
+                                              GSList             **results,
+                                              GError             **error);
+
+/**
+ * EDataBookCursorSetAlphabetIndexFunc:
+ * @cursor: an #EDataBookCursor
+ * @index: the alphabetic index
+ * @locale: the locale in which @index is expected to be a valid alphabetic index
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Method type for #EDataBookCursorClass.set_alphabetic_index()
+ *
+ * Sets the current cursor position to point to an index into the
+ * alphabet active in @locale.
+ *
+ * The implementing class must check that @locale matches the current
+ * locale setting of the underlying database and report an %E_CLIENT_ERROR_OUT_OF_SYNC
+ * error in the case that the locales do not match.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+typedef gboolean (*EDataBookCursorSetAlphabetIndexFunc) (EDataBookCursor     *cursor,
+                                                        gint                 index,
+                                                        const gchar         *locale,
+                                                        GError             **error);
+
+/**
+ * EDataBookCursorGetPositionFunc:
+ * @cursor: an #EDataBookCursor
+ * @total: (out): The total number of contacts matching @cursor's query expression
+ * @position: (out): The current position of @cursor in it's result list
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Method type for #EDataBookCursorClass.get_position()
+ *
+ * Cursor implementations must implement this to count the total results
+ * matching @cursor's query expression and to calculate the current relative
+ * position of @cursor in it's results.
+ *
+ * A cursor position is defined as an integer which is inclusive of the
+ * current contact to which it points (if the cursor points to an exact
+ * contact). A position of %0 indicates that the cursor is situated in
+ * a position that is before and after the entire result set. The cursor
+ * position should be %0 at creation time, and should start again from
+ * the symbolic %0 position whenever %E_BOOK_CURSOR_ORIGIN_RESET is
+ * specified in the #EDataBookCursorClass.move_by() method.
+ *
+ * This method is called by e_data_book_cursor_recalculate() and in some
+ * other cases where @cursor's current position and total must be
+ * recalculated from scratch.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+typedef gboolean (*EDataBookCursorGetPositionFunc) (EDataBookCursor     *cursor,
+                                                   gint                *total,
+                                                   gint                *position,
+                                                   GError             **error);
+
+/**
+ * EDataBookCursorCompareFunc:
+ * @cursor: an #EDataBookCursor
+ * @contact: the #EContact to compare with @cursor
+ * @matches_sexp: (out) (allow-none): return location to set whether @contact matched @cursor's search 
expression
+ *
+ * Method type for #EDataBookCursorClass.compare()
+ *
+ * Cursor implementations should implement this in order to compare a
+ * contact with the current cursor values.
+ *
+ * This is called when the addressbook backends notify active cursors
+ * that the addressbook has been modified with e_data_book_cursor_contact_added() and
+ * e_data_book_cursor_contact_removed(). Comparing the changed contact details is usually
+ * much less of an intensive operation than calling #EDataBookCursorClass.get_position(),
+ * however if this method is left unimplemented then #EDataBookCursorClass.get_position()
+ * will be used in it's place to recalculate the cursor position and total from scratch.
+ *
+ * Returns: A value that is less than, equal to, or greater than zero if @contact is found,
+ * respectively, to be less than, to match, or be greater than the current value of @cursor.
+ *
+ * Since: 3.10
+ */
+typedef gint (*EDataBookCursorCompareFunc) (EDataBookCursor     *cursor,
+                                           EContact            *contact,
+                                           gboolean            *matches_sexp);
+
+/**
+ * EDataBookCursorLoadLocaleFunc:
+ * @cursor: an #EDataBookCursor
+ * @total: (out): The total number of contacts matching @cursor's query expression
+ * @position: (out): The current position of @cursor in it's result list
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Method type for #EDataBookCursorClass.load_locale()
+ *
+ * Fetches the locale setting from the active addressbook
+ *
+ * If the locale has changed, then the cursor implementation is
+ * responsible for clearing it's internal state so that it's position
+ * is again at the initial symbolic %0 position, and then calling
+ * e_data_book_cursor_recalculate().
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.10
+ */
+typedef gboolean (*EDataBookCursorLoadLocaleFunc) (EDataBookCursor     *cursor,
+                                                  gchar              **locale,
+                                                  GError             **error);
+
+/**
+ * EDataBookCursor:
+ *
+ * An opaque handle for an addressbook cursor
+ *
+ * Since: 3.10
+ */
+struct _EDataBookCursor {
+       /*< public >*/
+       GObject parent;
+
+       /*< private >*/
+       EDataBookCursorPrivate *priv;
+};
+
+/**
+ * EDataBookCursorClass:
+ * @set_sexp: The #EDataBookCursorSetSexpFunc delegate to set the search expression
+ * @move_by: The #EDataBookCursorMoveByFunc delegate to navigate the cursor
+ * @set_alphabetic_index: The #EDataBookCursorSetAlphabetIndexFunc delegate to set the alphabetic position
+ * @get_position: The #EDataBookCursorGetPositionFunc delegate to calculate the current total and position 
values
+ * @compare: The #EDataBookCursorCompareFunc delegate to compare an #EContact with the the cursor position
+ * @load_locale: The #EDataBookCursorLoadLocaleFunc delegate used to reload the locale setting
+ *
+ * Methods to implement on an #EDataBookCursor concrete class.
+ *
+ * Since 3.10
+ */
+struct _EDataBookCursorClass {
+       /*< public >*/
+       GObjectClass parent;
+
+       EDataBookCursorSetSexpFunc set_sexp;
+       EDataBookCursorMoveByFunc move_by;
+       EDataBookCursorSetAlphabetIndexFunc set_alphabetic_index;
+       EDataBookCursorGetPositionFunc get_position;
+       EDataBookCursorCompareFunc compare;
+       EDataBookCursorLoadLocaleFunc load_locale;
+};
+
+GType                  e_data_book_cursor_get_type              (void);
+
+struct _EBookBackend   *e_data_book_cursor_get_backend           (EDataBookCursor     *cursor);
+gint                    e_data_book_cursor_get_total             (EDataBookCursor     *cursor);
+gint                    e_data_book_cursor_get_position          (EDataBookCursor     *cursor);
+gboolean                e_data_book_cursor_set_sexp              (EDataBookCursor     *cursor,
+                                                                 const gchar         *sexp,
+                                                                 GError             **error);
+gboolean                e_data_book_cursor_move_by               (EDataBookCursor     *cursor,
+                                                                 const gchar         *revision_guard,
+                                                                 EBookCursorOrigin    origin,
+                                                                 gint                 count,
+                                                                 GSList             **results,
+                                                                 GError             **error);
+gboolean                e_data_book_cursor_set_alphabetic_index  (EDataBookCursor     *cursor,
+                                                                 gint                 index,
+                                                                 const gchar         *locale,
+                                                                 GError             **error);
+gboolean                e_data_book_cursor_recalculate           (EDataBookCursor     *cursor,
+                                                                 GError             **error);
+gboolean                e_data_book_cursor_load_locale           (EDataBookCursor     *cursor,
+                                                                 gchar              **locale,
+                                                                 GError             **error);
+void                    e_data_book_cursor_contact_added         (EDataBookCursor     *cursor,
+                                                                 EContact            *contact);
+void                    e_data_book_cursor_contact_removed       (EDataBookCursor     *cursor,
+                                                                 EContact            *contact);
+gboolean                e_data_book_cursor_register_gdbus_object (EDataBookCursor     *cursor,
+                                                                 GDBusConnection     *connection,
+                                                                 const gchar         *object_path,
+                                                                 GError             **error);
+
+G_END_DECLS
+
+#endif /* E_DATA_BOOK_CURSOR_H */



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