diff options
| -rw-r--r-- | addressbook/libebook-contacts/e-book-contacts-types.h | 34 | ||||
| -rw-r--r-- | addressbook/libedata-book/Makefile.am | 4 | ||||
| -rw-r--r-- | addressbook/libedata-book/e-data-book-cursor-sqlite.c | 559 | ||||
| -rw-r--r-- | addressbook/libedata-book/e-data-book-cursor-sqlite.h | 81 | ||||
| -rw-r--r-- | addressbook/libedata-book/e-data-book-cursor.c | 1209 | ||||
| -rw-r--r-- | addressbook/libedata-book/e-data-book-cursor.h | 314 | ||||
| -rw-r--r-- | addressbook/libedata-book/libedata-book.h | 4 | ||||
| -rw-r--r-- | po/POTFILES.in | 3 |
8 files changed, 2207 insertions, 1 deletions
diff --git a/addressbook/libebook-contacts/e-book-contacts-types.h b/addressbook/libebook-contacts/e-book-contacts-types.h index 1f9d7fc05..01a6dfa04 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 2779cc0e1..880d1e632 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 000000000..408a031a2 --- /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_QUERY_REFUSED, + 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 000000000..88ea57701 --- /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 000000000..591e9953a --- /dev/null +++ b/addressbook/libedata-book/e-data-book-cursor.c @@ -0,0 +1,1209 @@ +/* -*- 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_QUERY_REFUSED 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. + * + * An explanation of how stepping is expected to behave can be found + * in the <link linkend="cursor-iteration">user facing reference documentation</link>. + * + * Returns: The number of contacts traversed if successful, 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 000000000..8d938b5a5 --- /dev/null +++ b/addressbook/libedata-book/e-data-book-cursor.h @@ -0,0 +1,314 @@ +/* -*- 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 cursor state to point to an + * <link linkend="cursor-alphabet">index into the active alphabet</link>. + * + * 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_BEGIN is + * specified in the #EDataBookCursorClass.step() method (or whenever + * moving the cursor beyond the end of the result set). + * + * If the cursor is positioned beyond the end of the list, then + * the position should be the total amount of contacts available + * in the list (as returned through the @total argument) plus one. + * + * 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 3bca3b2c7..d6f4251fa 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 04d20cbc9..606fea1db 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 |
