[evolution-data-server/cursor-staging: 16/26] Added EDataBookCursor & EDataBookCursorSqlite



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

    Added EDataBookCursor & EDataBookCursorSqlite
    
    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.
    
    EDataBookCursorSqlite is a cursor implementation which uses
    a EBookBackendSqliteDB cursor.

 .../libebook-contacts/e-book-contacts-types.h      |   21 +
 addressbook/libedata-book/Makefile.am              |    4 +
 .../libedata-book/e-data-book-cursor-sqlite.c      |  519 +++++++++
 .../libedata-book/e-data-book-cursor-sqlite.h      |   81 ++
 addressbook/libedata-book/e-data-book-cursor.c     | 1161 ++++++++++++++++++++
 addressbook/libedata-book/e-data-book-cursor.h     |  307 ++++++
 addressbook/libedata-book/libedata-book.h          |    4 +-
 po/POTFILES.in                                     |    3 +
 8 files changed, 2099 insertions(+), 1 deletions(-)
---
diff --git a/addressbook/libebook-contacts/e-book-contacts-types.h 
b/addressbook/libebook-contacts/e-book-contacts-types.h
index 1f9d7fc..4287156 100644
--- a/addressbook/libebook-contacts/e-book-contacts-types.h
+++ b/addressbook/libebook-contacts/e-book-contacts-types.h
@@ -149,6 +149,27 @@ typedef enum {
        E_BOOK_CURSOR_SORT_DESCENDING
 } EBookCursorSortType;
 
+/**
+ * EBookCursorOrigin:
+ * @E_BOOK_CURSOR_ORIGIN_CURRENT:  The current cursor position
+ * @E_BOOK_CURSOR_ORIGIN_PREVIOUS: The previously recorded cursor position, this can be used to repeat the 
previous query
+ * @E_BOOK_CURSOR_ORIGIN_RESET:    The beginning of the cursor results (or end of the results, if navigating 
in reverse).
+ *
+ * Defines the behaviour of e_book_client_cursor_move_by().
+ *
+ * The cursor always saves the previous cursor position as well as
+ * the new cursor position after performing a move. This allows
+ * cursor queries to be repeated in the case where content may have
+ * changed but the same content window should be refreshed in a UI.
+ *
+ * Since: 3.12
+ */
+typedef enum {
+       E_BOOK_CURSOR_ORIGIN_CURRENT,
+       E_BOOK_CURSOR_ORIGIN_PREVIOUS,
+       E_BOOK_CURSOR_ORIGIN_RESET
+} EBookCursorOrigin;
+
 GQuark         e_book_client_error_quark       (void) G_GNUC_CONST;
 const gchar *  e_book_client_error_to_string   (EBookClientError code);
 
diff --git a/addressbook/libedata-book/Makefile.am b/addressbook/libedata-book/Makefile.am
index 2779cc0..880d1e6 100644
--- a/addressbook/libedata-book/Makefile.am
+++ b/addressbook/libedata-book/Makefile.am
@@ -29,6 +29,8 @@ libedata_book_1_2_la_SOURCES = \
        e-book-backend-sqlitedb.c \
        e-book-backend.c \
        e-data-book.c \
+       e-data-book-cursor.c \
+       e-data-book-cursor-sqlite.c \
        e-data-book-direct.c \
        e-data-book-factory.c \
        e-data-book-view.c \
@@ -60,6 +62,8 @@ libedata_bookinclude_HEADERS = \
        e-data-book-factory.h \
        e-data-book-view.h \
        e-data-book.h \
+       e-data-book-cursor.h \
+       e-data-book-cursor-sqlite.h \
        e-data-book-direct.h \
        e-book-backend-cache.h \
        e-book-backend-sqlitedb.h \
diff --git a/addressbook/libedata-book/e-data-book-cursor-sqlite.c 
b/addressbook/libedata-book/e-data-book-cursor-sqlite.c
new file mode 100644
index 0000000..99bad53
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor-sqlite.c
@@ -0,0 +1,519 @@
+/* -*- 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>
+ */
+
+/**
+ * SECTION: e-data-book-cursor-sqlite
+ * @include: libedata-book/libedata-book.h
+ * @short_description: The SQLite cursor implementation
+ *
+ * This cursor implementation can be used with any backend which
+ * stores contacts using #EBookBackendSqliteDB.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "e-data-book-cursor-sqlite.h"
+
+/* GObjectClass */
+static void e_data_book_cursor_sqlite_dispose      (GObject *object);
+static void e_data_book_cursor_sqlite_finalize     (GObject *object);
+static void e_data_book_cursor_sqlite_set_property (GObject *object,
+                                                   guint property_id,
+                                                   const GValue *value,
+                                                   GParamSpec *pspec);
+
+/* EDataBookCursorClass */
+static gboolean e_data_book_cursor_sqlite_set_sexp             (EDataBookCursor     *cursor,
+                                                               const gchar         *sexp,
+                                                               GError             **error);
+static gboolean e_data_book_cursor_sqlite_move_by              (EDataBookCursor     *cursor,
+                                                               const gchar         *revision_guard,
+                                                               EBookCursorOrigin    origin,
+                                                               gint                 count,
+                                                               GSList             **results,
+                                                               GError             **error);
+static gboolean e_data_book_cursor_sqlite_set_alphabetic_index (EDataBookCursor     *cursor,
+                                                               gint                 index,
+                                                               const gchar         *locale,
+                                                               GError             **error);
+static gboolean e_data_book_cursor_sqlite_get_position         (EDataBookCursor     *cursor,
+                                                               gint                *total,
+                                                               gint                *position,
+                                                               GError             **error);
+static gint     e_data_book_cursor_sqlite_compare_contact      (EDataBookCursor     *cursor,
+                                                               EContact            *contact,
+                                                               gboolean            *matches_sexp);
+static gboolean e_data_book_cursor_sqlite_load_locale          (EDataBookCursor     *cursor,
+                                                               gchar              **locale,
+                                                               GError             **error);
+
+struct _EDataBookCursorSqlitePrivate {
+       EBookBackendSqliteDB *ebsdb;
+       EbSdbCursor          *cursor;
+       gchar                *folder_id;
+};
+
+enum {
+       PROP_0,
+       PROP_EBSDB,
+       PROP_FOLDER_ID,
+       PROP_CURSOR,
+};
+
+G_DEFINE_TYPE (EDataBookCursorSqlite, e_data_book_cursor_sqlite, E_TYPE_DATA_BOOK_CURSOR);
+
+/************************************************
+ *                  GObjectClass                *
+ ************************************************/
+static void
+e_data_book_cursor_sqlite_class_init (EDataBookCursorSqliteClass *class)
+{
+       GObjectClass *object_class;
+       EDataBookCursorClass *cursor_class;
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->dispose = e_data_book_cursor_sqlite_dispose;
+       object_class->finalize = e_data_book_cursor_sqlite_finalize;
+       object_class->set_property = e_data_book_cursor_sqlite_set_property;
+
+       cursor_class = E_DATA_BOOK_CURSOR_CLASS (class);
+       cursor_class->set_sexp = e_data_book_cursor_sqlite_set_sexp;
+       cursor_class->move_by = e_data_book_cursor_sqlite_move_by;
+       cursor_class->set_alphabetic_index = e_data_book_cursor_sqlite_set_alphabetic_index;
+       cursor_class->get_position = e_data_book_cursor_sqlite_get_position;
+       cursor_class->compare_contact = e_data_book_cursor_sqlite_compare_contact;
+       cursor_class->load_locale = e_data_book_cursor_sqlite_load_locale;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_EBSDB,
+               g_param_spec_object (
+                       "ebsdb", "EBookBackendSqliteDB",
+                       "The EBookBackendSqliteDB to use for queries",
+                       E_TYPE_BOOK_BACKEND_SQLITEDB,
+                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_FOLDER_ID,
+               g_param_spec_string (
+                       "folder-id", "Folder ID",
+                       "The folder identifier to use with the EBookBackendSqliteDB object",
+                       NULL,
+                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_CURSOR,
+               g_param_spec_pointer (
+                       "cursor", "Cursor",
+                       "The EbSdbCursor pointer",
+                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+       g_type_class_add_private (class, sizeof (EDataBookCursorSqlitePrivate));
+}
+
+static void
+e_data_book_cursor_sqlite_init (EDataBookCursorSqlite *cursor)
+{
+       cursor->priv = 
+         G_TYPE_INSTANCE_GET_PRIVATE (cursor,
+                                      E_TYPE_DATA_BOOK_CURSOR_SQLITE,
+                                      EDataBookCursorSqlitePrivate);
+}
+
+static void
+e_data_book_cursor_sqlite_dispose (GObject *object)
+{
+       EDataBookCursorSqlite        *cursor = E_DATA_BOOK_CURSOR_SQLITE (object);
+       EDataBookCursorSqlitePrivate *priv = cursor->priv;
+
+       if (priv->ebsdb) {
+
+               if (priv->cursor)
+                       e_book_backend_sqlitedb_cursor_free (priv->ebsdb,
+                                                            priv->cursor);
+
+               g_object_unref (priv->ebsdb);
+               priv->ebsdb = NULL;
+               priv->cursor = NULL;
+       }
+
+       G_OBJECT_CLASS (e_data_book_cursor_sqlite_parent_class)->dispose (object);
+}
+
+static void
+e_data_book_cursor_sqlite_finalize (GObject *object)
+{
+       EDataBookCursorSqlite        *cursor = E_DATA_BOOK_CURSOR_SQLITE (object);
+       EDataBookCursorSqlitePrivate *priv = cursor->priv;
+
+       g_free (priv->folder_id);
+
+       G_OBJECT_CLASS (e_data_book_cursor_sqlite_parent_class)->finalize (object);
+}
+
+static void
+e_data_book_cursor_sqlite_set_property (GObject *object,
+                                       guint property_id,
+                                       const GValue *value,
+                                       GParamSpec *pspec)
+{
+       EDataBookCursorSqlite        *cursor = E_DATA_BOOK_CURSOR_SQLITE (object);
+       EDataBookCursorSqlitePrivate *priv = cursor->priv;
+
+       switch (property_id) {
+       case PROP_EBSDB:
+               /* Construct-only, can only be set once */
+               priv->ebsdb = g_value_dup_object (value);
+               break;
+       case PROP_FOLDER_ID:
+               /* Construct-only, can only be set once */
+               priv->folder_id = g_value_dup_string (value);
+               break;
+       case PROP_CURSOR:
+               /* Construct-only, can only be set once */
+               priv->cursor = g_value_get_pointer (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+/************************************************
+ *            EDataBookCursorClass              *
+ ************************************************/
+static gboolean
+e_data_book_cursor_sqlite_set_sexp (EDataBookCursor     *cursor,
+                                   const gchar         *sexp,
+                                   GError             **error)
+{
+       EDataBookCursorSqlite *cursor_sqlite;
+       EDataBookCursorSqlitePrivate *priv;
+       GError *local_error = NULL;
+       gboolean success;
+
+       cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
+       priv = cursor_sqlite->priv;
+
+       success = e_book_backend_sqlitedb_cursor_set_sexp (priv->ebsdb,
+                                                          priv->cursor,
+                                                          sexp,
+                                                          &local_error);
+
+       if (!success) {
+               if (g_error_matches (local_error,
+                                    E_BOOK_SDB_ERROR,
+                                    E_BOOK_SDB_ERROR_INVALID_QUERY)) {
+                       g_set_error_literal (error,
+                                            E_CLIENT_ERROR,
+                                            E_CLIENT_ERROR_INVALID_QUERY,
+                                            local_error->message);
+                       g_clear_error (&local_error);
+               } else {
+                       g_propagate_error (error, local_error);
+               }
+       }
+
+       return success;
+}
+
+static gboolean
+convert_origin (EBookCursorOrigin    src_origin,
+               EbSdbCursorOrigin   *dest_origin,
+               GError             **error)
+{
+       gboolean success = TRUE;
+
+       switch (src_origin) {
+       case E_BOOK_CURSOR_ORIGIN_CURRENT:
+               *dest_origin = EBSDB_CURSOR_ORIGIN_CURRENT;
+               break;
+       case E_BOOK_CURSOR_ORIGIN_PREVIOUS:
+               *dest_origin = EBSDB_CURSOR_ORIGIN_PREVIOUS;
+               break;
+       case E_BOOK_CURSOR_ORIGIN_RESET:
+               *dest_origin = EBSDB_CURSOR_ORIGIN_RESET;
+               break;
+       default:
+               success = FALSE;
+               g_set_error_literal (error,
+                                    E_CLIENT_ERROR,
+                                    E_CLIENT_ERROR_INVALID_ARG,
+                                    _("Unrecognized cursor origin"));
+               break;
+       }
+
+       return success;
+}
+
+static gboolean
+e_data_book_cursor_sqlite_move_by (EDataBookCursor     *cursor,
+                                  const gchar         *revision_guard,
+                                  EBookCursorOrigin    origin,
+                                  gint                 count,
+                                  GSList             **results,
+                                  GError             **error)
+{
+       EDataBookCursorSqlite *cursor_sqlite;
+       EDataBookCursorSqlitePrivate *priv;
+       GSList *local_results = NULL, *local_converted_results = NULL, *l;
+       EbSdbCursorOrigin sqlitedb_origin = EBSDB_CURSOR_ORIGIN_CURRENT;
+       gchar *revision = NULL;
+       gboolean success = FALSE;
+
+       cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
+       priv = cursor_sqlite->priv;
+
+       if (!convert_origin (origin, &sqlitedb_origin, error))
+               return FALSE;
+
+       /* Here we check the EBookBackendSqliteDB revision
+        * against the revision_guard with an atomic transaction
+        * with the sqlite.
+        *
+        * The addressbook modifications and revision changes
+        * are also atomically committed to the SQLite.
+        */
+       success = e_book_backend_sqlitedb_lock_updates (priv->ebsdb, error);
+
+       if (success && revision_guard)
+               success = e_book_backend_sqlitedb_get_revision (priv->ebsdb,
+                                                               priv->folder_id,
+                                                               &revision,
+                                                               error);
+
+       if (success && revision_guard &&
+           g_strcmp0 (revision, revision_guard) != 0) {
+
+               g_set_error_literal (error,
+                                    E_CLIENT_ERROR,
+                                    E_CLIENT_ERROR_OUT_OF_SYNC,
+                                    _("Out of sync revision while moving cursor"));
+               success = FALSE;
+       }
+
+       if (success)
+               success = e_book_backend_sqlitedb_cursor_move_by (priv->ebsdb,
+                                                                 priv->cursor,
+                                                                 sqlitedb_origin,
+                                                                 count,
+                                                                 &local_results,
+                                                                 error);
+
+       if (success) {
+               success = e_book_backend_sqlitedb_unlock_updates (priv->ebsdb, TRUE, error);
+
+       } else {
+               GError *local_error = NULL;
+
+               /* Rollback transaction */
+               if (!e_book_backend_sqlitedb_unlock_updates (priv->ebsdb, FALSE, &local_error)) {
+                       g_warning ("Failed to rollback transaction after failing move cursor: %s",
+                                  local_error->message);
+                       g_clear_error (&local_error);
+               }
+       }
+
+       for (l = local_results; l; l = l->next) {
+               EbSdbSearchData *data = l->data;
+
+               local_converted_results =
+                       g_slist_prepend (local_converted_results, data->vcard);
+               data->vcard = NULL;
+       }
+
+       g_slist_free_full (local_results, (GDestroyNotify)e_book_backend_sqlitedb_search_data_free);
+
+       if (results)
+               *results = g_slist_reverse (local_converted_results);
+       else
+               g_slist_free_full (local_converted_results, (GDestroyNotify)g_free);
+
+       g_free (revision);
+
+       return success;
+}
+
+static gboolean
+e_data_book_cursor_sqlite_set_alphabetic_index (EDataBookCursor     *cursor,
+                                               gint                 index,
+                                               const gchar         *locale,
+                                               GError             **error)
+{
+       EDataBookCursorSqlite *cursor_sqlite;
+       EDataBookCursorSqlitePrivate *priv;
+       gchar *current_locale = NULL;
+
+       cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
+       priv = cursor_sqlite->priv;
+
+       if (!e_book_backend_sqlitedb_get_locale (priv->ebsdb, priv->folder_id,
+                                                &current_locale, error))
+               return FALSE;
+
+       /* Locale mismatch, need to report error */
+       if (g_strcmp0 (current_locale, locale) != 0) {
+               g_set_error_literal (error,
+                                    E_CLIENT_ERROR,
+                                    E_CLIENT_ERROR_OUT_OF_SYNC,
+                                    _("Alphabetic index was set for incorrect locale"));
+               g_free (current_locale);
+               return FALSE;
+       }
+
+       e_book_backend_sqlitedb_cursor_set_target_alphabetic_index (priv->ebsdb,
+                                                                   priv->cursor,
+                                                                   index);
+       g_free (current_locale);
+       return TRUE;
+}
+
+static gboolean
+e_data_book_cursor_sqlite_get_position (EDataBookCursor     *cursor,
+                                       gint                *total,
+                                       gint                *position,
+                                       GError             **error)
+{
+       EDataBookCursorSqlite *cursor_sqlite;
+       EDataBookCursorSqlitePrivate *priv;
+
+       cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
+       priv = cursor_sqlite->priv;
+
+       return e_book_backend_sqlitedb_cursor_calculate (priv->ebsdb,
+                                                        priv->cursor,
+                                                        total, position,
+                                                        error);
+}
+
+static gint
+e_data_book_cursor_sqlite_compare_contact (EDataBookCursor     *cursor,
+                                          EContact            *contact,
+                                          gboolean            *matches_sexp)
+{
+       EDataBookCursorSqlite *cursor_sqlite;
+       EDataBookCursorSqlitePrivate *priv;
+
+       cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
+       priv = cursor_sqlite->priv;
+
+       return e_book_backend_sqlitedb_cursor_compare_contact (priv->ebsdb,
+                                                              priv->cursor,
+                                                              contact,
+                                                              matches_sexp);
+}
+
+static gboolean
+e_data_book_cursor_sqlite_load_locale (EDataBookCursor     *cursor,
+                                      gchar              **locale,
+                                      GError             **error)
+{
+       EDataBookCursorSqlite        *cursor_sqlite;
+       EDataBookCursorSqlitePrivate *priv;
+
+       cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
+       priv = cursor_sqlite->priv;
+
+       return e_book_backend_sqlitedb_get_locale (priv->ebsdb,
+                                                  priv->folder_id,
+                                                  locale,
+                                                  error);
+}
+
+/************************************************
+ *                       API                    *
+ ************************************************/
+/**
+ * e_data_book_cursor_sqlite_new:
+ * @backend: the #EBookBackend creating this cursor
+ * @ebsdb: the #EBookBackendSqliteDB object to base this cursor on
+ * @folder_id: the folder identifier to be used in EBookBackendSqliteDB API calls
+ * @sort_fields: (array length=n_fields): an array of #EContactFields as sort keys in order of priority
+ * @sort_types: (array length=n_fields): an array of #EBookCursorSortTypes, one for each field in 
@sort_fields
+ * @n_fields: the number of fields to sort results by.
+ * @error: a return location to story any error that might be reported.
+ *
+ * Creates an #EDataBookCursor and implements all of the cursor methods
+ * using the delegate @ebsdb object.
+ *
+ * This is a suitable cursor type for any backend which stores its contacts
+ * using the #EBookBackendSqliteDB object.
+ *
+ * Returns: (transfer full): A newly created #EDataBookCursor, or %NULL if cursor creation failed.
+ *
+ * Since: 3.12
+ */
+EDataBookCursor *
+e_data_book_cursor_sqlite_new (EBookBackend         *backend,
+                              EBookBackendSqliteDB *ebsdb,
+                              const gchar          *folder_id,
+                              EContactField        *sort_fields,
+                              EBookCursorSortType  *sort_types,
+                              guint                 n_fields,
+                              GError              **error)
+{
+       EDataBookCursor *cursor = NULL;
+       EbSdbCursor     *ebsdb_cursor;
+       GError          *local_error = NULL;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND (backend), NULL);
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITEDB (ebsdb), NULL);
+       g_return_val_if_fail (folder_id && folder_id[0], NULL);
+
+       ebsdb_cursor = e_book_backend_sqlitedb_cursor_new (ebsdb, folder_id, NULL,
+                                                          sort_fields,
+                                                          sort_types,
+                                                          n_fields,
+                                                          &local_error);
+
+       if (ebsdb_cursor) {
+               cursor = g_object_new (E_TYPE_DATA_BOOK_CURSOR_SQLITE,
+                                      "backend", backend,
+                                      "ebsdb", ebsdb,
+                                      "folder-id", folder_id,
+                                      "cursor", ebsdb_cursor,
+                                      NULL);
+
+               /* Initially created cursors should have a position & total */
+               if (!e_data_book_cursor_load_locale (E_DATA_BOOK_CURSOR (cursor),
+                                                    NULL, error))
+                       g_clear_object (&cursor);
+
+       } else if (g_error_matches (local_error,
+                                 E_BOOK_SDB_ERROR,
+                                 E_BOOK_SDB_ERROR_INVALID_QUERY)) {
+               g_set_error_literal (error,
+                                    E_CLIENT_ERROR,
+                                    E_CLIENT_ERROR_INVALID_QUERY,
+                                    local_error->message);
+               g_clear_error (&local_error);
+       } else {
+               g_propagate_error (error, local_error);
+       }
+
+       return cursor;
+}
diff --git a/addressbook/libedata-book/e-data-book-cursor-sqlite.h 
b/addressbook/libedata-book/e-data-book-cursor-sqlite.h
new file mode 100644
index 0000000..88ea577
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor-sqlite.h
@@ -0,0 +1,81 @@
+/* -*- 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_SQLITE_H
+#define E_DATA_BOOK_CURSOR_SQLITE_H
+
+#include <libedata-book/e-data-book-cursor.h>
+#include <libedata-book/e-book-backend-sqlitedb.h>
+#include <libedata-book/e-book-backend.h>
+
+#define E_TYPE_DATA_BOOK_CURSOR_SQLITE        (e_data_book_cursor_sqlite_get_type ())
+#define E_DATA_BOOK_CURSOR_SQLITE(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), 
E_TYPE_DATA_BOOK_CURSOR_SQLITE, EDataBookCursorSqlite))
+#define E_DATA_BOOK_CURSOR_SQLITE_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), E_TYPE_DATA_BOOK_CURSOR_SQLITE, 
EDataBookCursorSqliteClass))
+#define E_IS_DATA_BOOK_CURSOR_SQLITE(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
E_TYPE_DATA_BOOK_CURSOR_SQLITE))
+#define E_IS_DATA_BOOK_CURSOR_SQLITE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_DATA_BOOK_CURSOR_SQLITE))
+#define E_DATA_BOOK_CURSOR_SQLITE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
E_TYPE_DATA_BOOK_CURSOR_SQLITE, EDataBookCursorSqliteClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EDataBookCursorSqlite EDataBookCursorSqlite;
+typedef struct _EDataBookCursorSqliteClass EDataBookCursorSqliteClass;
+typedef struct _EDataBookCursorSqlitePrivate EDataBookCursorSqlitePrivate;
+
+
+/**
+ * EDataBookCursorSqlite:
+ *
+ * An opaque handle for the SQLite cursor instance.
+ *
+ * Since: 3.12
+ */
+struct _EDataBookCursorSqlite {
+       EDataBookCursor parent;
+       EDataBookCursorSqlitePrivate *priv;
+};
+
+
+/**
+ * EDataBookCursorSqliteClass:
+ *
+ * The SQLite cursor class structure.
+ *
+ * Since: 3.12
+ */
+struct _EDataBookCursorSqliteClass {
+       EDataBookCursorClass parent;
+};
+
+GType                  e_data_book_cursor_sqlite_get_type         (void);
+EDataBookCursor        *e_data_book_cursor_sqlite_new              (EBookBackend         *backend,
+                                                                   EBookBackendSqliteDB *ebsdb,
+                                                                   const gchar          *folder_id,
+                                                                   EContactField        *sort_fields,
+                                                                   EBookCursorSortType  *sort_types,
+                                                                   guint                 n_fields,
+                                                                   GError              **error);
+
+G_END_DECLS
+
+#endif /* E_DATA_BOOK_CURSOR_SQLITE_H */
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..2e6d7bf
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor.c
@@ -0,0 +1,1161 @@
+/* -*- 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>
+ */
+
+/**
+ * SECTION: e-data-book-cursor
+ * @include: libedata-book/libedata-book.h
+ * @short_description: The abstract cursor API
+ *
+ * The #EDataBookCursor API is the high level cursor API on the
+ * addressbook server, it can respond to client requests directly
+ * when opened in direct read access mode, otherwise it will implement
+ * the org.gnome.evolution.dataserver.AddressBookCursor D-Bus interface
+ * when instantiated by the addressbook server.
+ *
+ * <note><para>EDataBookCursor is an implementation detail for backends who wish
+ * to implement cursors. If you need to use the client API to iterate over contacts
+ * stored in Evolution Data Server; you should be using #EBookClientCursor instead.
+ * </para></note>
+ *
+ * <refsect2 id="cursor-implementing">
+ * <title>Implementing Cursors</title>
+ * <para>
+ * In order for an addressbook backend to implement cursors, it must
+ * first be locale aware, persist a current locale setting and implement
+ * the #EBookBackendClass.set_locale() and #EBookBackendClass.get_locale()
+ * methods.
+ * </para>
+ * <para>
+ * The backend indicates that it supports cursors by implementing the
+ * #EBookBackendClass.create_cursor() and returning an #EDataBookCursor,
+ * any backend implementing #EBookBackendClass.create_cursor() should also
+ * implement #EBookBackendClass.delete_cursor().
+ * </para>
+ * <para>
+ * For backends which use #EBookBackendSqliteDB to store contacts,
+ * an #EDataBookCursorSqlite can be used as a cursor implementation.
+ * </para>
+ * <para>
+ * Implementing a concrete cursor class for your own addressbook
+ * backend is a matter of implementing all of the virtual methods
+ * on the #EDataBookCursorClass vtable, each virtual method has
+ * documentation describing how each of the methods should be implemented.
+ * </para>
+ * </refsect2>
+ *
+ * <refsect2 id="cursor-track-state">
+ * <title>Tracking Cursor State</title>
+ * <para>
+ * The cursor state itself is defined as an array of sort keys
+ * and an %E_CONTACT_UID value. There should be one sort key
+ * stored for each contact field which was passed to
+ * #EBookBackendClass.create_cursor().
+ * </para>
+ * <para>
+ * Cursor position is always set to last result which
+ * was traversed in the most recent query. After moving the
+ * cursor through the next batch of results, the last result
+ * should always be saved as the new current cursor state, unless
+ * the end of the contact list is reached and the requested amount
+ * of contacts could not be returned, in which case the cursor
+ * should reset itself to a null state (the cursor walks off the
+ * end of the results into a reset state).
+ * </para>
+ * <para>
+ * The cursor must track two states at all times, one
+ * state which is the current cursor position, and one
+ * state which was the previous cursor position in order
+ * to satisfy the %E_BOOK_CURSOR_ORIGIN_PREVIOUS origin
+ * in a call to #EDataBookCursorClass.move_by().
+ * </para>
+ * <para>
+ * Calls to #EDataBookCursorClass.move_by() with the
+ * %E_BOOK_CURSOR_ORIGIN_RESET origin resets both current
+ * and previous cursor states before proceeding to move.
+ * Calls to #EDataBookCursorClass.set_alphabetic_index() also
+ * effect both cursor states.
+ * </para>
+ * </refsect2>
+ *
+ * <refsect2 id="cursor-localized-sorting">
+ * <title>Implementing Localized Sorting</title>
+ * <para>
+ * To implement localized sorting in an addressbook backend, an #ECollator
+ * can be used. The #ECollator provides all the functionality needed
+ * to respond to the cursor methods.
+ * </para>
+ * <para>
+ * When storing contacts in your backend, sort keys should be generated
+ * for any fields which might be used as sort key parameters for a cursor,
+ * keys for these fields should be generated with e_collator_generate_key()
+ * using an #ECollator created for the locale in which your addressbook is
+ * currently configured (undesired fields may be rejected at cursor creation
+ * time with an %E_CLIENT_ERROR_INVALID_QUERY error).
+ * </para>
+ * <para>
+ * The generated sort keys can then be used with strcmp() in order to
+ * compare results with the currently stored cursor state. These comparisons
+ * should compare contact fields in order of precedence in the array of
+ * sort fields which the cursor was configured with. If two contacts match
+ * exactly, then the %E_CONTACT_UID value is used to determine which
+ * contact sorts above or below the other.
+ * </para>
+ * <para>
+ * When your sort keys are generated using #ECollator, you can easily
+ * use e_collator_generate_key_for_index() to implement
+ * #EDataBookCursorClass.set_alphabetic_index() and set the cursor
+ * position before (below) a given letter in the active alphabet. The key
+ * generated for an alphabetic index is guaranteed to sort below any word
+ * starting with the given letter, and above any word starting with the
+ * preceeding letter.
+ * </para>
+ * </refsect2>
+ *
+ * <refsect2 id="cursor-dra">
+ * <title>Direct Read Access</title>
+ * <para>
+ * In order to support cursors in backends which support Direct Read Access
+ * mode, the underlying addressbook data must be written atomically along with each
+ * new revision attribute. The cursor mechanics rely on this atomicity in order
+ * to avoid any race conditions and ensure that data read back from the addressbook
+ * is current and up to date.
+ * </para>
+ * </refsect2>
+ *
+ * <refsect2 id="cursor-backends">
+ * <title>Backend Tasks</title>
+ * <para>
+ * Backends have ownership of the cursors which they create
+ * and have some responsibility when supporting cursors.
+ * </para>
+ * <para>
+ * As mentioned above, in Direct Read Access mode (if supported), all
+ * revision writes and addressbook modifications must be committed
+ * atomically.
+ * </para>
+ * <para>
+ * Beyond that, it is the responsibility of the backend to call
+ * e_data_book_cursor_contact_added() and e_data_book_cursor_contact_removed()
+ * whenever the addressbook is modified. When a contact is modified
+ * but not added or removed, then e_data_book_cursor_contact_removed()
+ * should be called with the old existing contact and then
+ * e_data_book_cursor_contact_added() should be called with
+ * the new contact. This will automatically update the cursor
+ * total and position status.
+ * </para>
+ * <para>
+ * Note that if it's too much trouble to load the existing
+ * contact data when a contact is modified, then
+ * e_data_book_cursor_recalculate() can be called instead. This
+ * will use the #EDataBookCursorClass.get_position() method
+ * recalculate current cursor position from scratch.
+ * </para>
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#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_contact (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,
+                                                             const gchar            *revision,
+                                                             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_contact (EDataBookCursor     *cursor,
+                                 EContact            *contact,
+                                 gint                *result,
+                                 gboolean            *matches_sexp)
+{
+       if (!E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->compare_contact)
+               return FALSE;
+
+       g_object_ref (cursor);
+       *result = (* E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->compare_contact) (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,
+                                const gchar            *revision,
+                                EBookCursorOrigin       origin,
+                                gint                    count,
+                                gboolean                fetch_results,
+                                EDataBookCursor        *cursor)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+
+       if (!e_data_book_cursor_move_by (cursor, revision, origin, count,
+                                        fetch_results ? &results : NULL,
+                                        &error)) {
+               g_dbus_method_invocation_return_gerror (invocation, error);
+               g_clear_error (&error);
+       } else {
+               gchar **strv = NULL;
+               const gchar * const empty_str[] = { 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,
+                                                            strv ? 
+                                                            (const gchar *const *)strv :
+                                                            empty_str,
+                                                            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;
+       GError *error = NULL;
+
+       /* The backend will release the cursor, just make sure that
+        * we survive long enough to complete this method call
+        */
+       g_object_ref (cursor);
+
+       /* This should never really happen, but if it does, there is no
+        * we cannot expect the client to recover well from an error at
+        * dispose time, so let's just log the warning.
+        */
+       if (!e_book_backend_delete_cursor (priv->backend, cursor, &error)) {
+               g_warning ("Error trying to delete cursor: %s", error->message);
+               g_clear_error (&error);
+       }
+       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.12
+ */
+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.12
+ */
+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 its result list.
+ *
+ * Returns: the current position of @cursor
+ *
+ * Since: 3.12
+ */
+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 and @error is set.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_data_book_cursor_set_sexp (EDataBookCursor     *cursor,
+                            const gchar         *sexp,
+                            GError             **error)
+{
+       GError *local_error = NULL;
+       gboolean success = FALSE;
+
+       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);
+
+       } else {
+               g_set_error_literal (error,
+                                    E_CLIENT_ERROR,
+                                    E_CLIENT_ERROR_NOT_SUPPORTED,
+                                    _("Cursor does not support setting the search expression"));
+       }
+
+       /* We already set the new search expression,
+        * we can't fail anymore so just fire a warning
+        */
+       if (success &&
+           !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);
+       }
+
+       g_object_unref (cursor);
+
+       return success;
+}
+
+/**
+ * e_data_book_cursor_move_by:
+ * @cursor: an #EDataBookCursor
+ * @revision_guard: 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 a @revision_guard is specified, the cursor implementation will issue an
+ * %E_CLIENT_ERROR_OUT_OF_SYNC error if the @revision_guard does not match
+ * the current addressbook revision.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
+ *
+ * Since: 3.12
+ */
+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_literal (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 @cursor position to an
+ * <link linkend="cursor-alphabet">Alphabetic Index</link>
+ * into the alphabet active in the @locale of the addressbook.
+ *
+ * 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 and @error is set.
+ *
+ * Since: 3.12
+ */
+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_literal (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 and @error is set.
+ *
+ * Since: 3.12
+ */
+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.
+ *
+ * This will implicitly reset @cursor's state and position.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
+ *
+ * Since: 3.12
+ */
+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),
+                                                           &local_error)) {
+                       g_warning ("Error recalculating cursor position after locale change: %s",
+                                  local_error->message);
+                       g_clear_error (&local_error);
+               }
+       }
+
+       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.12
+ */
+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_contact (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 doesn't 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.12
+ */
+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_contact (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 and @error is set.
+ *
+ * Since: 3.12
+ */
+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..44d04de
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor.h
@@ -0,0 +1,307 @@
+/* -*- 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. After this is called, the position and total will
+ * be recalculated.
+ *
+ * If the cursor implementation is unable to deal with the #EContactFields
+ * referred to in @sexp, then an %E_CLIENT_ERROR_INVALID_QUERY error should
+ * be set to indicate this.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.12
+ */
+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.move_by()
+ *
+ * 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, will be set to the %CLIENT_BACKEND_PROPERTY_REVISION
+ * value at the time which the given client issued the call to move the cursor.
+ * If the @revision_guard provided by the client does not match the stored addressbook
+ * revision, then an %E_CLIENT_ERROR_OUT_OF_SYNC error should be set to indicate
+ * that the revision was out of sync while attempting to move the cursor.
+ *
+ * <note><para>If the addressbook backend supports direct read access, then the
+ * revision comparison and reading of the data store must be coupled into a
+ * single atomic operation (the data read back from the store must be the correct
+ * data for the given addressbook revision).</para></note>
+ *
+ * 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.12
+ */
+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
+ * active alphabet.
+ *
+ * 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.12
+ */
+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 amount of contacts
+ * leading up to the current cursor state (cursor inclusive).
+ *
+ * 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 (or whenever
+ * moving the cursor beyond the end of the result set).
+ *
+ * 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.12
+ */
+typedef gboolean (*EDataBookCursorGetPositionFunc) (EDataBookCursor     *cursor,
+                                                   gint                *total,
+                                                   gint                *position,
+                                                   GError             **error);
+
+/**
+ * EDataBookCursorCompareContactFunc:
+ * @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_contact()
+ *
+ * Cursor implementations should implement this in order to compare a
+ * contact with the current cursor state.
+ *
+ * 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.12
+ */
+typedef gint (*EDataBookCursorCompareContactFunc) (EDataBookCursor     *cursor,
+                                                  EContact            *contact,
+                                                  gboolean            *matches_sexp);
+
+/**
+ * EDataBookCursorLoadLocaleFunc:
+ * @cursor: an #EDataBookCursor
+ * @locale: (out) (transfer full): return location to store the newly loaded locale
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Method type for #EDataBookCursorClass.load_locale()
+ *
+ * Fetches the locale setting from @cursor's addressbook
+ *
+ * If the locale setting has changed, the cursor must reload any
+ * internal locale specific data and ensure that comparisons of
+ * sort keys will function properly in the new locale.
+ *
+ * Upon locale changes, the implementation need not worry about
+ * updating it's current cursor state, the cursor state will be
+ * reset automatically for you.
+ *
+ * Returns: %TRUE on Success, otherwise %FALSE is returned if any error occurred
+ * and @error is set to reflect the error which occurred.
+ *
+ * Since: 3.12
+ */
+typedef gboolean (*EDataBookCursorLoadLocaleFunc) (EDataBookCursor     *cursor,
+                                                  gchar              **locale,
+                                                  GError             **error);
+
+/**
+ * EDataBookCursor:
+ *
+ * An opaque handle for an addressbook cursor
+ *
+ * Since: 3.12
+ */
+struct _EDataBookCursor {
+       GObject parent;
+
+       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_contact: The #EDataBookCursorCompareContactFunc 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.12
+ */
+struct _EDataBookCursorClass {
+       /*< private >*/
+       GObjectClass parent;
+
+       /*< public >*/
+       EDataBookCursorSetSexpFunc set_sexp;
+       EDataBookCursorMoveByFunc move_by;
+       EDataBookCursorSetAlphabetIndexFunc set_alphabetic_index;
+       EDataBookCursorGetPositionFunc get_position;
+       EDataBookCursorCompareContactFunc compare_contact;
+       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 */
diff --git a/addressbook/libedata-book/libedata-book.h b/addressbook/libedata-book/libedata-book.h
index 3bca3b2..d6f4251 100644
--- a/addressbook/libedata-book/libedata-book.h
+++ b/addressbook/libedata-book/libedata-book.h
@@ -31,8 +31,10 @@
 #include <libedata-book/e-book-backend-sqlitedb.h>
 #include <libedata-book/e-book-backend-summary.h>
 #include <libedata-book/e-book-backend.h>
-#include <libedata-book/e-data-book-factory.h>
+#include <libedata-book/e-data-book-cursor.h>
+#include <libedata-book/e-data-book-cursor-sqlite.h>
 #include <libedata-book/e-data-book-direct.h>
+#include <libedata-book/e-data-book-factory.h>
 #include <libedata-book/e-data-book-view.h>
 #include <libedata-book/e-data-book.h>
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 04d20cb..606fea1 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -12,12 +12,15 @@ addressbook/libebook-contacts/e-contact.c
 addressbook/libebook-contacts/e-phone-number.c
 addressbook/libebook/e-book.c
 addressbook/libebook/e-book-client.c
+addressbook/libebook/e-book-client-cursor.c
 addressbook/libebook/e-book-client-view.c
 addressbook/libebook/e-destination.c
 addressbook/libedata-book/e-book-backend.c
 addressbook/libedata-book/e-book-backend-sexp.c
 addressbook/libedata-book/e-book-backend-sqlitedb.c
 addressbook/libedata-book/e-data-book.c
+addressbook/libedata-book/e-data-book-cursor.c
+addressbook/libedata-book/e-data-book-cursor-sqlite.c
 addressbook/libedata-book/e-data-book-factory.c
 calendar/backends/caldav/e-cal-backend-caldav.c
 calendar/backends/contacts/e-cal-backend-contacts.c


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