[evolution-data-server/cursor-staging: 16/34] Added EDataBookCursor & EDataBookCursorSqlite
- From: Tristan Van Berkom <tvb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/cursor-staging: 16/34] Added EDataBookCursor & EDataBookCursorSqlite
- Date: Sun, 13 Oct 2013 02:16:12 +0000 (UTC)
commit 2c61dcf2a6618c0dc5eb564423649d797897a642
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,
+ ¤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..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]