[evolution-data-server/cursor-staging: 17/25] Added EBookClientCursor
- From: Tristan Van Berkom <tvb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/cursor-staging: 17/25] Added EBookClientCursor
- Date: Sat, 12 Oct 2013 01:55:33 +0000 (UTC)
commit 3c941acb84251c1e896878b8d85c25a9366bbb59
Author: Tristan Van Berkom <tristanvb openismus com>
Date: Tue Jul 2 17:47:34 2013 +0900
Added EBookClientCursor
The user facing cursor API, deals with calling cursor APIs in DRA mode
and normal modes over D-Bus, handles threading situations, and notifies
of cursor state changes in the caller's main context.
addressbook/libebook/Makefile.am | 2 +
addressbook/libebook/e-book-client-cursor.c | 2480 +++++++++++++++++++++++++++
addressbook/libebook/e-book-client-cursor.h | 144 ++
3 files changed, 2626 insertions(+), 0 deletions(-)
---
diff --git a/addressbook/libebook/Makefile.am b/addressbook/libebook/Makefile.am
index 9fe5ef4..724b38a 100644
--- a/addressbook/libebook/Makefile.am
+++ b/addressbook/libebook/Makefile.am
@@ -41,6 +41,7 @@ libebook_1_2_la_CPPFLAGS = \
libebook_1_2_la_SOURCES = \
$(ENUM_GENERATED) \
e-book-client.c \
+ e-book-client-cursor.c \
e-book-client-view.c \
e-book-view-private.h \
e-book-view.c \
@@ -69,6 +70,7 @@ libebookincludedir = $(privincludedir)/libebook
libebookinclude_HEADERS = \
libebook.h \
e-book-client.h \
+ e-book-client-cursor.h \
e-book-client-view.h \
e-book-enumtypes.h \
e-book-view.h \
diff --git a/addressbook/libebook/e-book-client-cursor.c b/addressbook/libebook/e-book-client-cursor.c
new file mode 100644
index 0000000..93f50fd
--- /dev/null
+++ b/addressbook/libebook/e-book-client-cursor.c
@@ -0,0 +1,2480 @@
+/* -*- 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-book-client-cursor
+ * @include: libebook/libebook.h
+ * @short_description: An addressbook cursor
+ *
+ * The #EBookClientCursor is an iteration based interface for browsing
+ * a sorted list of contacts in the addressbook.
+ *
+ * <refsect2 id="cursor-sort-keys">
+ * <title>Sort Keys</title>
+ * <para>
+ * When creating the cursor initially with e_book_client_get_cursor(),
+ * a list of sort keys must be provided to define the behavior
+ * of the newly created cursor. Only summary #EContactFields are
+ * supported for usage as sort keys. Whether a sort key is valid in
+ * your addressbook can be verified by checking with
+ * e_source_backend_summary_setup_get_summary_fields().
+ * </para>
+ * <para>
+ * Any string type #EContactField in the summary can be used as a sort key,
+ * the keys are provided in order of precedence, the primary sort
+ * key is first followed by any secondary or tertiary sort keys.
+ * </para>
+ * <para>
+ * Sort order is immutable, if you need to browse content in a different
+ * order, then you need to create a separate cursor.
+ * </para>
+ * </refsect2>
+ *
+ * <refsect2 id="cursor-search">
+ * <title>Search Expressions</title>
+ * <para>
+ * The list of contacts associated to a given cursor can be filtered
+ * with a search expression generated by e_book_query_to_string(). This
+ * however comes with the same limitation as sort keys, only #EContactFields
+ * which are stored in the addressbook summary can be referred to in
+ * search expressions provided to e_book_client_cursor_set_sexp().
+ * </para>
+ * <para>
+ * Changing the search expression can be done at any time using
+ * e_book_client_cursor_set_sexp() The <link linkend="cursor-stats">cursor status</link>
+ * will be updated synchronously after successfully setting the
+ * search expression at which time you might refresh the current
+ * view, displaying the new filtered list of contacts at the same
+ * cursor position.
+ * </para>
+ * </refsect2>
+ *
+ * <refsect2 id="cursor-iteration">
+ * <title>Iteration with the cursor API</title>
+ * <para>
+ * The cursor API allows you to iterate through a sorted list of contacts
+ * without keeping a potentially large collection of contacts loaded
+ * in memory.
+ * </para>
+ * <para>
+ * Iterating through the contact list is done with e_book_client_cursor_move_by(), this
+ * function allows one to move the cursor and fetch the results following the current
+ * cursor position:
+ * <informalexample>
+ * <programlisting>
+ * <![CDATA[GError *error = NULL;
+ * GSList *results;
+ *
+ * if (!e_book_client_cursor_move_by_sync (cursor,
+ * E_BOOK_CURSOR_ORIGIN_CURRENT,
+ * 10,
+ * &results,
+ * NULL,
+ * &error))
+ * {
+ * if (g_error_matches (error,
+ * E_CLIENT_ERROR,
+ * E_CLIENT_ERROR_OUT_OF_SYNC))
+ * handle_out_of_sync_condition (cursor);
+ * else
+ * handle_error_condition (cursor, error);
+ *
+ * g_clear_error (&error);
+ * }]]></programlisting>
+ * </informalexample>
+ * In the above example we chose %E_BOOK_CURSOR_ORIGIN_CURRENT as our #EBookCursorOrigin so the above
+ * call will fetch 10 contacts after the cursor's current position and reposition the current cursor
+ * location 10 contacts further into the results. We could have chosen the %E_BOOK_CURSOR_ORIGIN_RESET
+ * origin to start at the beginning of the results or the %E_BOOK_CURSOR_ORIGIN_PREVIOUS
+ * origin to start the query from the previous position. The cursor keeps a record of the last
+ * known cursor position for the purpose of repeating queries when the addressbook is modified.
+ * </para>
+ * <para>
+ * Because Evolution's addressbook might be modified at any time by another application,
+ * it's important to handle the %E_CLIENT_ERROR_OUT_OF_SYNC error. This error will occur
+ * at any time that the cursor detects an addressbook change while trying to move.
+ * Whenever an out of sync condition arises, the cursor should be left alone until the
+ * next #EBookClientCursor::refresh signal. The #EBookClientCursor::refresh signal is triggered
+ * any time that the addressbook changes and is the right place to refresh the currently
+ * loaded content, it is also guaranteed to be triggered after any %E_CLIENT_ERROR_OUT_OF_SYNC
+ * error.
+ * </para>
+ * </refsect2>
+ *
+ * <refsect2 id="cursor-stats">
+ * <title>Cursor Status</title>
+ * <para>
+ * The cursor's current status is available through the #EBookClientCursor:total and
+ * #EBookClientCursor:position properties. These values are guaranteed to always
+ * be coherent, they are updated synchronously upon successful completion of any
+ * of the asynchronous cursor API calls, and also updated asynchronously whenever
+ * the addressbook changes and a #EBookClientCursor::refresh signal is delivered.
+ * </para>
+ * <para>
+ * Change notifications are guaranteed to only ever be delivered in the #GMainContext which
+ * was the thread default main context at cursor creation time.
+ * </para>
+ * </refsect2>
+ *
+ * <refsect2 id="cursor-alphabet">
+ * <title>Alphabetic Indexes</title>
+ * <para>
+ * The cursor implementation uses ICU libraries to provide a rich locale sensitive
+ * featureset, this ensures that results are always sorted in the expected addressbook
+ * sort order (as opposed to dictionary order or phonetic order) and in the expected
+ * order according to the user's locale.
+ * </para>
+ * <para>
+ * One of the more interesting features this allows for, is support for locale
+ * specific language scripts, i.e. Alphabetic Indexes.
+ * </para>
+ * <para>
+ * The cursor exposes the active alphabet via the #EBookClientCursor:alphabet property.
+ * which is a null terminated array of strings. The strings are appropriate to display
+ * to represent positions (letters / characters) in the active alphabet, for instance
+ * for a Latin alphabet the array will contain <emphasis>"A", "B", "C", ...</emphasis>.
+ * The full information on the active alphabet can always be fetched with
+ * e_book_client_cursor_get_alphabet().
+ * </para>
+ * <para>
+ * Using the active alphabet, one can build a user interface which allows the user
+ * to navigate to a specific letter in the results. To set the cursor's position
+ * directly before any results starting with a specific letter, one can use
+ * e_book_client_cursor_set_alphabetic_index():
+ * <informalexample>
+ * <programlisting>
+ * <![CDATA[GError *error = NULL;
+ * gint index = currently_selected_index (user_interface);
+ *
+ * if (!e_book_client_cursor_set_alphabetic_index_sync (cursor,
+ * index,
+ * NULL,
+ * &error))
+ * {
+ * if (g_error_matches (error,
+ * E_CLIENT_ERROR,
+ * E_CLIENT_ERROR_OUT_OF_SYNC))
+ * handle_out_of_sync_condition (cursor);
+ * else
+ * handle_error_condition (cursor, error);
+ *
+ * g_clear_error (&error);
+ * }]]></programlisting>
+ * </informalexample>
+ * After setting the alphabetic index successfully, you can go ahead
+ * and use e_book_client_cursor_move_by() to load some contacts at the
+ * beginning of the given letter.
+ * </para>
+ * <para>
+ * This API can also result in an %E_CLIENT_ERROR_OUT_OF_SYNC error. This error will
+ * occur at any time that the cursor tries to set the alphabetic index whilst the
+ * addressbook is changing its active locale setting. In the case of a dynamic locale
+ * change, a change notification will be delivered for the #EBookClientCursor:alphabet
+ * property at which point the application should reload anything related to the
+ * alphabet (a #EBookClientCursor::refresh signal will also be delivered at this point).
+ * </para>
+ * <para>
+ * One can determine the appropriate index for a given #EContact by calling
+ * e_book_client_cursor_get_contact_alphabetic_index(), this is useful to use
+ * when updating any indicators in the user interface showing what letter
+ * you have reached in the active alphabet.
+ * </para>
+ * </refsect2>
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+#include <libedata-book/libedata-book.h>
+
+/* Private D-Bus class. */
+#include <e-dbus-address-book-cursor.h>
+
+#include "e-book-client.h"
+#include "e-book-client-cursor.h"
+
+/* Forward declarations */
+typedef struct _SetSexpContext SetSexpContext;
+typedef struct _MoveByContext MoveByContext;
+typedef struct _AlphabetIndexContext AlphabetIndexContext;
+typedef enum _NotificationType NotificationType;
+typedef struct _Notification Notification;
+
+/* GObjectClass */
+static void book_client_cursor_dispose (GObject *object);
+static void book_client_cursor_finalize (GObject *object);
+static void book_client_cursor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void book_client_cursor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+/* GInitable */
+static void e_book_client_cursor_initable_init (GInitableIface *interface);
+static gboolean book_client_cursor_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error);
+
+/* Private mutators */
+static void book_client_cursor_set_client (EBookClientCursor *cursor,
+ EBookClient *client);
+static void book_client_cursor_set_context (EBookClientCursor *cursor,
+ GMainContext *context);
+static GMainContext *book_client_cursor_ref_context (EBookClientCursor *cursor);
+static gboolean book_client_cursor_context_is_current (EBookClientCursor *cursor);
+static void book_client_cursor_set_proxy (EBookClientCursor *cursor,
+ EDBusAddressBookCursor *proxy);
+static void book_client_cursor_set_connection (EBookClientCursor *cursor,
+ GDBusConnection *connection);
+static void book_client_cursor_set_direct_cursor (EBookClientCursor *cursor,
+ EDataBookCursor *direct_cursor);
+static void book_client_cursor_set_object_path (EBookClientCursor *cursor,
+ const gchar *object_path);
+static void book_client_cursor_set_locale (EBookClientCursor *cursor,
+ const gchar *locale);
+static void book_client_cursor_set_revision (EBookClientCursor *cursor,
+ const gchar *revision);
+static void book_client_cursor_set_total (EBookClientCursor *cursor,
+ gint total);
+static void book_client_cursor_set_position (EBookClientCursor *cursor,
+ gint position);
+
+/* Notifications from other threads */
+static void notification_new_string (EBookClientCursor *cursor,
+ NotificationType type,
+ const gchar *value);
+static void notification_new_int (EBookClientCursor *cursor,
+ NotificationType type,
+ gint value);
+static void notification_free (Notification *notification);
+static void notification_queue (EBookClientCursor *cursor,
+ Notification *notification);
+static gboolean notification_dispatch (GWeakRef *weak_ref);
+
+/* Callbacks from EBookClient */
+static void client_revision_changed_cb (EClient *client,
+ const gchar *prop_name,
+ const gchar *prop_value,
+ GWeakRef *weak_ref);
+static void client_locale_changed_cb (EBookClient *book_client,
+ GParamSpec *pspec,
+ GWeakRef *weak_ref);
+
+/* Callbacks from EDBusAddressBookCursor */
+static void proxy_total_changed_cb (EDBusAddressBookCursor *proxy,
+ GParamSpec *pspec,
+ GWeakRef *weak_ref);
+static void proxy_position_changed_cb (EDBusAddressBookCursor *proxy,
+ GParamSpec *pspec,
+ GWeakRef *weak_ref);
+
+/* Callbacks from EDataBookCursor */
+static void dra_total_changed_cb (EDataBookCursor *direct_cursor,
+ GParamSpec *pspec,
+ EBookClientCursor *cursor);
+static void dra_position_changed_cb (EDataBookCursor *direct_cursor,
+ GParamSpec *pspec,
+ EBookClientCursor *cursor);
+
+/* Threaded method call contexts */
+static SetSexpContext *set_sexp_context_new (const gchar *sexp);
+static void set_sexp_context_free (SetSexpContext *context);
+static void set_sexp_thread (GSimpleAsyncResult *simple,
+ GObject *source_object,
+ GCancellable *cancellable);
+static MoveByContext *move_by_context_new (const gchar *revision,
+ EBookCursorOrigin origin,
+ gint count,
+ gboolean fetch_results);
+static void move_by_context_free (MoveByContext *context);
+static void move_by_thread (GSimpleAsyncResult *simple,
+ GObject *source_object,
+ GCancellable *cancellable);
+static AlphabetIndexContext *alphabet_index_context_new (gint index,
+ const gchar *locale);
+static void alphabet_index_context_free (AlphabetIndexContext *context);
+static void alphabet_index_thread (GSimpleAsyncResult *simple,
+ GObject *source_object,
+ GCancellable *cancellable);
+
+enum _NotificationType {
+ REVISION_CHANGED = 0,
+ LOCALE_CHANGED,
+ TOTAL_CHANGED,
+ POSITION_CHANGED,
+ N_NOTIFICATION_TYPES
+};
+
+struct _Notification {
+ GWeakRef cursor;
+ NotificationType type;
+ GValue value;
+};
+
+struct _EBookClientCursorPrivate {
+ /* Weak reference to the EBookClient and
+ * to the GMainContext in which notifications
+ * should be delivered to the EBookClientCursor user */
+ GWeakRef client;
+ GMainContext *main_context;
+ GMutex main_context_lock;
+
+ /* Connection with the addressbook cursor over D-Bus */
+ EDBusAddressBookCursor *dbus_proxy;
+ GDBusConnection *connection;
+ gchar *object_path;
+
+ /* Direct Read Access to the addressbook cursor */
+ EDataBookCursor *direct_cursor;
+
+ /* A local copy of the #EContactFields we are
+ * sorting by (field names)
+ */
+ gchar **sort_fields;
+
+ /* Keep a handle on the current locale according
+ * to the EBookClient, this is also how we
+ * derive the active alphabet
+ */
+ gchar *locale;
+
+ /* Keep a handle on the revision, we need to
+ * hold on to the currently known revision for
+ * DRA mode cursors. Also we trigger the
+ * refresh signal in normal mode whenever the
+ * revision changes
+ */
+ gchar *revision;
+
+ /* A handy collator which we change with locale changes.
+ */
+ ECollator *collator;
+ gint n_labels; /* The amount of labels in the active alphabet */
+
+ /* Client side positional values */
+ gint position;
+ gint total;
+
+ /* Make sure all notifications are delivered in a single idle callback */
+ GSource *notification_source;
+ Notification *notification[N_NOTIFICATION_TYPES];
+ GMutex notifications_lock;
+
+ /* Signal connection ids */
+ gulong revision_changed_id;
+ gulong locale_changed_id;
+ gulong proxy_total_changed_id;
+ gulong proxy_position_changed_id;
+ gulong dra_total_changed_id;
+ gulong dra_position_changed_id;
+};
+
+enum {
+ PROP_0,
+ PROP_SORT_FIELDS,
+ PROP_CLIENT,
+ PROP_CONTEXT,
+ PROP_CONNECTION,
+ PROP_OBJECT_PATH,
+ PROP_DIRECT_CURSOR,
+ PROP_ALPHABET,
+ PROP_TOTAL,
+ PROP_POSITION,
+};
+
+enum {
+ REFRESH,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (
+ EBookClientCursor,
+ e_book_client_cursor,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (
+ G_TYPE_INITABLE,
+ e_book_client_cursor_initable_init))
+
+/****************************************************
+ * GObjectClass *
+ ****************************************************/
+static void
+e_book_client_cursor_class_init (EBookClientCursorClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = book_client_cursor_dispose;
+ object_class->finalize = book_client_cursor_finalize;
+ object_class->set_property = book_client_cursor_set_property;
+ object_class->get_property = book_client_cursor_get_property;
+
+ /**
+ * EBookClientCursor:sort-fields:
+ *
+ * The #EContactField names to sort this cursor with
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_SORT_FIELDS,
+ g_param_spec_boxed (
+ "sort-fields",
+ "Sort Fields",
+ "The #EContactField names to sort this cursor with",
+ G_TYPE_STRV,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * EBookClientCursor:client:
+ *
+ * The #EBookClient which this cursor was created for
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CLIENT,
+ g_param_spec_object (
+ "client",
+ "Client",
+ "The EBookClient for the cursor",
+ E_TYPE_BOOK_CLIENT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * EBookClientCursor:context:
+ *
+ * The #GMainContext in which the #EBookClient created this cursor.
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CONTEXT,
+ g_param_spec_boxed (
+ "context",
+ "Context",
+ "The GMainContext in which this cursor was created",
+ G_TYPE_MAIN_CONTEXT,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * EBookClientCursor:connection:
+ *
+ * The #GDBusConnection to the addressbook server.
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CONNECTION,
+ g_param_spec_object (
+ "connection",
+ "Connection",
+ "The GDBusConnection used "
+ "to create the D-Bus proxy",
+ G_TYPE_DBUS_CONNECTION,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * EBookClientCursor:object-path:
+ *
+ * The D-Bus object path to find the server side cursor object.
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_OBJECT_PATH,
+ g_param_spec_string (
+ "object-path",
+ "Object Path",
+ "The object path used "
+ "to create the D-Bus proxy",
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EBookClientCursor:direct-cursor:
+ *
+ * The direct handle to the #EDataBookCursor for direct read access mode.
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_DIRECT_CURSOR,
+ g_param_spec_object (
+ "direct-cursor",
+ "Direct Cursor",
+ "The EDataBookCursor for direct read access",
+ E_TYPE_DATA_BOOK_CURSOR,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * EBookClientCursor:alphabet:
+ *
+ * The active alphabet.
+ *
+ * The value is a %NULL terminated array of strings,
+ * each string is suitable to display a specific letter
+ * in the active alphabet.
+ *
+ * Indexes from this array can later be used with
+ * e_book_client_cursor_set_alphabetic_index().
+ *
+ * This property will automatically change if the
+ * active locale of the addressbook server changes.
+ *
+ * Property change notifications are guaranteed to be
+ * delivered in the #GMainContext which was the thread
+ * default context at cursor creation time.
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_ALPHABET,
+ g_param_spec_boxed (
+ "alphabet",
+ "Alphabet",
+ "The active alphabet",
+ G_TYPE_STRV,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EBookClientCursor:total:
+ *
+ * The total number of contacts which satisfy the cursor's query.
+ *
+ * Property change notifications are guaranteed to be
+ * delivered in the #GMainContext which was the thread
+ * default context at cursor creation time.
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_TOTAL,
+ g_param_spec_int (
+ "total",
+ "Total",
+ "The total contacts for this cursor's query",
+ 0, G_MAXINT, 0,
+ G_PARAM_READABLE));
+
+ /**
+ * EBookClientCursor:position:
+ *
+ * The current cursor position in the cursor's result list.
+ *
+ * Property change notifications are guaranteed to be
+ * delivered in the #GMainContext which was the thread
+ * default context at cursor creation time.
+ *
+ * Since: 3.12
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_POSITION,
+ g_param_spec_int (
+ "position",
+ "Position",
+ "The current cursor position",
+ 0, G_MAXINT, 0,
+ G_PARAM_READABLE));
+
+ /**
+ * EBookClientCursor::refresh:
+ * @cursor: The #EBookClientCursor which needs to be refreshed
+ *
+ * Indicates that the addressbook has been modified and
+ * that the cursor results should be refreshed.
+ *
+ * This is normally done by calling e_book_client_cursor_move_by()
+ * with an %E_BOOK_CURSOR_ORIGIN_PREVIOUS origin.
+ *
+ * This signal is guaranteed to be delivered in the #GMainContext
+ * which was the thread default context at cursor creation time.
+ *
+ * Since: 3.12
+ */
+ signals[REFRESH] = g_signal_new (
+ "refresh",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EBookClientCursorClass, refresh),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (class, sizeof (EBookClientCursorPrivate));
+}
+
+static void
+e_book_client_cursor_init (EBookClientCursor *cursor)
+{
+ cursor->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (cursor,
+ E_TYPE_BOOK_CLIENT_CURSOR,
+ EBookClientCursorPrivate);
+ g_mutex_init (&cursor->priv->main_context_lock);
+ g_mutex_init (&cursor->priv->notifications_lock);
+}
+
+static void
+book_client_cursor_dispose (GObject *object)
+{
+ EBookClientCursor *cursor = E_BOOK_CLIENT_CURSOR (object);
+ EBookClientCursorPrivate *priv = cursor->priv;
+ gint i;
+
+ book_client_cursor_set_client (cursor, NULL);
+ book_client_cursor_set_proxy (cursor, NULL);
+ book_client_cursor_set_direct_cursor (cursor, NULL);
+ book_client_cursor_set_connection (cursor, NULL);
+ book_client_cursor_set_context (cursor, NULL);
+
+ g_mutex_lock (&cursor->priv->notifications_lock);
+ if (priv->notification_source) {
+ g_source_destroy (priv->notification_source);
+ g_source_unref (priv->notification_source);
+ priv->notification_source = NULL;
+ }
+
+ for (i = 0; i < N_NOTIFICATION_TYPES; i++) {
+ notification_free (priv->notification[i]);
+ priv->notification[i] = NULL;
+ }
+ g_mutex_unlock (&cursor->priv->notifications_lock);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_book_client_cursor_parent_class)->dispose (object);
+}
+
+static void
+book_client_cursor_finalize (GObject *object)
+{
+ EBookClientCursor *cursor = E_BOOK_CLIENT_CURSOR (object);
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ g_free (priv->locale);
+ g_free (priv->revision);
+ g_free (priv->object_path);
+ if (priv->sort_fields)
+ g_strfreev (priv->sort_fields);
+ if (priv->collator)
+ e_collator_unref (priv->collator);
+ g_mutex_clear (&priv->main_context_lock);
+ g_mutex_clear (&cursor->priv->notifications_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_book_client_cursor_parent_class)->finalize (object);
+}
+
+static void
+book_client_cursor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EBookClientCursor *cursor = E_BOOK_CLIENT_CURSOR (object);
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ switch (property_id) {
+ case PROP_SORT_FIELDS:
+ priv->sort_fields = g_value_dup_boxed (value);
+ break;
+
+ case PROP_CLIENT:
+ book_client_cursor_set_client (
+ E_BOOK_CLIENT_CURSOR (object),
+ g_value_get_object (value));
+ break;
+
+ case PROP_CONTEXT:
+ book_client_cursor_set_context (
+ E_BOOK_CLIENT_CURSOR (object),
+ g_value_get_boxed (value));
+ break;
+
+ case PROP_CONNECTION:
+ book_client_cursor_set_connection (
+ E_BOOK_CLIENT_CURSOR (object),
+ g_value_get_object (value));
+ break;
+
+ case PROP_OBJECT_PATH:
+ book_client_cursor_set_object_path (
+ E_BOOK_CLIENT_CURSOR (object),
+ g_value_get_string (value));
+ break;
+
+ case PROP_DIRECT_CURSOR:
+ book_client_cursor_set_direct_cursor (
+ E_BOOK_CLIENT_CURSOR (object),
+ g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+book_client_cursor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CLIENT:
+ g_value_take_object (
+ value,
+ e_book_client_cursor_ref_client (
+ E_BOOK_CLIENT_CURSOR (object)));
+ break;
+
+ case PROP_ALPHABET:
+ g_value_set_boxed (
+ value,
+ e_book_client_cursor_get_alphabet (
+ E_BOOK_CLIENT_CURSOR (object),
+ NULL, NULL, NULL, NULL));
+ break;
+
+ case PROP_TOTAL:
+ g_value_set_int (
+ value,
+ e_book_client_cursor_get_total (
+ E_BOOK_CLIENT_CURSOR (object)));
+ break;
+
+ case PROP_POSITION:
+ g_value_set_int (
+ value,
+ e_book_client_cursor_get_position (
+ E_BOOK_CLIENT_CURSOR (object)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+
+ }
+}
+
+/****************************************************
+ * GInitable *
+ ****************************************************/
+static void
+e_book_client_cursor_initable_init (GInitableIface *interface)
+{
+ interface->init = book_client_cursor_initable_init;
+}
+
+static gboolean
+book_client_cursor_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EBookClientCursor *cursor = E_BOOK_CLIENT_CURSOR (initable);
+ EBookClientCursorPrivate *priv = cursor->priv;
+ EDBusAddressBookCursor *proxy;
+
+ /* We only need a proxy for regular access, no need in DRA mode */
+ if (priv->direct_cursor)
+ return TRUE;
+
+ proxy = e_dbus_address_book_cursor_proxy_new_sync (
+ priv->connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ ADDRESS_BOOK_DBUS_SERVICE_NAME,
+ priv->object_path,
+ cancellable, error);
+
+ if (!proxy)
+ return FALSE;
+
+ book_client_cursor_set_proxy (cursor, proxy);
+ g_object_unref (proxy);
+
+ return TRUE;
+}
+
+/****************************************************
+ * Private Mutators *
+ ****************************************************
+ *
+ * All private mutators are called either in the thread
+ * which e_book_client_get_cursor() was originally called,
+ * or in the object construction process where there is
+ * a well known strong reference to the EBookClientCursor
+ * instance.
+ */
+static void
+book_client_cursor_set_client (EBookClientCursor *cursor,
+ EBookClient *client)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+ EBookClient *current_client;
+
+ g_return_if_fail (client == NULL || E_IS_BOOK_CLIENT (client));
+
+ current_client = e_book_client_cursor_ref_client (cursor);
+
+ /* Clients can't really change, but we set up this
+ * mutator style code just to manage the signal connections
+ * we watch on the client, we need to disconnect them properly.
+ */
+ if (current_client != client) {
+
+ if (current_client) {
+
+ /* Disconnect signals */
+ g_signal_handler_disconnect (current_client, priv->revision_changed_id);
+ g_signal_handler_disconnect (current_client, priv->locale_changed_id);
+ priv->revision_changed_id = 0;
+ priv->locale_changed_id = 0;
+ }
+
+ /* Set the new client */
+ g_weak_ref_set (&priv->client, client);
+
+ if (client) {
+ gchar *revision = NULL;
+
+ /* Connect signals */
+ priv->revision_changed_id =
+ g_signal_connect_data (client, "backend-property-changed",
+ G_CALLBACK (client_revision_changed_cb),
+ e_weak_ref_new (cursor),
+ (GClosureNotify) e_weak_ref_free,
+ 0);
+ priv->locale_changed_id =
+ g_signal_connect_data (client, "notify::locale",
+ G_CALLBACK (client_locale_changed_cb),
+ e_weak_ref_new (cursor),
+ (GClosureNotify) e_weak_ref_free,
+ 0);
+
+ /* Load initial locale & revision */
+ book_client_cursor_set_locale (cursor, e_book_client_get_locale (client));
+
+ /* This loads a cached D-Bus property, no D-Bus activity */
+ e_client_get_backend_property_sync (E_CLIENT (client),
+ CLIENT_BACKEND_PROPERTY_REVISION,
+ &revision, NULL, NULL);
+ book_client_cursor_set_revision (cursor, revision);
+ g_free (revision);
+ }
+ }
+
+ /* e_book_client_cursor_ref_client() gave us a ref */
+ g_clear_object (¤t_client);
+}
+
+static void
+book_client_cursor_set_connection (EBookClientCursor *cursor,
+ GDBusConnection *connection)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ g_return_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection));
+
+ if (priv->connection != connection) {
+
+ if (priv->connection)
+ g_object_unref (priv->connection);
+
+ priv->connection = connection;
+
+ if (priv->connection)
+ g_object_ref (priv->connection);
+ }
+}
+
+static void
+proxy_dispose_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GError *local_error = NULL;
+
+ e_dbus_address_book_cursor_call_dispose_finish (
+ E_DBUS_ADDRESS_BOOK_CURSOR (source_object), result, &local_error);
+
+ if (local_error != NULL) {
+ g_dbus_error_strip_remote_error (local_error);
+ g_warning ("%s: %s", G_STRFUNC, local_error->message);
+ g_error_free (local_error);
+ }
+}
+
+static void
+book_client_cursor_set_proxy (EBookClientCursor *cursor,
+ EDBusAddressBookCursor *proxy)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ g_return_if_fail (proxy == NULL || E_DBUS_IS_ADDRESS_BOOK_CURSOR (proxy));
+
+ if (priv->dbus_proxy != proxy) {
+
+ if (priv->dbus_proxy) {
+ g_signal_handler_disconnect (priv->dbus_proxy, priv->proxy_total_changed_id);
+ g_signal_handler_disconnect (priv->dbus_proxy, priv->proxy_position_changed_id);
+ priv->proxy_total_changed_id = 0;
+ priv->proxy_position_changed_id = 0;
+
+ /* Call D-Bus dispose() asynchronously
+ * so we don't block in our dispose() phase.*/
+ e_dbus_address_book_cursor_call_dispose (
+ priv->dbus_proxy, NULL,
+ proxy_dispose_cb, NULL);
+
+ g_object_unref (priv->dbus_proxy);
+ }
+
+ priv->dbus_proxy = proxy;
+
+ if (priv->dbus_proxy) {
+ gint position, total;
+
+ priv->proxy_total_changed_id =
+ g_signal_connect_data (priv->dbus_proxy, "notify::total",
+ G_CALLBACK (proxy_total_changed_cb),
+ e_weak_ref_new (cursor),
+ (GClosureNotify) e_weak_ref_free,
+ 0);
+ priv->proxy_position_changed_id =
+ g_signal_connect_data (priv->dbus_proxy, "notify::position",
+ G_CALLBACK (proxy_position_changed_cb),
+ e_weak_ref_new (cursor),
+ (GClosureNotify) e_weak_ref_free,
+ 0);
+
+ /* Set initial values */
+ total = e_dbus_address_book_cursor_get_total (proxy);
+ position = e_dbus_address_book_cursor_get_position (proxy);
+ book_client_cursor_set_total (cursor, total);
+ book_client_cursor_set_position (cursor, position);
+
+ g_object_ref (priv->dbus_proxy);
+ }
+ }
+}
+
+static void
+book_client_cursor_set_context (EBookClientCursor *cursor,
+ GMainContext *context)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ g_mutex_lock (&cursor->priv->main_context_lock);
+
+ if (priv->main_context != context) {
+ if (priv->main_context)
+ g_main_context_unref (priv->main_context);
+
+ priv->main_context = context;
+
+ if (priv->main_context)
+ g_main_context_ref (priv->main_context);
+ }
+
+ g_mutex_unlock (&cursor->priv->main_context_lock);
+}
+
+static GMainContext *
+book_client_cursor_ref_context (EBookClientCursor *cursor)
+{
+ GMainContext *main_context = NULL;
+
+ /* This is called from D-Bus callbacks which will fire from
+ * whichever thread the EBookClient created the EBookClientCursor
+ * in, and also from EBookClient signal callbacks which get
+ * fired in the thread that the EBookClient was created in,
+ * which might not be the same thread that e_book_client_get_cursor()
+ * was called from.
+ */
+ g_mutex_lock (&cursor->priv->main_context_lock);
+
+ if (cursor->priv->main_context)
+ main_context = g_main_context_ref (cursor->priv->main_context);
+
+ g_mutex_unlock (&cursor->priv->main_context_lock);
+
+ return main_context;
+}
+
+static gboolean
+book_client_cursor_context_is_current (EBookClientCursor *cursor)
+{
+ GMainContext *main_context, *current_context;
+ gboolean is_current = FALSE;
+
+ main_context = book_client_cursor_ref_context (cursor);
+ current_context = g_main_context_ref_thread_default ();
+
+ if (main_context) {
+
+ is_current = (main_context == current_context);
+
+ g_main_context_unref (main_context);
+ }
+
+ g_main_context_unref (current_context);
+
+ return is_current;
+}
+
+static void
+book_client_cursor_set_direct_cursor (EBookClientCursor *cursor,
+ EDataBookCursor *direct_cursor)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ g_return_if_fail (direct_cursor == NULL || E_IS_DATA_BOOK_CURSOR (direct_cursor));
+
+ if (priv->direct_cursor != direct_cursor) {
+
+ if (priv->direct_cursor) {
+
+ g_signal_handler_disconnect (priv->direct_cursor, priv->dra_total_changed_id);
+ g_signal_handler_disconnect (priv->direct_cursor, priv->dra_position_changed_id);
+ priv->dra_total_changed_id = 0;
+ priv->dra_position_changed_id = 0;
+
+ g_object_unref (priv->direct_cursor);
+ }
+
+ priv->direct_cursor = direct_cursor;
+
+ if (priv->direct_cursor) {
+ GError *error = NULL;
+ gchar *freeme = NULL;
+ gint total, position;
+
+ priv->dra_total_changed_id =
+ g_signal_connect (priv->direct_cursor, "notify::total",
+ G_CALLBACK (dra_total_changed_cb),
+ cursor);
+ priv->dra_position_changed_id =
+ g_signal_connect (priv->direct_cursor, "notify::position",
+ G_CALLBACK (dra_position_changed_cb),
+ cursor);
+
+ /* Load initial locale */
+ if (priv->direct_cursor &&
+ !e_data_book_cursor_load_locale (priv->direct_cursor,
+ &freeme, &error)) {
+ g_warning ("Error loading locale in direct read access cursor: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ g_free (freeme);
+
+ /* Set initial values */
+ total = e_data_book_cursor_get_total (priv->direct_cursor);
+ position = e_data_book_cursor_get_position (priv->direct_cursor);
+ book_client_cursor_set_total (cursor, total);
+ book_client_cursor_set_position (cursor, position);
+
+ g_object_ref (priv->direct_cursor);
+ }
+ }
+}
+
+static void
+book_client_cursor_set_object_path (EBookClientCursor *cursor,
+ const gchar *object_path)
+{
+ g_return_if_fail (cursor->priv->object_path == NULL);
+
+ cursor->priv->object_path = g_strdup (object_path);
+}
+
+static void
+book_client_cursor_set_locale (EBookClientCursor *cursor,
+ const gchar *locale)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+ GError *error = NULL;
+
+ if (g_strcmp0 (priv->locale, locale) == 0)
+ return;
+
+ g_free (priv->locale);
+ if (priv->collator)
+ e_collator_unref (priv->collator);
+
+ priv->locale = g_strdup (locale);
+ priv->collator = e_collator_new (locale, &error);
+
+ if (!priv->collator) {
+ g_warning ("Error loading collator for locale '%s': %s",
+ locale, error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ e_collator_get_index_labels (priv->collator,
+ &priv->n_labels,
+ NULL, NULL, NULL);
+
+ /* The server side EDataBookCursor should have already
+ * reset its cursor values internally and notified
+ * a new total & position value, however we need to
+ * explicitly load the new locale for DRA cursors.
+ */
+ if (priv->direct_cursor &&
+ !e_data_book_cursor_load_locale (priv->direct_cursor, NULL, &error)) {
+ g_warning ("Error loading locale in direct read access cursor: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+
+ /* Notify the alphabet change */
+ g_object_notify (G_OBJECT (cursor), "alphabet");
+
+ /* The alphabet changing should have been enough,
+ * but still trigger a refresh
+ */
+ g_signal_emit (cursor, signals[REFRESH], 0);
+}
+
+static void
+book_client_cursor_set_revision (EBookClientCursor *cursor,
+ const gchar *revision)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ if (g_strcmp0 (priv->revision, revision) != 0) {
+
+ g_free (priv->revision);
+ priv->revision = g_strdup (revision);
+
+ /* In DRA mode we need to reload our local
+ * total / position calculations with EDataBookCursor APIs
+ */
+ if (priv->direct_cursor) {
+ GError *error = NULL;
+
+ if (!e_data_book_cursor_recalculate (priv->direct_cursor, &error)) {
+ g_warning ("Error calcualting cursor position: %s", error->message);
+ } else {
+ g_object_freeze_notify (G_OBJECT (cursor));
+ book_client_cursor_set_total (cursor, e_data_book_cursor_get_total
(priv->direct_cursor));
+ book_client_cursor_set_position (cursor, e_data_book_cursor_get_position
(priv->direct_cursor));
+ g_object_thaw_notify (G_OBJECT (cursor));
+ }
+ }
+
+ /* The addressbook has changed, need a refresh */
+ g_signal_emit (cursor, signals[REFRESH], 0);
+ }
+}
+
+static void
+book_client_cursor_set_total (EBookClientCursor *cursor,
+ gint total)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ if (priv->total != total) {
+ priv->total = total;
+ g_object_notify (G_OBJECT (cursor), "total");
+ }
+}
+
+static void
+book_client_cursor_set_position (EBookClientCursor *cursor,
+ gint position)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+
+ if (priv->position != position) {
+ priv->position = position;
+ g_object_notify (G_OBJECT (cursor), "position");
+ }
+}
+
+/****************************************************
+ * Notifications from other threads *
+ ****************************************************
+ *
+ * The notification subsystem takes care of calling
+ * our private mutator functions from the thread in
+ * which e_book_client_get_cursor() was originally
+ * called, where it's safe to emit signals on the
+ * EBookClientCursor instance.
+ *
+ * The notification functions, notification_new_string()
+ * and notification_new_int() must be called where
+ * a strong reference to the EBookClientCursor exists.
+ */
+
+static void
+notification_new_string (EBookClientCursor *cursor,
+ NotificationType type,
+ const gchar *value)
+{
+ Notification *notification = g_slice_new0 (Notification);
+
+ notification->type = type;
+ g_weak_ref_set (¬ification->cursor, cursor);
+
+ g_value_init (¬ification->value, G_TYPE_STRING);
+ g_value_set_string (¬ification->value, value);
+
+ notification_queue (cursor, notification);
+}
+
+static void
+notification_new_int (EBookClientCursor *cursor,
+ NotificationType type,
+ gint value)
+{
+ Notification *notification = g_slice_new0 (Notification);
+
+ notification->type = type;
+ g_weak_ref_set (¬ification->cursor, cursor);
+
+ g_value_init (¬ification->value, G_TYPE_INT);
+ g_value_set_int (¬ification->value, value);
+
+ notification_queue (cursor, notification);
+}
+
+static void
+notification_free (Notification *notification)
+{
+ if (notification) {
+ g_weak_ref_set (¬ification->cursor, NULL);
+ g_value_unset (¬ification->value);
+ g_slice_free (Notification, notification);
+ }
+}
+
+static void
+notification_queue (EBookClientCursor *cursor,
+ Notification *notification)
+{
+ EBookClientCursorPrivate *priv = cursor->priv;
+ GMainContext *context;
+
+ g_mutex_lock (&cursor->priv->notifications_lock);
+
+ notification_free (priv->notification[notification->type]);
+ priv->notification[notification->type] = notification;
+
+ context = book_client_cursor_ref_context (cursor);
+
+ if (context && priv->notification_source == NULL) {
+ /* Hold on to a reference, release our reference in dispatch() */
+ priv->notification_source = g_idle_source_new ();
+ g_source_set_callback (priv->notification_source,
+ (GSourceFunc) notification_dispatch,
+ e_weak_ref_new (cursor),
+ (GDestroyNotify) e_weak_ref_free);
+ g_source_attach (priv->notification_source, context);
+ g_main_context_unref (context);
+ }
+
+ g_mutex_unlock (&cursor->priv->notifications_lock);
+}
+
+static gboolean
+notification_dispatch (GWeakRef *weak_ref)
+{
+ EBookClientCursor *cursor;
+ EBookClientCursorPrivate *priv;
+ Notification *notification[N_NOTIFICATION_TYPES];
+ gint i;
+
+ cursor = g_weak_ref_get (weak_ref);
+ if (!cursor)
+ return FALSE;
+
+ priv = cursor->priv;
+
+ /* Collect notifications now and let notifications
+ * be queued from other threads after this point
+ */
+ g_mutex_lock (&cursor->priv->notifications_lock);
+
+ for (i = 0; i < N_NOTIFICATION_TYPES; i++) {
+ notification[i] = priv->notification[i];
+ priv->notification[i] = NULL;
+ }
+
+ g_source_unref (priv->notification_source);
+ priv->notification_source = NULL;
+ g_mutex_unlock (&cursor->priv->notifications_lock);
+
+ g_object_freeze_notify (G_OBJECT (cursor));
+
+ if (notification[TOTAL_CHANGED])
+ book_client_cursor_set_total (
+ cursor,
+ g_value_get_int (&(notification[TOTAL_CHANGED]->value)));
+
+ if (notification[POSITION_CHANGED])
+ book_client_cursor_set_position (
+ cursor,
+ g_value_get_int (&(notification[POSITION_CHANGED]->value)));
+
+ if (notification[REVISION_CHANGED])
+ book_client_cursor_set_revision (
+ cursor,
+ g_value_get_string (&(notification[REVISION_CHANGED]->value)));
+
+ if (notification[LOCALE_CHANGED])
+ book_client_cursor_set_locale (
+ cursor,
+ g_value_get_string (&(notification[LOCALE_CHANGED]->value)));
+
+ g_object_thaw_notify (G_OBJECT (cursor));
+
+ for (i = 0; i < N_NOTIFICATION_TYPES; i++)
+ notification_free (notification[i]);
+
+ g_object_unref (cursor);
+
+ return FALSE;
+}
+
+/****************************************************
+ * Callbacks from EBookClient *
+ ****************************************************/
+static void
+client_revision_changed_cb (EClient *client,
+ const gchar *prop_name,
+ const gchar *prop_value,
+ GWeakRef *weak_ref)
+{
+ EBookClientCursor *cursor;
+
+ if (g_strcmp0 (prop_name, CLIENT_BACKEND_PROPERTY_REVISION) != 0)
+ return;
+
+ cursor = g_weak_ref_get (weak_ref);
+ if (cursor) {
+ notification_new_string (cursor, REVISION_CHANGED, prop_value);
+ g_object_unref (cursor);
+ }
+}
+
+static void
+client_locale_changed_cb (EBookClient *book_client,
+ GParamSpec *pspec,
+ GWeakRef *weak_ref)
+{
+ EBookClientCursor *cursor;
+
+ cursor = g_weak_ref_get (weak_ref);
+ if (cursor) {
+ notification_new_string (cursor, LOCALE_CHANGED, e_book_client_get_locale (book_client));
+ g_object_unref (cursor);
+ }
+}
+
+/****************************************************
+ * Callbacks from EDBusAddressBookCursor *
+ ****************************************************/
+static void
+proxy_total_changed_cb (EDBusAddressBookCursor *proxy,
+ GParamSpec *pspec,
+ GWeakRef *weak_ref)
+{
+ EBookClientCursor *cursor;
+
+ cursor = g_weak_ref_get (weak_ref);
+ if (cursor) {
+ notification_new_int (cursor, TOTAL_CHANGED,
+ e_dbus_address_book_cursor_get_total (proxy));
+ g_object_unref (cursor);
+ }
+}
+
+static void
+proxy_position_changed_cb (EDBusAddressBookCursor *proxy,
+ GParamSpec *pspec,
+ GWeakRef *weak_ref)
+{
+ EBookClientCursor *cursor;
+
+ cursor = g_weak_ref_get (weak_ref);
+ if (cursor) {
+ notification_new_int (cursor, POSITION_CHANGED,
+ e_dbus_address_book_cursor_get_position (proxy));
+ g_object_unref (cursor);
+ }
+}
+
+/****************************************************
+ * Callbacks from EDBusAddressBookCursor *
+ ****************************************************/
+static void
+dra_total_changed_cb (EDataBookCursor *direct_cursor,
+ GParamSpec *pspec,
+ EBookClientCursor *cursor)
+{
+ notification_new_int (cursor, TOTAL_CHANGED,
+ e_data_book_cursor_get_total (direct_cursor));
+}
+
+static void
+dra_position_changed_cb (EDataBookCursor *direct_cursor,
+ GParamSpec *pspec,
+ EBookClientCursor *cursor)
+{
+ notification_new_int (cursor, POSITION_CHANGED,
+ e_data_book_cursor_get_position (direct_cursor));
+}
+
+/****************************************************
+ * Threaded method call contexts *
+ ****************************************************
+ *
+ * This subsystem is simply a toolbox of helper functions
+ * to execute synchronous D-Bus method calls while providing
+ * an asynchronous API.
+ *
+ * We choose this method of asynchronous D-Bus calls only
+ * to be consistent with the rest of the libebook library.
+ */
+struct _MoveByContext {
+ gchar *revision;
+ EBookCursorOrigin origin;
+ gint count;
+ gboolean fetch_results;
+ GSList *contacts;
+ guint new_total;
+ guint new_position;
+};
+
+struct _AlphabetIndexContext {
+ gint index;
+ gchar *locale;
+ guint new_total;
+ guint new_position;
+};
+
+struct _SetSexpContext {
+ gchar *sexp;
+ guint new_total;
+ guint new_position;
+};
+
+static SetSexpContext *
+set_sexp_context_new (const gchar *sexp)
+{
+ SetSexpContext *context = g_slice_new0 (SetSexpContext);
+
+ context->sexp = g_strdup (sexp);
+
+ return context;
+}
+
+static void
+set_sexp_context_free (SetSexpContext *context)
+{
+ if (context) {
+ g_free (context->sexp);
+ g_slice_free (SetSexpContext, context);
+ }
+}
+
+static gboolean
+set_sexp_sync_internal (EBookClientCursor *cursor,
+ const gchar *sexp,
+ guint *new_total,
+ guint *new_position,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EBookClientCursorPrivate *priv;
+ gchar *utf8_sexp;
+ GError *local_error = NULL;
+
+ priv = cursor->priv;
+
+ if (priv->direct_cursor) {
+
+ if (!e_data_book_cursor_set_sexp (priv->direct_cursor,
+ sexp, error))
+ return FALSE;
+
+ *new_total = e_data_book_cursor_get_total (priv->direct_cursor);
+ *new_position = e_data_book_cursor_get_position (priv->direct_cursor);
+
+ return TRUE;
+ }
+
+ utf8_sexp = e_util_utf8_make_valid (sexp);
+ e_dbus_address_book_cursor_call_set_query_sync (
+ priv->dbus_proxy,
+ utf8_sexp,
+ new_total,
+ new_position,
+ cancellable,
+ &local_error);
+ g_free (utf8_sexp);
+
+ if (local_error != NULL) {
+ g_dbus_error_strip_remote_error (local_error);
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+set_sexp_thread (GSimpleAsyncResult *simple,
+ GObject *source_object,
+ GCancellable *cancellable)
+{
+ SetSexpContext *context;
+ GError *local_error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+ set_sexp_sync_internal (E_BOOK_CLIENT_CURSOR (source_object),
+ context->sexp,
+ &context->new_total,
+ &context->new_position,
+ cancellable,
+ &local_error);
+
+ if (local_error != NULL)
+ g_simple_async_result_take_error (simple, local_error);
+}
+
+static MoveByContext *
+move_by_context_new (const gchar *revision,
+ EBookCursorOrigin origin,
+ gint count,
+ gboolean fetch_results)
+{
+ MoveByContext *context = g_slice_new0 (MoveByContext);
+
+ context->revision = g_strdup (revision);
+ context->origin = origin;
+ context->count = count;
+ context->fetch_results = fetch_results;
+
+ return context;
+}
+
+static void
+move_by_context_free (MoveByContext *context)
+{
+ if (context) {
+ g_free (context->revision);
+ g_slist_free_full (context->contacts, (GDestroyNotify)g_object_unref);
+ g_slice_free (MoveByContext, context);
+ }
+}
+
+static gboolean
+move_by_sync_internal (EBookClientCursor *cursor,
+ const gchar *revision,
+ EBookCursorOrigin origin,
+ gint count,
+ GSList **out_contacts,
+ guint *new_total,
+ guint *new_position,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EBookClientCursorPrivate *priv;
+ GError *local_error = NULL;
+ gchar **vcards = NULL;
+
+ priv = cursor->priv;
+
+ if (priv->direct_cursor) {
+ GSList *results = NULL, *l;
+ GSList *contacts = NULL;
+
+ if (!e_data_book_cursor_move_by (priv->direct_cursor,
+ revision,
+ origin,
+ count,
+ out_contacts ? &results : NULL,
+ error))
+ return FALSE;
+
+ for (l = results; l; l = l->next) {
+ gchar *vcard = l->data;
+ EContact *contact = e_contact_new_from_vcard (vcard);
+
+ if (contact)
+ contacts = g_slist_prepend (contacts, contact);
+ }
+
+ g_slist_free_full (results, (GDestroyNotify)g_free);
+
+ if (out_contacts)
+ *out_contacts = g_slist_reverse (contacts);
+ else
+ g_slist_free_full (contacts, (GDestroyNotify)g_object_unref);
+
+ *new_total = e_data_book_cursor_get_total (priv->direct_cursor);
+ *new_position = e_data_book_cursor_get_position (priv->direct_cursor);
+
+ return TRUE;
+ }
+
+ e_dbus_address_book_cursor_call_move_by_sync (
+ priv->dbus_proxy,
+ revision,
+ origin, count,
+ out_contacts != NULL,
+ &vcards,
+ new_total,
+ new_position,
+ cancellable,
+ &local_error);
+
+ if (local_error != NULL) {
+ g_dbus_error_strip_remote_error (local_error);
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ if (vcards != NULL) {
+ EContact *contact;
+ GSList *tmp = NULL;
+ gint i;
+
+ for (i = 0; vcards[i] != NULL; i++) {
+ contact = e_contact_new_from_vcard (vcards[i]);
+ tmp = g_slist_prepend (tmp, contact);
+ }
+
+ *out_contacts = g_slist_reverse (tmp);
+
+ g_strfreev (vcards);
+ }
+
+ return TRUE;
+}
+
+static void
+move_by_thread (GSimpleAsyncResult *simple,
+ GObject *source_object,
+ GCancellable *cancellable)
+{
+ MoveByContext *context;
+ GError *local_error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ move_by_sync_internal (E_BOOK_CLIENT_CURSOR (source_object),
+ context->revision,
+ context->origin,
+ context->count,
+ context->fetch_results ? &(context->contacts) : NULL,
+ &context->new_total,
+ &context->new_position,
+ cancellable, &local_error);
+
+ if (local_error != NULL)
+ g_simple_async_result_take_error (simple, local_error);
+}
+
+static AlphabetIndexContext *
+alphabet_index_context_new (gint index,
+ const gchar *locale)
+{
+ AlphabetIndexContext *context = g_slice_new0 (AlphabetIndexContext);
+
+ context->index = index;
+ context->locale = g_strdup (locale);
+
+ return context;
+}
+
+static void
+alphabet_index_context_free (AlphabetIndexContext *context)
+{
+ if (context) {
+ g_free (context->locale);
+ g_slice_free (AlphabetIndexContext, context);
+ }
+}
+
+static gboolean
+set_alphabetic_index_sync_internal (EBookClientCursor *cursor,
+ gint index,
+ const gchar *locale,
+ guint *new_total,
+ guint *new_position,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EBookClientCursorPrivate *priv;
+ GError *local_error = NULL;
+
+ priv = cursor->priv;
+
+ if (priv->direct_cursor) {
+
+ if (!e_data_book_cursor_set_alphabetic_index (priv->direct_cursor,
+ index,
+ locale,
+ error))
+ return FALSE;
+
+ *new_total = e_data_book_cursor_get_total (priv->direct_cursor);
+ *new_position = e_data_book_cursor_get_position (priv->direct_cursor);
+
+ return TRUE;
+ }
+
+ e_dbus_address_book_cursor_call_set_alphabetic_index_sync (
+ cursor->priv->dbus_proxy,
+ index, locale,
+ new_total,
+ new_position,
+ cancellable,
+ &local_error);
+
+ if (local_error != NULL) {
+ g_dbus_error_strip_remote_error (local_error);
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+alphabet_index_thread (GSimpleAsyncResult *simple,
+ GObject *source_object,
+ GCancellable *cancellable)
+{
+ AlphabetIndexContext *context;
+ GError *local_error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ set_alphabetic_index_sync_internal (E_BOOK_CLIENT_CURSOR (source_object),
+ context->index,
+ context->locale,
+ &context->new_total,
+ &context->new_position,
+ cancellable,
+ &local_error);
+
+ if (local_error != NULL)
+ g_simple_async_result_take_error (simple, local_error);
+}
+
+/****************************************************
+ * API *
+ ****************************************************/
+/**
+ * e_book_client_cursor_ref_client:
+ * @cursor: an #EBookClientCursor
+ *
+ * Returns the #EBookClientCursor:client associated with @cursor.
+ *
+ * The returned #EBookClient is referenced because the cursor
+ * does not keep a strong reference to the client.
+ *
+ * Unreference the #EBookClient with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): an #EBookClient
+ *
+ * Since: 3.12
+ */
+EBookClient *
+e_book_client_cursor_ref_client (EBookClientCursor *cursor)
+{
+ g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), NULL);
+
+ return g_weak_ref_get (&cursor->priv->client);
+}
+
+/**
+ * e_book_client_cursor_get_alphabet:
+ * @cursor: an #EBookClientCursor
+ * @n_labels: (out) (allow-none): The number of labels in the active alphabet
+ * @underflow: (allow-none) (out): The underflow index, for any words which sort below the active alphabet
+ * @inflow: (allow-none) (out): The inflow index, for any words which sort between the active alphabets (if
there is more than one)
+ * @overflow: (allow-none) (out): The overflow index, for any words which sort above the active alphabet
+ *
+ * Fetches the array of displayable labels for the active alphabet.
+ *
+ * The active alphabet is based on the current locale configuration of the
+ * addressbook, and can be a different alphabet for locales requiring non-Latin
+ * language scripts. These UTF-8 labels are appropriate to display in a user
+ * interface to represent the alphabetic position of the cursor in the user's
+ * native alphabet.
+ *
+ * The positions of these labels in the returned array can later be used
+ * with e_book_client_cursor_set_alphabetic_index() to explicitly set the
+ * cursor position to a given index in the active alphabet. This allows
+ * one to construct a user interface which allows the user to jump to
+ * a given letter in the ordered cursor results.
+ *
+ * The alphabet can periodically change when the system locale changes, change
+ * notifications will be delivered asynchronously to the #EBookClientCursor:alphabet
+ * property at any time the system locale changes and the active alphabet is updated.
+ *
+ * The @underflow, @inflow and @overflow parameters allow one to observe which
+ * indexes Evolution Data Server is using to store words which sort outside
+ * of the alphabet, for instance words from foreign language scripts and
+ * words which start with numeric characters, or other types of character
+ * (the @inflow index is currently unused, but can be used for words which
+ * sort between the active alphabets, if more than one alphabet is displayed
+ * for a given locale).
+ *
+ * Returns: (array zero-terminated=1) (element-type utf8) (transfer none):
+ * The array of displayable labels for each index in the active alphabet.
+ *
+ * Since: 3.12
+ */
+const gchar * const *
+e_book_client_cursor_get_alphabet (EBookClientCursor *cursor,
+ gint *n_labels,
+ gint *underflow,
+ gint *inflow,
+ gint *overflow)
+{
+ EBookClientCursorPrivate *priv;
+
+ g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), NULL);
+
+ priv = cursor->priv;
+
+ return e_collator_get_index_labels (priv->collator,
+ n_labels,
+ underflow,
+ inflow,
+ overflow);
+}
+
+/**
+ * e_book_client_cursor_get_total:
+ * @cursor: an #EBookClientCursor
+ *
+ * Fetches the total number of contacts in the addressbook
+ * which match @cursor's query
+ *
+ * Returns: The total number of contacts matching @cursor's query
+ *
+ * Since: 3.12
+ */
+gint
+e_book_client_cursor_get_total (EBookClientCursor *cursor)
+{
+ g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), -1);
+
+ return cursor->priv->total;
+}
+
+/**
+ * e_book_client_cursor_get_position:
+ * @cursor: an #EBookClientCursor
+ *
+ * Fetches the current cursor position within @cursor's
+ * query results.
+ *
+ * The position value can be any where from 0 to the total
+ * number of contacts at any given time, a value of 0 indicates
+ * that the cursor is positioned before the contact list, if
+ * the position is equal to the total, as returned by
+ * e_book_client_cursor_get_total(), then the cursor points
+ * to the last contact in @cursor's query results.
+ *
+ * Returns: The current cursor position
+ *
+ * Since: 3.12
+ */
+gint
+e_book_client_cursor_get_position (EBookClientCursor *cursor)
+{
+ g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), -1);
+
+ return cursor->priv->position;
+}
+
+/**
+ * e_book_client_cursor_set_sexp:
+ * @cursor: an #EBookClientCursor
+ * @sexp: the new search expression for @cursor
+ * @cancellable: (allow-none): a #GCancellable to optionally cancel this operation while in progress
+ * @callback: callback to call when a result is ready
+ * @user_data: user data for the @callback
+ *
+ * Sets the <link linkend="cursor-search">Search Expression</link> for the cursor.
+ *
+ * See: e_book_client_cursor_set_sexp_sync().
+ *
+ * This asynchronous call is completed with a call to
+ * e_book_client_cursor_set_sexp_finish() from the specified @callback.
+ *
+ * Since: 3.12
+ */
+void
+e_book_client_cursor_set_sexp (EBookClientCursor *cursor,
+ const gchar *sexp,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+
+
+ GSimpleAsyncResult *simple;
+ SetSexpContext *context;
+
+ g_return_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor));
+ g_return_if_fail (callback != NULL);
+
+ context = set_sexp_context_new (sexp);
+ simple = g_simple_async_result_new (G_OBJECT (cursor),
+ callback, user_data,
+ e_book_client_cursor_set_sexp);
+
+ g_simple_async_result_set_check_cancellable (simple, cancellable);
+ g_simple_async_result_set_op_res_gpointer (simple, context,
+ (GDestroyNotify) set_sexp_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, set_sexp_thread,
+ G_PRIORITY_DEFAULT, cancellable);
+
+ g_object_unref (simple);
+}
+
+/**
+ * e_book_client_cursor_set_sexp_finish:
+ * @cursor: an #EBookClientCursor
+ * @result: a #GAsyncResult
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Completes an asynchronous call initiated by e_book_client_cursor_set_sexp(), reporting
+ * whether the new search expression was accepted.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_client_cursor_set_sexp_finish (EBookClientCursor *cursor,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ SetSexpContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (cursor),
+ e_book_client_cursor_set_sexp), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ /* If we are in the thread where the cursor was created,
+ * then synchronize the new total & position right away
+ */
+ if (book_client_cursor_context_is_current (cursor)) {
+ g_object_freeze_notify (G_OBJECT (cursor));
+ book_client_cursor_set_total (cursor, context->new_total);
+ book_client_cursor_set_position (cursor, context->new_position);
+ g_object_thaw_notify (G_OBJECT (cursor));
+ }
+
+ return TRUE;
+}
+
+/**
+ * e_book_client_cursor_set_sexp_sync:
+ * @cursor: an #EBookClientCursor
+ * @sexp: the new search expression for @cursor
+ * @cancellable: (allow-none): a #GCancellable to optionally cancel this operation while in progress
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Sets the <link linkend="cursor-search">Search Expression</link> for the cursor.
+ *
+ * A side effect of setting the search expression is that the
+ * #EBookClientCursor:position and #EBookClientCursor:total
+ * properties will be updated.
+ *
+ * If this method is called from the same thread context in which
+ * the cursor was created, then the updates to the #EBookClientCursor:position
+ * and #EBookClientCursor:total properties are guaranteed to be delivered
+ * synchronously upon successful completion of setting the search expression.
+ * Otherwise, notifications will be delivered asynchronously in the cursor's
+ * original thread context.
+ *
+ * If the backend does not support the given search expression,
+ * an %E_CLIENT_ERROR_INVALID_QUERY error will be set.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_client_cursor_set_sexp_sync (EBookClientCursor *cursor,
+ const gchar *sexp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success;
+ guint new_total = 0, new_position = 0;
+
+ g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), FALSE);
+
+ success = set_sexp_sync_internal (cursor,
+ sexp,
+ &new_total,
+ &new_position,
+ cancellable,
+ error);
+
+ /* If we are in the thread where the cursor was created,
+ * then synchronize the new total & position right away
+ */
+ if (success && book_client_cursor_context_is_current (cursor)) {
+ g_object_freeze_notify (G_OBJECT (cursor));
+ book_client_cursor_set_total (cursor, new_total);
+ book_client_cursor_set_position (cursor, new_position);
+ g_object_thaw_notify (G_OBJECT (cursor));
+ }
+
+ return success;
+}
+
+/**
+ * e_book_client_cursor_move_by:
+ * @cursor: an #EBookClientCursor
+ * @origin: the #EBookCursorOrigin for this move
+ * @count: a positive or negative amount of contacts to try and fetch
+ * @fetch_results: whether to fetch the list of results, if %FALSE then only the position and cursor value
is modified.
+ * @cancellable: (allow-none): a #GCancellable to optionally cancel this operation while in progress
+ * @callback: callback to call when a result is ready
+ * @user_data: user data for the @callback
+ *
+ * <link linkend="cursor-iteration">Moves the cursor through the results</link> by
+ * a maximum of @count and fetch the results traversed.
+ *
+ * If @fetch_results is %FALSE, the cursor will be moved but no results will be
+ * fetched from the addressbook, this can be useful to reduce D-Bus traffic
+ * when the cursor must be moved but results are not needed.
+ *
+ * See: e_book_client_cursor_move_by_sync().
+ *
+ * This asynchronous call is completed with a call to
+ * e_book_client_cursor_move_by_finish() from the specified @callback.
+ *
+ * Since: 3.12
+ */
+void
+e_book_client_cursor_move_by (EBookClientCursor *cursor,
+ EBookCursorOrigin origin,
+ gint count,
+ gboolean fetch_results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ MoveByContext *context;
+
+ g_return_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor));
+ g_return_if_fail (count != 0 || origin == E_BOOK_CURSOR_ORIGIN_RESET);
+ g_return_if_fail (callback != NULL);
+
+ context = move_by_context_new (cursor->priv->revision,
+ origin, count, fetch_results);
+ simple = g_simple_async_result_new (G_OBJECT (cursor),
+ callback, user_data,
+ e_book_client_cursor_move_by);
+
+ g_simple_async_result_set_check_cancellable (simple, cancellable);
+ g_simple_async_result_set_op_res_gpointer (simple, context,
+ (GDestroyNotify) move_by_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, move_by_thread,
+ G_PRIORITY_DEFAULT, cancellable);
+
+ g_object_unref (simple);
+}
+
+/**
+ * e_book_client_cursor_move_by_finish:
+ * @cursor: an #EBookClientCursor
+ * @result: a #GAsyncResult
+ * @out_contacts: (element-type EContact) (out) (transfer full) (allow-none): return location for a #GSList
of #EContacts
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Completes an asynchronous call initiated by e_book_client_cursor_move_by(), fetching
+ * any contacts which might have been returned by the call.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_client_cursor_move_by_finish (EBookClientCursor *cursor,
+ GAsyncResult *result,
+ GSList **out_contacts,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ MoveByContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (cursor),
+ e_book_client_cursor_move_by), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (out_contacts != NULL) {
+ *out_contacts = context->contacts;
+ context->contacts = NULL;
+ }
+
+ /* If we are in the thread where the cursor was created,
+ * then synchronize the new total & position right away
+ */
+ if (book_client_cursor_context_is_current (cursor)) {
+ g_object_freeze_notify (G_OBJECT (cursor));
+ book_client_cursor_set_total (cursor, context->new_total);
+ book_client_cursor_set_position (cursor, context->new_position);
+ g_object_thaw_notify (G_OBJECT (cursor));
+ }
+
+ return TRUE;
+}
+
+/**
+ * e_book_client_cursor_move_by_sync:
+ * @cursor: an #EBookClientCursor
+ * @origin: the #EBookCursorOrigin for this move
+ * @count: a positive or negative amount of contacts to try and fetch
+ * @out_contacts: (element-type EContact) (out) (transfer full) (allow-none): return location for a #GSList
of #EContacts
+ * @cancellable: (allow-none): a #GCancellable to optionally cancel this operation while in progress
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * <link linkend="cursor-iteration">Moves the cursor through the results</link> by
+ * a maximum of @count and fetch the results traversed.
+ *
+ * If @out_contacts is %NULL, the cursor will be moved but no results will be
+ * fetched from the addressbook, this can be useful to reduce D-Bus traffic
+ * when the cursor must be moved but results are not needed.
+ *
+ * If @count is negative, then the cursor will move backwards.
+ *
+ * If @cursor's position is %0, 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.
+ *
+ * The #EBookCursorOrigin allows one to control the origin from where
+ * the cursor should move from, this allows one to reset the cursor
+ * position at any time, to move the cursor from the current position,
+ * or to reuse the previously stored cursor position. The cursor implementation
+ * stores a dual cursor state for the express purpose of allowing one
+ * to query the cursor using the %E_BOOK_CURSOR_ORIGIN_PREVIOUS origin,
+ * this is provided so that one can easily repeat the last cursor query
+ * at any time the addressbook changes and results need to be reloaded.
+ *
+ * If @cursor reaches the beginning or end of the query results, then the
+ * returned results might not contain the requested amount of contacts, or might
+ * return no results at all if the cursor currently points to the last contact.
+ * This is not considered an error condition, but will result in a cursor position
+ * of %0.
+ *
+ * A side effect of moving the cursor is that the #EBookClientCursor:position
+ * property will be updated.
+ *
+ * If this method is called from the same thread context in which
+ * the cursor was created, then the updates to the #EBookClientCursor:position
+ * property are guaranteed to be delivered synchronously upon successful completion
+ * of moving the cursor. Otherwise, notifications will be delivered asynchronously
+ * in the cursor's original thread context.
+ *
+ * If this method completes with an %E_CLIENT_ERROR_OUT_OF_SYNC error, it is an
+ * indication that the addressbook has been modified and it would be unsafe to
+ * move the cursor at this time. Any %E_CLIENT_ERROR_OUT_OF_SYNC error is guaranteed
+ * to be followed by an #EBookClientCursor::refresh signal at which point any content
+ * should be reloaded.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_client_cursor_move_by_sync (EBookClientCursor *cursor,
+ EBookCursorOrigin origin,
+ gint count,
+ GSList **out_contacts,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint new_total = 0, new_position = 0;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), FALSE);
+ g_return_val_if_fail (count != 0 || origin == E_BOOK_CURSOR_ORIGIN_RESET, FALSE);
+
+ success = move_by_sync_internal (cursor, cursor->priv->revision, origin, count, out_contacts,
+ &new_total, &new_position, cancellable, error);
+
+ /* If we are in the thread where the cursor was created,
+ * then synchronize the new total & position right away
+ */
+ if (success && book_client_cursor_context_is_current (cursor)) {
+ g_object_freeze_notify (G_OBJECT (cursor));
+ book_client_cursor_set_total (cursor, new_total);
+ book_client_cursor_set_position (cursor, new_position);
+ g_object_thaw_notify (G_OBJECT (cursor));
+ }
+
+ return success;
+}
+
+/**
+ * e_book_client_cursor_set_alphabetic_index:
+ * @cursor: an #EBookClientCursor
+ * @index: the alphabetic index
+ * @cancellable: (allow-none): a #GCancellable to optionally cancel this operation while in progress
+ * @callback: callback to call when a result is ready
+ * @user_data: user data for the @callback
+ *
+ * Sets the current cursor position to point to an <link linkend="cursor-alphabet">Alphabetic Index</link>.
+ *
+ * See: e_book_client_cursor_set_alphabetic_index_sync().
+ *
+ * This asynchronous call is completed with a call to
+ * e_book_client_cursor_set_alphabetic_index_finish() from the specified @callback.
+ *
+ * Since: 3.12
+ */
+void
+e_book_client_cursor_set_alphabetic_index (EBookClientCursor *cursor,
+ gint index,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AlphabetIndexContext *context;
+
+ g_return_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor));
+ g_return_if_fail (index >= 0 && index < cursor->priv->n_labels);
+ g_return_if_fail (callback != NULL);
+
+ context = alphabet_index_context_new (index, cursor->priv->locale);
+ simple = g_simple_async_result_new (G_OBJECT (cursor),
+ callback, user_data,
+ e_book_client_cursor_set_alphabetic_index);
+
+ g_simple_async_result_set_check_cancellable (simple, cancellable);
+ g_simple_async_result_set_op_res_gpointer (simple, context,
+ (GDestroyNotify) alphabet_index_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, alphabet_index_thread,
+ G_PRIORITY_DEFAULT, cancellable);
+
+ g_object_unref (simple);
+}
+
+/**
+ * e_book_client_cursor_set_alphabetic_index_finish:
+ * @cursor: an #EBookClientCursor
+ * @result: a #GAsyncResult
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Completes an asynchronous call initiated by e_book_client_cursor_set_alphabetic_index().
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
+ */
+gboolean
+e_book_client_cursor_set_alphabetic_index_finish (EBookClientCursor *cursor,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AlphabetIndexContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (cursor),
+ e_book_client_cursor_set_alphabetic_index), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ /* If we are in the thread where the cursor was created,
+ * then synchronize the new total & position right away
+ */
+ if (book_client_cursor_context_is_current (cursor)) {
+ g_object_freeze_notify (G_OBJECT (cursor));
+ book_client_cursor_set_total (cursor, context->new_total);
+ book_client_cursor_set_position (cursor, context->new_position);
+ g_object_thaw_notify (G_OBJECT (cursor));
+ }
+
+ return TRUE;
+}
+
+/**
+ * e_book_client_cursor_set_alphabetic_index_sync:
+ * @cursor: an #EBookClientCursor
+ * @index: the alphabetic index
+ * @cancellable: (allow-none): a #GCancellable to optionally cancel this operation while in progress
+ * @error: (out) (allow-none): return location for a #GError, or %NULL
+ *
+ * Sets the current cursor position to point to an <link linkend="cursor-alphabet">Alphabetic Index</link>.
+ *
+ * After setting the target alphabetic index, for example the
+ * index for letter 'E', then further calls to e_book_client_cursor_move_by()
+ * will return results starting with the letter 'E' (or results starting
+ * with the last result in 'D' when navigating through cursor results
+ * in reverse).
+ *
+ * The passed index must be a valid index into the alphabet parameters
+ * returned by e_book_client_cursor_get_alphabet().
+ *
+ * If this method is called from the same thread context in which
+ * the cursor was created, then the updates to the #EBookClientCursor:position
+ * property are guaranteed to be delivered synchronously upon successful completion
+ * of moving the cursor. Otherwise, notifications will be delivered asynchronously
+ * in the cursor's original thread context.
+ *
+ * If this method completes with an %E_CLIENT_ERROR_OUT_OF_SYNC error, it is an
+ * indication that the addressbook has been set into a new locale and it would be
+ * unsafe to set the alphabetic index at this time. If you receive an out of sync
+ * error from this method, then you should wait until a #EBookClientCursor:alphabet
+ * property change notification is delivered and then proceed to load the new
+ * alphabet before trying to set any alphabetic index.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_client_cursor_set_alphabetic_index_sync (EBookClientCursor *cursor,
+ gint index,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint new_total = 0, new_position = 0;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), FALSE);
+ g_return_val_if_fail (index >= 0 && index < cursor->priv->n_labels, FALSE);
+
+ success = set_alphabetic_index_sync_internal (cursor, index, cursor->priv->locale,
+ &new_total, &new_position, cancellable, error);
+
+ /* If we are in the thread where the cursor was created,
+ * then synchronize the new total & position right away
+ */
+ if (success && book_client_cursor_context_is_current (cursor)) {
+ g_object_freeze_notify (G_OBJECT (cursor));
+ book_client_cursor_set_total (cursor, new_total);
+ book_client_cursor_set_position (cursor, new_position);
+ g_object_thaw_notify (G_OBJECT (cursor));
+ }
+
+ return success;
+}
+
+/**
+ * e_book_client_cursor_get_contact_alphabetic_index:
+ * @cursor: an #EBookClientCursor
+ * @contact: the #EContact to check
+ *
+ * Checks which alphabetic index @contact would be sorted
+ * into according to @cursor.
+ *
+ * The returned index will be a valid position in the array
+ * of labels returned by e_book_client_cursor_get_alphabet().
+ *
+ * Returns: The alphabetic index of @contact in @cursor.
+ *
+ * Since: 3.12
+ */
+gint
+e_book_client_cursor_get_contact_alphabetic_index (EBookClientCursor *cursor,
+ EContact *contact)
+{
+ EBookClientCursorPrivate *priv;
+ EContactField field;
+ const gchar *value;
+ gint index = 0;
+
+ g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), 0);
+ g_return_val_if_fail (E_IS_CONTACT (contact), 0);
+
+ priv = cursor->priv;
+
+ if (priv->collator && priv->sort_fields) {
+
+ /* Find the alphabetic index according to the primary
+ * cursor sort key
+ */
+ field = e_contact_field_id (priv->sort_fields[0]);
+ value = e_contact_get_const (contact, field);
+ index = e_collator_get_index (priv->collator, value);
+ }
+
+ return index;
+}
diff --git a/addressbook/libebook/e-book-client-cursor.h b/addressbook/libebook/e-book-client-cursor.h
new file mode 100644
index 0000000..7e2bdff
--- /dev/null
+++ b/addressbook/libebook/e-book-client-cursor.h
@@ -0,0 +1,144 @@
+/* -*- 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 (__LIBEBOOK_H_INSIDE__) && !defined (LIBEBOOK_COMPILATION)
+#error "Only <libebook/libebook.h> should be included directly."
+#endif
+
+#ifndef E_BOOK_CLIENT_CURSOR_H
+#define E_BOOK_CLIENT_CURSOR_H
+
+#include <glib-object.h>
+#include <libebook-contacts/libebook-contacts.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_CLIENT_CURSOR \
+ (e_book_client_cursor_get_type ())
+#define E_BOOK_CLIENT_CURSOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_BOOK_CLIENT_CURSOR, EBookClientCursor))
+#define E_BOOK_CLIENT_CURSOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_BOOK_CLIENT_CURSOR, EBookClientCursorClass))
+#define E_IS_BOOK_CLIENT_CURSOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_BOOK_CLIENT_CURSOR))
+#define E_IS_BOOK_CLIENT_CURSOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_BOOK_CLIENT_CURSOR))
+#define E_BOOK_CLIENT_CURSOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_BOOK_CLIENT_CURSOR, EBookClientCursorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EBookClientCursor EBookClientCursor;
+typedef struct _EBookClientCursorClass EBookClientCursorClass;
+typedef struct _EBookClientCursorPrivate EBookClientCursorPrivate;
+
+struct _EBookClient;
+
+/**
+ * EBookClientCursor:
+ *
+ * Contains only private data.
+ *
+ * Since: 3.12
+ */
+struct _EBookClientCursor {
+ /*< private >*/
+ GObject parent;
+ EBookClientCursorPrivate *priv;
+};
+
+/**
+ * EBookClientCursorClass:
+ * @refresh: The class handler for the #EBookClientCursor::refresh signal
+ *
+ * The cursor class structure.
+ *
+ * Since: 3.12
+ */
+struct _EBookClientCursorClass {
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+
+ /* Signals */
+ void (* refresh) (EBookClientCursor *cursor);
+};
+
+GType e_book_client_cursor_get_type (void) G_GNUC_CONST;
+struct _EBookClient *e_book_client_cursor_ref_client (EBookClientCursor *cursor);
+const gchar * const *e_book_client_cursor_get_alphabet (EBookClientCursor *cursor,
+ gint *n_labels,
+ gint *underflow,
+ gint *inflow,
+ gint *overflow);
+gint e_book_client_cursor_get_total (EBookClientCursor *cursor);
+gint e_book_client_cursor_get_position (EBookClientCursor *cursor);
+void e_book_client_cursor_set_sexp (EBookClientCursor *cursor,
+ const gchar *sexp,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_book_client_cursor_set_sexp_finish (EBookClientCursor *cursor,
+ GAsyncResult *result,
+ GError **error);
+gboolean e_book_client_cursor_set_sexp_sync (EBookClientCursor *cursor,
+ const gchar *sexp,
+ GCancellable *cancellable,
+ GError **error);
+void e_book_client_cursor_move_by (EBookClientCursor *cursor,
+ EBookCursorOrigin origin,
+ gint count,
+ gboolean fetch_results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_book_client_cursor_move_by_finish (EBookClientCursor *cursor,
+ GAsyncResult *result,
+ GSList **out_contacts,
+ GError **error);
+gboolean e_book_client_cursor_move_by_sync (EBookClientCursor *cursor,
+ EBookCursorOrigin origin,
+ gint count,
+ GSList **out_contacts,
+ GCancellable *cancellable,
+ GError **error);
+void e_book_client_cursor_set_alphabetic_index (EBookClientCursor *cursor,
+ gint index,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_book_client_cursor_set_alphabetic_index_finish (EBookClientCursor *cursor,
+ GAsyncResult *result,
+ GError **error);
+gboolean e_book_client_cursor_set_alphabetic_index_sync (EBookClientCursor *cursor,
+ gint index,
+ GCancellable *cancellable,
+ GError **error);
+gint e_book_client_cursor_get_contact_alphabetic_index(EBookClientCursor *cursor,
+ EContact *contact);
+
+G_END_DECLS
+
+#endif /* E_BOOK_CLIENT_CURSOR_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]