[evolution-data-server/cursor-staging: 12/20] Added EDataBookCursor & EDataBookCursorSqlite
- From: Tristan Van Berkom <tvb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/cursor-staging: 12/20] Added EDataBookCursor & EDataBookCursorSqlite
- Date: Sat, 19 Oct 2013 18:14:31 +0000 (UTC)
commit dc6566890fb9c5b6ca3c4e4d938b87d3d66eeda0
Author: Tristan Van Berkom <tristanvb openismus com>
Date: Sat Oct 19 20:03:54 2013 +0200
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 to implement the EDataBookCursor
methods.
This patch also adds the E_CLIENT_ERROR_END_OF_LIST error, intended
to be a generic boundary error when a client api reaches the end
of some list of data (this is used by the cursor API of course
to report an error when trying to navigate the cursor outside of the
list boundaries).
.../libebook-contacts/e-book-contacts-types.h | 34 +
addressbook/libedata-book/Makefile.am | 4 +
.../libedata-book/e-data-book-cursor-sqlite.c | 559 +++++++++
.../libedata-book/e-data-book-cursor-sqlite.h | 81 ++
addressbook/libedata-book/e-data-book-cursor.c | 1206 ++++++++++++++++++++
addressbook/libedata-book/e-data-book-cursor.h | 310 +++++
addressbook/libedata-book/libedata-book.h | 4 +-
libedataserver/e-client.h | 3 +-
po/POTFILES.in | 3 +
9 files changed, 2202 insertions(+), 2 deletions(-)
---
diff --git a/addressbook/libebook-contacts/e-book-contacts-types.h
b/addressbook/libebook-contacts/e-book-contacts-types.h
index 1f9d7fc..01a6dfa 100644
--- a/addressbook/libebook-contacts/e-book-contacts-types.h
+++ b/addressbook/libebook-contacts/e-book-contacts-types.h
@@ -149,6 +149,40 @@ typedef enum {
E_BOOK_CURSOR_SORT_DESCENDING
} EBookCursorSortType;
+/**
+ * EBookCursorOrigin:
+ * @E_BOOK_CURSOR_ORIGIN_CURRENT: The current cursor position
+ * @E_BOOK_CURSOR_ORIGIN_BEGIN: The beginning of the cursor results.
+ * @E_BOOK_CURSOR_ORIGIN_END: The ending of the cursor results.
+ *
+ * Specifies the start position to in the list of traversed contacts
+ * in calls to e_book_client_cursor_step().
+ *
+ * When an #EBookClientCursor is created, the current position implied by %E_BOOK_CURSOR_ORIGIN_CURRENT
+ * is the same as %E_BOOK_CURSOR_ORIGIN_BEGIN.
+ *
+ * Since: 3.12
+ */
+typedef enum {
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ E_BOOK_CURSOR_ORIGIN_BEGIN,
+ E_BOOK_CURSOR_ORIGIN_END
+} EBookCursorOrigin;
+
+/**
+ * EBookCursorStepFlags:
+ * @E_BOOK_CURSOR_STEP_MOVE: The cursor position should be modified while stepping
+ * @E_BOOK_CURSOR_STEP_FETCH: Traversed contacts should be listed and returned while stepping.
+ *
+ * Defines the behaviour of e_book_client_cursor_step().
+ *
+ * Since: 3.12
+ */
+typedef enum {
+ E_BOOK_CURSOR_STEP_MOVE = (1 << 0),
+ E_BOOK_CURSOR_STEP_FETCH = (1 << 1)
+} EBookCursorStepFlags;
+
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..12e6d7d
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor-sqlite.c
@@ -0,0 +1,559 @@
+/* -*- 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 gint e_data_book_cursor_sqlite_step (EDataBookCursor *cursor,
+ const gchar *revision_guard,
+ EBookCursorStepFlags flags,
+ 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->step = e_data_book_cursor_sqlite_step;
+ 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_BEGIN:
+ *dest_origin = EBSDB_CURSOR_ORIGIN_BEGIN;
+ break;
+ case E_BOOK_CURSOR_ORIGIN_END:
+ *dest_origin = EBSDB_CURSOR_ORIGIN_END;
+ break;
+ default:
+ success = FALSE;
+ g_set_error_literal (error,
+ E_CLIENT_ERROR,
+ E_CLIENT_ERROR_INVALID_ARG,
+ _("Unrecognized cursor origin"));
+ break;
+ }
+
+ return success;
+}
+
+static void
+convert_flags (EBookCursorStepFlags src_flags,
+ EbSdbCursorStepFlags *dest_flags)
+{
+ if (src_flags & E_BOOK_CURSOR_STEP_MOVE)
+ *dest_flags |= EBSDB_CURSOR_STEP_MOVE;
+
+ if (src_flags & E_BOOK_CURSOR_STEP_FETCH)
+ *dest_flags |= EBSDB_CURSOR_STEP_FETCH;
+}
+
+static gint
+e_data_book_cursor_sqlite_step (EDataBookCursor *cursor,
+ const gchar *revision_guard,
+ EBookCursorStepFlags flags,
+ 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;
+ EbSdbCursorStepFlags sqlitedb_flags = 0;
+ gchar *revision = NULL;
+ gboolean success = FALSE;
+ gint n_results = -1;
+
+ cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
+ priv = cursor_sqlite->priv;
+
+ if (!convert_origin (origin, &sqlitedb_origin, error))
+ return FALSE;
+
+ convert_flags (flags, &sqlitedb_flags);
+
+ /* 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) {
+ GError *local_error = NULL;
+
+ n_results = e_book_backend_sqlitedb_cursor_step (priv->ebsdb,
+ priv->cursor,
+ sqlitedb_flags,
+ sqlitedb_origin,
+ count,
+ &local_results,
+ &local_error);
+
+ if (n_results < 0) {
+
+ /* Convert the SQLite backend error to an EClient error */
+ if (g_error_matches (local_error,
+ E_BOOK_SDB_ERROR,
+ E_BOOK_SDB_ERROR_END_OF_LIST)) {
+ g_set_error_literal (error, E_CLIENT_ERROR,
+ E_CLIENT_ERROR_END_OF_LIST,
+ local_error->message);
+ g_clear_error (&local_error);
+ } else
+ g_propagate_error (error, local_error);
+
+ success = FALSE;
+ }
+ }
+
+ 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);
+
+ if (success)
+ return n_results;
+
+ return -1;
+}
+
+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,
+ ¤t_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..43092ce
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor.c
@@ -0,0 +1,1206 @@
+/* -*- 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>
+ * Initially, the cursor state is assumed to be clear and
+ * positioned naturally at the beginning so that the first
+ * calls to #EDataBookCursorClass.step() using the
+ * %E_BOOK_CURSOR_ORIGIN_CURRENT origin would respond in the
+ * same way as the %E_BOOK_CURSOR_ORIGIN_BEGIN origin would.
+ * </para>
+ * <para>
+ * Unless the %E_BOOK_CURSOR_STEP_FETCH flag is not specified
+ * in calls to #EDataBookCursorClass.step(), the cursor state
+ * should be always be set to the last contact which was traversed
+ * in every call to #EDataBookCursorClass.step(). In the case
+ * that #EDataBookCursorClass.step() was asked to step beyond the
+ * bounderies of the list, i.e. when stepping passed the end
+ * of the list of before the beginning, then the cursor state
+ * can be cleared and the implementation must track whether
+ * the cursor is at the beginning or the end of the list.
+ * </para>
+ * <para>
+ * The task of actually collecting the cursor state from a
+ * contact should be done using an #ECollator created for
+ * the locale in which your #EBookBackend is configured for.
+ * </para>
+ * <example>
+ * <title>Collecting sort keys for a given contact</title>
+ * <programlisting><![CDATA[
+ * static void
+ * update_state_from_contact (EBookBackendSmth *smth,
+ * EBookBackendSmthCursor *cursor,
+ * EContact *contact)
+ * {
+ * gint i;
+ *
+ * clear_state (smth, cursor);
+ *
+ * // For each sort key the cursor was created for
+ * for (i = 0; i < cursor->n_sort_fields; i++) {
+ *
+ * // Using an ECollator created for the locale
+ * // set on your EBookBackend...
+ * const gchar *string = e_contact_get_const (contact, cursor->sort_fields[i]);
+ *
+ * // Generate a sort key for each value
+ * if (string)
+ * cursor->state->values[i] =
+ * e_collator_generate_key (smth->collator,
+ * string, NULL);
+ * else
+ * cursor->state->values[i] = g_strdup ("");
+ * }
+ *
+ * state->last_uid = e_contact_get (contact, E_CONTACT_UID);
+ * }
+ * ]]></programlisting>
+ * </example>
+ * <para>
+ * Using the strings collected above for a given contact,
+ * two contacts can easily be compared for equality in
+ * a locale sensitive way, using strcmp() directly on
+ * the generated sort keys.
+ * </para>
+ * <para>
+ * Calls to #EDataBookCursorClass.step() with the
+ * %E_BOOK_CURSOR_ORIGIN_BEGIN or %E_BOOK_CURSOR_ORIGIN_END reset
+ * the cursor state before fetching results from either the
+ * beginning or ending of the result list respectively.
+ * </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_step_position (EDataBookCursor *cursor,
+ EBookCursorOrigin origin,
+ gint count,
+ gint results);
+
+/* D-Bus callbacks */
+static gint data_book_cursor_handle_step (EDBusAddressBookCursor *dbus_object,
+ GDBusMethodInvocation *invocation,
+ const gchar *revision,
+ EBookCursorStepFlags flags,
+ EBookCursorOrigin origin,
+ gint count,
+ 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_step_position (EDataBookCursor *cursor,
+ EBookCursorOrigin origin,
+ gint count,
+ gint results)
+{
+ EDataBookCursorPrivate *priv = cursor->priv;
+ gint new_position = priv->position;
+ gint offset = results;
+
+ /* If we didnt get as many contacts as asked for, it indicates that
+ * we've reached the end of the list (or beginning)... in this case
+ * we add 1 to the offset
+ * so that we land on the 0 position or the total + 1 position
+ * respectively.
+ */
+ if (offset < ABS (count)) {
+ offset += 1;
+ }
+
+ /* Convert our 'number of results' into a signed 'offset'
+ * to add to the cursor position.
+ */
+ if (count < 0)
+ offset = -offset;
+
+ /* Don't assert the boundaries of values here, we
+ * did that in e_data_book_cursor_step() already.
+ */
+ switch (origin) {
+ case E_BOOK_CURSOR_ORIGIN_CURRENT:
+ new_position = priv->position + offset;
+ break;
+
+ case E_BOOK_CURSOR_ORIGIN_BEGIN:
+ new_position = offset;
+ break;
+
+ case E_BOOK_CURSOR_ORIGIN_END:
+ new_position = (priv->total + 1) + offset;
+ break;
+ }
+
+ new_position = CLAMP (new_position, 0, priv->total + 1);
+ data_book_cursor_set_values (cursor, priv->total, new_position);
+}
+
+/************************************************
+ * D-Bus Callbacks *
+ ************************************************/
+static gboolean
+data_book_cursor_handle_step (EDBusAddressBookCursor *dbus_object,
+ GDBusMethodInvocation *invocation,
+ const gchar *revision,
+ EBookCursorStepFlags flags,
+ EBookCursorOrigin origin,
+ gint count,
+ EDataBookCursor *cursor)
+{
+ GSList *results = NULL;
+ GError *error = NULL;
+ gint n_results;
+
+ n_results = e_data_book_cursor_step (cursor,
+ revision,
+ flags,
+ origin,
+ count,
+ &results,
+ &error);
+
+ if (n_results < 0) {
+ 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_step (dbus_object,
+ invocation,
+ n_results,
+ 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_step:
+ * @cursor: an #EDataBookCursor
+ * @revision_guard: The expected current addressbook revision, or %NULL
+ * @flags: The #EBookCursorStepFlags for this step
+ * @origin: The #EBookCursorOrigin from whence to step
+ * @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 if %E_BOOK_CURSOR_STEP_FETCH is not specified in %flags
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Steps @cursor through it's sorted query by a maximum of @count contacts
+ * starting from @origin.
+ *
+ * If @count is negative, then the cursor will move through the list in reverse.
+ *
+ * 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.
+ * Reaching the end of the list is not considered an error condition. Attempts
+ * to step beyond the end of the list after having reached the end of the list
+ * will however trigger an %E_CLIENT_ERROR_END_OF_LIST error.
+ *
+ * If %E_BOOK_CURSOR_STEP_FETCH is specified in %flags, a pointer to
+ * a %NULL #GSList pointer should be provided for the @results parameter.
+ *
+ * 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: The number of contacts traversed if successfull, otherwise -1 is
+ * returned and @error is set.
+ *
+ * Since: 3.12
+ */
+gint
+e_data_book_cursor_step (EDataBookCursor *cursor,
+ const gchar *revision_guard,
+ EBookCursorStepFlags flags,
+ EBookCursorOrigin origin,
+ gint count,
+ GSList **results,
+ GError **error)
+{
+ gint retval;
+
+ g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
+ g_return_val_if_fail ((flags & E_BOOK_CURSOR_STEP_FETCH) == 0 ||
+ (results != NULL && *results == NULL), -1);
+
+ if (!E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->step) {
+ g_set_error_literal (error,
+ E_CLIENT_ERROR,
+ E_CLIENT_ERROR_NOT_SUPPORTED,
+ _("Cursor does not support step"));
+ return FALSE;
+ }
+
+ g_object_ref (cursor);
+ retval = (* E_DATA_BOOK_CURSOR_GET_CLASS (cursor)->step) (cursor,
+ revision_guard,
+ flags,
+ origin,
+ count,
+ results,
+ error);
+ g_object_unref (cursor);
+
+ if (retval >= 0 && (flags & E_BOOK_CURSOR_STEP_MOVE) != 0) {
+
+ calculate_step_position (cursor, origin, count, retval);
+ }
+
+ return retval;
+}
+
+/**
+ * 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_step()
+ * 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_step (cursor, NULL,
+ E_BOOK_CURSOR_STEP_MOVE,
+ E_BOOK_CURSOR_ORIGIN_BEGIN,
+ 0, NULL, &local_error) < 0) {
+ 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-step",
+ G_CALLBACK (data_book_cursor_handle_step), 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..eb7a879
--- /dev/null
+++ b/addressbook/libedata-book/e-data-book-cursor.h
@@ -0,0 +1,310 @@
+/* -*- 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);
+
+/**
+ * EDataBookCursorStepFunc:
+ * @cursor: an #EDataBookCursor
+ * @revision_guard: (allow-none): The expected current addressbook revision, or %NULL
+ * @flags: The #EBookCursorStepFlags for this step
+ * @origin: The #EBookCursorOrigin from whence to step
+ * @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 if %E_BOOK_CURSOR_STEP_FETCH is not specified in %flags
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Method type for #EDataBookCursorClass.step()
+ *
+ * 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_step() for more details on the expected behaviour of this method.
+ *
+ * Returns: The number of contacts traversed if successfull, otherwise -1 is
+ * returned and @error is set.
+ *
+ * Since: 3.12
+ */
+typedef gint (*EDataBookCursorStepFunc) (EDataBookCursor *cursor,
+ const gchar *revision_guard,
+ EBookCursorStepFlags flags,
+ 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.step() 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
+ * @step: The #EDataBookCursorStepFunc 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;
+ EDataBookCursorStepFunc step;
+ 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);
+gint e_data_book_cursor_step (EDataBookCursor *cursor,
+ const gchar *revision_guard,
+ EBookCursorStepFlags flags,
+ 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/libedataserver/e-client.h b/libedataserver/e-client.h
index 69638b4..78719f8 100644
--- a/libedataserver/e-client.h
+++ b/libedataserver/e-client.h
@@ -149,7 +149,8 @@ typedef enum {
E_CLIENT_ERROR_DBUS_ERROR,
E_CLIENT_ERROR_OTHER_ERROR,
E_CLIENT_ERROR_NOT_OPENED,
- E_CLIENT_ERROR_OUT_OF_SYNC
+ E_CLIENT_ERROR_OUT_OF_SYNC,
+ E_CLIENT_ERROR_END_OF_LIST
} EClientError;
const gchar * e_client_error_to_string (EClientError code);
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]