1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2013 Intel Corporation
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Tristan Van Berkom <tristanvb@openismus.com>
18  */
19 
20 /**
21  * SECTION: e-book-client-cursor
22  * @include: libebook/libebook.h
23  * @short_description: An addressbook cursor
24  *
25  * The #EBookClientCursor is an iteration based interface for browsing
26  * a sorted list of contacts in the addressbook.
27  *
28  * Aside from the documentation found here, a fully functional example
29  * program <link linkend="eds-cursor-example">can be found here</link>.
30  *
31  * <refsect2 id="cursor-sort-keys">
32  * <title>Sort Keys</title>
33  * <para>
34  * When creating the cursor initially with e_book_client_get_cursor(),
35  * a list of #EContactFields must be provided to define the sort order for
36  * the newly created cursor. Only contact fields of type %G_TYPE_STRING
37  * can potentially be used to define a cursor's sort order.
38  * </para>
39  * <para>
40  * Backends which support cursors may refuse to create a cursor based
41  * on the fields specified as sort keys,  if this happens then an
42  * %E_CLIENT_ERROR_INVALID_QUERY error will be reported by
43  * e_book_client_get_cursor().
44  * </para>
45  * <para>
46  * The default SQLite backend provided with Evolution Data Server
47  * only supports #EContactFields that are specified as summary information
48  * to be used as sort keys. Whether a contact field is configured to
49  * be part of the summary for your addressbook can be verified with
50  * e_source_backend_summary_setup_get_summary_fields().
51  * </para>
52  * <para>
53  * The order of sort keys given to e_book_client_get_cursor() defines
54  * which sort key will be the primary sort key and which keys will
55  * serve as tie breakers where the previous sort keys are exact matches.
56  * In the following example we create a typical cursor sorted with
57  * %E_CONTACT_FAMILY_NAME as the primary sort key and %E_CONTACT_GIVEN_NAME
58  * as a tie breaker.
59  * |[
60  *     EBookClientCursor *cursor = NULL;
61  *     EContactField sort_fields[] = { E_CONTACT_FAMILY_NAME, E_CONTACT_GIVEN_NAME };
62  *     EBookCursorSortType sort_types[] = { E_BOOK_CURSOR_SORT_ASCENDING, E_BOOK_CURSOR_SORT_ASCENDING };
63  *     GError *error = NULL;
64  *
65  *     if (e_book_client_get_cursor_sync (book_client, // EBookClient
66  *                                        NULL,        // Search Expression
67  *                                        sort_fields, // Sort Keys
68  *                                        sort_types,  // Ascending / Descending
69  *                                        2,           // Number of keys
70  *                                        &cursor,     // Return location for cursor
71  *                                        NULL,        // GCancellable
72  *                                        &error)) {
73  *             // Now we have a cursor ...
74  *     }
75  * ]|
76  * </para>
77  * <para>
78  * Sort order is immutable, if you need to browse content in a different
79  * order, then you need to create a separate cursor.
80  * </para>
81  * </refsect2>
82  *
83  * <refsect2 id="cursor-state">
84  * <title>Understanding cursor state</title>
85  * <para>
86  * At any given time in a cursor's life cycle, a cursor's internal state will refer to a
87  * relative position in a sorted list.
88  * </para>
89  * <para>
90  * There are three basic varieties of cursor states:
91  * <itemizedlist>
92  *   <listitem>
93  *     <para>
94  *       Virtual states referring to the beginnng and end of the list.
95  *     </para>
96  *     <para>
97  *       The beginning state is positioned before any contact in the addressbook.
98  *       When the cursor is in this state, a call to e_book_client_cursor_step()
99  *       will always start reporting contacts from the beginning of the list.
100  *       Similarly when in the end state, stepping in reverse will start
101  *       reporting contacts from the end of the list.
102  *     </para>
103  *     <para>
104  *       The beginning and end states can be reached by stepping off the
105  *       end of the list, or by specifying the %E_BOOK_CURSOR_ORIGIN_BEGIN or
106  *       %E_BOOK_CURSOR_ORIGIN_END origins to e_book_client_cursor_step(). The
107  *       cursor is also initially positioned before the contact list.
108  *     </para>
109  *   </listitem>
110  *   <listitem>
111  *     <para>
112  *       States referring to a specific contact.
113  *     </para>
114  *     <para>
115  *       A state which refers to a specific contact in the list of
116  *       contacts associated with a given cursor. At the end of any
117  *       successful call to e_book_client_cursor_step() with
118  *       the %E_BOOK_CURSOR_STEP_MOVE flag specified; the cursor
119  *       state is updated with the value of the last result.
120  *     </para>
121  *   </listitem>
122  *   <listitem>
123  *     <para>
124  *       States referring to an alphabetic position.
125  *     </para>
126  *     <para>
127  *       When a state refers to an
128  *       <link linkend="cursor-alphabet">Alphabetic Index</link>,
129  *       it refers to a position which is in between contacts.
130  *       For instance the alphabetic position "E" refers to a
131  *       position after contacts starting with "D" and before contacts
132  *       starting with "E".
133  *     </para>
134  *   </listitem>
135  * </itemizedlist>
136  * </para>
137  * </refsect2>
138  *
139  * <refsect2 id="cursor-pos-total">
140  * <title>Cursor position and total</title>
141  * <para>
142  * The #EBookClientCursor:position and #EBookClientCursor:total attributes
143  * provide feedback about a cursor's position in relation to the addressbook
144  * provided the cursor's sort order.
145  * </para>
146  * <para>
147  * The total reflects that total amount of contacts in the addressbook given
148  * the cursor's <link linkend="cursor-search">Search Expression</link>. The position
149  * is defined as the number of contacts leading up to the cursor position inclusive
150  * of the cursor position.
151  * </para>
152  * <para>
153  * To help illustrate how the total and position attributes relate to a sorted list
154  * of contacts, we've provided the diagram below.
155  * </para>
156  * <inlinegraphic fileref="cursor-positions.png" format="PNG" align="center"></inlinegraphic>
157  * <para>
158  * The above diagram shows two representations of a sorted contact list, using
159  * %E_CONTACT_FAMILY_NAME as the primary sort key and %E_CONTACT_GIVEN_NAME as
160  * a secondary sort key. On either side we can see the symbolic positions
161  * %E_BOOK_CURSOR_ORIGIN_BEGIN and %E_BOOK_CURSOR_ORIGIN_END.
162  * </para>
163  * <para>
164  * For a given cursor state, the position value will be equal to the total
165  * number of contacts leading up to the current cursor state inclusive of the
166  * cursor state itself. In the left hand side of the above diagram the cursor
167  * points to the fourth contact and the cursor position is also 4. An exception
168  * to this is when the cursor state refers to the %E_BOOK_CURSOR_ORIGIN_END position.
169  * When the cursor state refers to the end of list, the position property
170  * will have a value of (total + 1).
171  * </para>
172  * <para>
173  * Another thing the above diagram illustrates is the effect that an
174  * asynchronous addressbook modification has on the cursor. The right
175  * hand side of the diagram portrays the result of deleting "Mickey Mouse"
176  * from the original list on the left.
177  * </para>
178  * <para>
179  * The cursor state at this time still litteraly refers to "Mickey Mouse",
180  * however the number of contacts leading up to "Mickey Mouse" is now 3
181  * instead of 4. As one might have guessed, any addition of a contact
182  * which is considered to be less than or equal to "Mickey Mouse" at this point,
183  * will cause the position to increase again. In this way, asynchronous
184  * addressbook modification might cause the cursor's position and total
185  * values to change, but never effect the cursor's state and it's
186  * actual position in relation to other contacts in the sorted list.
187  * </para>
188  * <para>
189  * The position and total can be useful for various tasks
190  * such as determining "Forward" / "Back" button sensitivity
191  * in a browser interface, or displaying some indication
192  * of the view window's position in the full contact list.
193  * |[
194  *     gint position, total;
195  *     gdouble percent;
196  *
197  *     // Fetch the position & total
198  *     position = e_book_client_cursor_get_position (cursor);
199  *     total    = e_book_client_cursor_get_total (cursor);
200  *
201  *     // The position can be total + 1 if we're at the end of the
202  *     // list, let's ignore that for this calculation.
203  *     position = CLAMP (position, 0, total);
204  *
205  *     // Calculate the percentage.
206  *     percent = position * 1.0F / (total - N_DISPLAY_CONTACTS);
207  *
208  *     // Let the user know the percentage of contacts in the list
209  *     // which are positioned before the view position (the
210  *     // percentage of the addressbook which the user has seen so far).
211  *     update_percentage_of_list_browsed (user_interface, percent);
212  * ]|
213  * </para>
214  * <para>
215  * These total and position values are guaranteed to always be coherent, they are
216  * updated synchronously upon successful completion of any of the asynchronous
217  * cursor API calls, and also updated asynchronously whenever the addressbook
218  * changes and an #EBookClientCursor::refresh signal is delivered.
219  * </para>
220  * <para>
221  * Change notifications are guaranteed to only ever be delivered in the #GMainContext which
222  * was the thread default main context at cursor creation time.
223  * </para>
224  * </refsect2>
225  *
226  * <refsect2 id="cursor-search">
227  * <title>Search Expressions</title>
228  * <para>
229  * The list of contacts associated to a given cursor can be filtered
230  * with a search expression generated by e_book_query_to_string(). Since
231  * this effects how the data will be traversed in the backend, seach
232  * expressions come with the same limitation as sort keys. Backends
233  * will report %E_CLIENT_ERROR_INVALID_QUERY for contact fields which
234  * are not supported. For the default local addressbook, any fields
235  * which are configured in the summary can be used to filter cursor
236  * results.
237  * </para>
238  * <para>
239  * Changing the search expression can be done at any time using
240  * e_book_client_cursor_set_sexp().
241  * The cursor <link linkend="cursor-pos-total">position and total</link>
242  * values will be updated synchronously after successfully setting the
243  * search expression at which time you might refresh the current
244  * view, displaying the new filtered list of contacts at the same
245  * cursor position.
246  * </para>
247  * <inlinegraphic fileref="cursor-positions-filtered.png" format="PNG" align="center"></inlinegraphic>
248  * </refsect2>
249  *
250  * <refsect2 id="cursor-iteration">
251  * <title>Iteration with the cursor API</title>
252  * <para>
253  * The cursor API allows you to iterate through a sorted list of contacts
254  * without keeping a potentially large collection of contacts loaded
255  * in memory.
256  * </para>
257  * <para>
258  * Iterating through the contact list is done with e_book_client_cursor_step(), this
259  * function allows one to move the cursor and fetch the results following the current
260  * cursor position.
261  * |[
262  *     GError *error = NULL;
263  *     GSList *results = NULL;
264  *     gint n_results;
265  *
266  *     // Move the cursor forward by 10 contacts and fetch the results.
267  *     n_results = e_book_client_cursor_step_sync (cursor,
268  *                                                 E_BOOK_CURSOR_STEP_MOVE |
269  *                                                 E_BOOK_CURSOR_STEP_FETCH,
270  *                                                 E_BOOK_CURSOR_ORIGIN_CURRENT,
271  *                                                 10,
272  *                                                 &results,
273  *                                                 NULL,
274  *                                                 &error);
275  *
276  *     if (n_results < 0)
277  *       {
278  *         if (g_error_matches (error,
279  *                              E_CLIENT_ERROR,
280  *                              E_CLIENT_ERROR_OUT_OF_SYNC))
281  *           {
282  *             // The addressbook has been modified at the same time as
283  *             // we asked to step. The appropriate thing to do is wait
284  *             // for the "refresh" signal before trying again.
285  *             handle_out_of_sync_condition (cursor);
286  *           }
287  *         else if (g_error_matches (error,
288  *                                   E_CLIENT_ERROR,
289  *                                   E_CLIENT_ERROR_QUERY_REFUSED))
290  *           {
291  *             // We asked for 10 contacts but were already positioned
292  *             // at the end of the list (or we asked for -10 contacts
293  *             // and were positioned at the beginning).
294  *             handle_end_of_list_condition (cursor);
295  *           }
296  *         else
297  *           {
298  *             // Some error actually occurred
299  *             handle_error_condition (cursor, error);
300  *           }
301  *
302  *         g_clear_error (&error);
303  *       }
304  *     else if (n_results < 10)
305  *       {
306  *         // Cursor did not traverse as many contacts as requested.
307  *         //
308  *         // This is not an error but rather an indication that
309  *         // the end of the list was reached. The next attempt to
310  *         // move the cursor in the same direction will result in
311  *         // an E_CLIENT_ERROR_QUERY_REFUSED error.
312  *       }
313  * ]|
314  * In the above example we chose %E_BOOK_CURSOR_ORIGIN_CURRENT as our #EBookCursorOrigin so the above
315  * call will traverse 10 contacts following the cursor's current position. One can also choose the
316  * %E_BOOK_CURSOR_ORIGIN_BEGIN or %E_BOOK_CURSOR_ORIGIN_END origin to start at the beginning or end
317  * of the results at any time.
318  * </para>
319  * <para>
320  * We also specified both of the flags %E_BOOK_CURSOR_STEP_MOVE and %E_BOOK_CURSOR_STEP_FETCH,
321  * this means we want to receive results from the addressbook and we also want to modify
322  * the current cursor state (move the cursor), these operations can however be done
323  * completely independantly of eachother, which is often what is desired for a contact
324  * browsing user interface. It is however recommended to move and fetch
325  * results in a single pass wherever that makes sense in your application.
326  * </para>
327  * <para>
328  * Because the addressbook might be modified at any time by another application,
329  * it's important to handle the %E_CLIENT_ERROR_OUT_OF_SYNC error. This error will occur
330  * at any time that the cursor detects an addressbook change while trying to step.
331  * Whenever an out of sync condition arises, the cursor should be left alone until the
332  * next #EBookClientCursor::refresh signal. The #EBookClientCursor::refresh signal is triggered
333  * any time that the addressbook changes and is the right place to refresh the currently
334  * loaded content, it is also guaranteed to be triggered after any %E_CLIENT_ERROR_OUT_OF_SYNC
335  * error.
336  * </para>
337  * <para>
338  * The diagram below illustrates some scenarios of how the cursor state is updated
339  * in calls to e_book_client_cursor_step().
340  * </para>
341  * <inlinegraphic fileref="cursor-positions-step.png" format="PNG" align="center"></inlinegraphic>
342  * </refsect2>
343  *
344  * <refsect2 id="cursor-alphabet">
345  * <title>Alphabetic Indexes</title>
346  * <para>
347  * The cursor permits navigation of the sorted contact list in terms of alphabetic
348  * positions in the list, allowing one to jump from one letter to the next in
349  * the active alphabet.
350  * </para>
351  * <para>
352  * The active alphabet itself is represented as an array of UTF-8 strings which are
353  * suitable to display a given glyph or alphabetic position in the user's active alphabet.
354  * This array of alphabetic position labels is exposed via the #EBookClientCursor:alphabet
355  * property and can always be fetched with e_book_client_cursor_get_alphabet().
356  * </para>
357  * <para>
358  * As shown below, each index in the active alphabet array is a potential cursor state
359  * which refers to a position before, after or in between contacts in the sorted contact list.
360  * Most of the positions in the active alphabet array refer to alphabetic glyhps or positions,
361  * however the 'underflow', 'inflow' and 'overflow' positions represent positions for
362  * contacts which sort outside the bounderies of the active alphabet.
363  * </para>
364  * <inlinegraphic fileref="cursor-alphabetic-indexes.png" format="PNG" align="center"></inlinegraphic>
365  * <para>
366  * The active alphabet is dynamically resolved from the system locale at startup time and
367  * whenever a system locale change notification is delivered to Evolution Data Server. If
368  * ever the system locale changes at runtime then a change notification will be delivered
369  * for the #EBookClientCursor:alphabet property, this is a good time to refresh the list
370  * of alphabetic positions available in a user interface.
371  * </para>
372  * <para>
373  * Using the active alphabet, one can build a user interface which allows the user
374  * to navigate to a specific letter in the results. To set the cursor's position
375  * directly before any results starting with a specific letter, one can use
376  * e_book_client_cursor_set_alphabetic_index().
377  * |[
378  *     GError *error = NULL;
379  *     gint index = currently_selected_index (user_interface);
380  *
381  *     // At this point 'index' must be a numeric value corresponding
382  *     // to one of the positions in the array returned by
383  *     // e_book_client_cursor_get_alphabet().
384  *     if (!e_book_client_cursor_set_alphabetic_index_sync (cursor,
385  *                                                          index,
386  *                                                          NULL,
387  *                                                          &error))
388  *       {
389  *         if (g_error_matches (error,
390  *                              E_CLIENT_ERROR,
391  *                              E_CLIENT_ERROR_OUT_OF_SYNC))
392  *           {
393  *             // The system locale has changed at the same time
394  *             // as we were setting an alphabetic cursor position.
395  *             handle_out_of_sync_condition (cursor);
396  *           }
397  *         else
398  *           {
399  *             // Some error actually occurred
400  *             handle_error_condition (cursor, error);
401  *           }
402  *
403  *         g_clear_error (&error);
404  *       }
405  * ]|
406  * After setting the alphabetic index successfully, you can go ahead
407  * and use e_book_client_cursor_step() to load some contacts at the
408  * beginning of the given letter.
409  * </para>
410  * <para>
411  * This API can result in an %E_CLIENT_ERROR_OUT_OF_SYNC error. This error will
412  * occur at any time that the cursor tries to set the alphabetic index whilst the
413  * addressbook is changing its active locale setting. In the case of a dynamic locale
414  * change, a change notification will be delivered for the #EBookClientCursor:alphabet
415  * property at which point the application should reload anything related to the
416  * alphabet (an #EBookClientCursor::refresh signal will also be delivered at this point).
417  * </para>
418  * <para>
419  * While moving through the cursor results using e_book_client_cursor_step(),
420  * it can be useful to know which alphabetic position a given contact sorts
421  * under. This can be useful if your user interface displays an alphabetic
422  * label indicating where the first contact in your view is positioned in
423  * the alphabet.
424  * </para>
425  * <para>
426  * One can determine the appropriate index for a given #EContact by calling
427  * e_book_client_cursor_get_contact_alphabetic_index() after refreshing
428  * the currently displayed contacts in a view.
429  * |[
430  *     EContact *contact;
431  *     const gchar * const *alphabet;
432  *     gint index;
433  *
434  *     // Fetch the first displayed EContact in the view
435  *     contact = first_contact_in_the_list (user_interface);
436  *
437  *     // Calculate the position in the alphabet for this contact
438  *     index = e_book_client_cursor_get_contact_alphabetic_index (cursor, contact);
439  *
440  *     // Fetch the alphabet labels
441  *     alphabet = e_book_client_cursor_get_alphabet (cursor, &n_labels,
442  *                                                   NULL, NULL, NULL);
443  *
444  *     // Update label in user interface
445  *     set_alphabetic_position_feedback_text (user_interface, alphabet[index]);
446  * ]|
447  * </para>
448  * </refsect2>
449  *
450  */
451 #include "evolution-data-server-config.h"
452 
453 #include <glib/gi18n-lib.h>
454 
455 #include <libedataserver/libedataserver.h>
456 #include <libedata-book/libedata-book.h>
457 
458 /* Private D-Bus class. */
459 #include <e-dbus-address-book-cursor.h>
460 
461 #include "e-book-client.h"
462 #include "e-book-client-cursor.h"
463 
464 /* Forward declarations */
465 typedef struct _SetSexpContext        SetSexpContext;
466 typedef struct _StepContext           StepContext;
467 typedef struct _AlphabetIndexContext  AlphabetIndexContext;
468 typedef enum   _NotificationType      NotificationType;
469 typedef struct _Notification          Notification;
470 
471 /* GObjectClass */
472 static void          book_client_cursor_dispose            (GObject                *object);
473 static void          book_client_cursor_finalize           (GObject                *object);
474 static void          book_client_cursor_set_property       (GObject                *object,
475 							    guint                   property_id,
476 							    const GValue           *value,
477 							    GParamSpec             *pspec);
478 static void          book_client_cursor_get_property       (GObject                *object,
479 							    guint                   property_id,
480 							    GValue                 *value,
481 							    GParamSpec             *pspec);
482 
483 /* GInitable */
484 static void	     e_book_client_cursor_initable_init    (GInitableIface         *iface);
485 static gboolean      book_client_cursor_initable_init      (GInitable              *initable,
486 							    GCancellable           *cancellable,
487 							    GError                **error);
488 
489 /* Private mutators */
490 static void          book_client_cursor_set_client         (EBookClientCursor      *cursor,
491 							    EBookClient            *client);
492 static void          book_client_cursor_set_context        (EBookClientCursor      *cursor,
493 							    GMainContext           *context);
494 static GMainContext *book_client_cursor_ref_context        (EBookClientCursor      *cursor);
495 static gboolean      book_client_cursor_context_is_current (EBookClientCursor      *cursor);
496 static void          book_client_cursor_set_proxy          (EBookClientCursor      *cursor,
497 							    EDBusAddressBookCursor *proxy);
498 static void          book_client_cursor_set_connection     (EBookClientCursor      *cursor,
499 							    GDBusConnection        *connection);
500 static void          book_client_cursor_set_direct_cursor  (EBookClientCursor      *cursor,
501 							    EDataBookCursor        *direct_cursor);
502 static void          book_client_cursor_set_object_path    (EBookClientCursor      *cursor,
503 							    const gchar            *object_path);
504 static void          book_client_cursor_set_locale         (EBookClientCursor      *cursor,
505 							    const gchar            *locale);
506 static void          book_client_cursor_set_revision       (EBookClientCursor      *cursor,
507 							    const gchar            *revision);
508 static void          book_client_cursor_set_total          (EBookClientCursor      *cursor,
509 							    gint                    total);
510 static void          book_client_cursor_set_position       (EBookClientCursor      *cursor,
511 							    gint                    position);
512 
513 /* Notifications from other threads */
514 static void          notification_new_string               (EBookClientCursor      *cursor,
515 							    NotificationType        type,
516 							    const gchar            *value);
517 static void          notification_new_int                  (EBookClientCursor      *cursor,
518 							    NotificationType        type,
519 							    gint                    value);
520 static void          notification_free                     (Notification           *notification);
521 static void          notification_queue                    (EBookClientCursor      *cursor,
522 							    Notification           *notification);
523 static gboolean      notification_dispatch                 (GWeakRef               *weak_ref);
524 
525 /* Callbacks from EBookClient */
526 static void	     client_revision_changed_cb            (EClient                *client,
527 							    const gchar            *prop_name,
528 							    const gchar            *prop_value,
529 							    GWeakRef               *weak_ref);
530 static void	     client_locale_changed_cb              (EBookClient            *book_client,
531 							    GParamSpec             *pspec,
532 							    GWeakRef               *weak_ref);
533 
534 /* Callbacks from EDBusAddressBookCursor */
535 static void	     proxy_total_changed_cb                (EDBusAddressBookCursor *proxy,
536 							    GParamSpec             *pspec,
537 							    GWeakRef               *weak_ref);
538 static void	     proxy_position_changed_cb             (EDBusAddressBookCursor *proxy,
539 							    GParamSpec             *pspec,
540 							    GWeakRef               *weak_ref);
541 
542 /* Callbacks from EDataBookCursor */
543 static void	     dra_total_changed_cb                  (EDataBookCursor        *direct_cursor,
544 							    GParamSpec             *pspec,
545 							    EBookClientCursor      *cursor);
546 static void	     dra_position_changed_cb               (EDataBookCursor        *direct_cursor,
547 							    GParamSpec             *pspec,
548 							    EBookClientCursor      *cursor);
549 
550 /* Threaded method call contexts */
551 static SetSexpContext       *set_sexp_context_new          (const gchar            *sexp);
552 static void                  set_sexp_context_free         (SetSexpContext         *context);
553 static void                  set_sexp_thread               (GSimpleAsyncResult     *simple,
554 							    GObject                *source_object,
555 							    GCancellable           *cancellable);
556 static StepContext          *step_context_new              (const gchar            *revision,
557 							    EBookCursorStepFlags    flags,
558 							    EBookCursorOrigin       origin,
559 							    gint                    count);
560 static void                  step_context_free             (StepContext            *context);
561 static void                  step_thread                   (GSimpleAsyncResult     *simple,
562 							    GObject                *source_object,
563 							    GCancellable           *cancellable);
564 static AlphabetIndexContext *alphabet_index_context_new    (gint                    index,
565 							    const gchar            *locale);
566 static void                  alphabet_index_context_free   (AlphabetIndexContext   *context);
567 static void                  alphabet_index_thread         (GSimpleAsyncResult     *simple,
568 							    GObject                *source_object,
569 							    GCancellable           *cancellable);
570 
571 enum _NotificationType {
572 	REVISION_CHANGED = 0,
573 	LOCALE_CHANGED,
574 	TOTAL_CHANGED,
575 	POSITION_CHANGED,
576 	N_NOTIFICATION_TYPES
577 };
578 
579 struct _Notification {
580 	GWeakRef cursor;
581 	NotificationType type;
582 	GValue value;
583 };
584 
585 struct _EBookClientCursorPrivate {
586 	/* Strong reference to the EBookClient and
587 	 * to the GMainContext in which notifications
588 	 * should be delivered to the EBookClientCursor user */
589 	EBookClient  *client;
590 	GMainContext *main_context;
591 	GMutex        main_context_lock;
592 
593 	/* Connection with the addressbook cursor over D-Bus */
594 	EDBusAddressBookCursor *dbus_proxy;
595 	GDBusConnection        *connection;
596 	gchar                  *object_path;
597 
598 	/* Direct Read Access to the addressbook cursor */
599 	EDataBookCursor *direct_cursor;
600 
601 	/* A local copy of the #EContactFields we are
602 	 * sorting by (field names)
603 	 */
604 	gchar **sort_fields;
605 
606 	/* Keep a handle on the current locale according
607 	 * to the EBookClient, this is also how we
608 	 * derive the active alphabet
609 	 */
610 	gchar *locale;
611 
612 	/* Keep a handle on the revision, we need to
613 	 * hold on to the currently known revision for
614 	 * DRA mode cursors. Also we trigger the
615 	 * refresh signal in normal mode whenever the
616 	 * revision changes
617 	 */
618 	gchar *revision;
619 
620 	/* A handy collator which we change with locale changes.
621 	 */
622 	ECollator *collator;
623 	gint       n_labels; /* The amount of labels in the active alphabet */
624 
625 	/* Client side positional values */
626 	gint    position;
627 	gint    total;
628 
629 	/* Make sure all notifications are delivered in a single idle callback */
630 	GSource      *notification_source;
631 	Notification *notification[N_NOTIFICATION_TYPES];
632 	GMutex        notifications_lock;
633 
634 	/* Signal connection ids */
635 	gulong revision_changed_id;
636 	gulong locale_changed_id;
637 	gulong proxy_total_changed_id;
638 	gulong proxy_position_changed_id;
639 	gulong dra_total_changed_id;
640 	gulong dra_position_changed_id;
641 };
642 
643 enum {
644 	PROP_0,
645 	PROP_SORT_FIELDS,
646 	PROP_CLIENT,
647 	PROP_CONTEXT,
648 	PROP_CONNECTION,
649 	PROP_OBJECT_PATH,
650 	PROP_DIRECT_CURSOR,
651 	PROP_ALPHABET,
652 	PROP_TOTAL,
653 	PROP_POSITION,
654 };
655 
656 enum {
657 	REFRESH,
658 	LAST_SIGNAL
659 };
660 
661 static guint signals[LAST_SIGNAL];
662 
G_DEFINE_TYPE_WITH_CODE(EBookClientCursor,e_book_client_cursor,G_TYPE_OBJECT,G_ADD_PRIVATE (EBookClientCursor)G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,e_book_client_cursor_initable_init))663 G_DEFINE_TYPE_WITH_CODE (
664 	EBookClientCursor,
665 	e_book_client_cursor,
666 	G_TYPE_OBJECT,
667 	G_ADD_PRIVATE (EBookClientCursor)
668 	G_IMPLEMENT_INTERFACE (
669 		G_TYPE_INITABLE,
670 		e_book_client_cursor_initable_init))
671 
672 /****************************************************
673  *                  GObjectClass                    *
674  ****************************************************/
675 static void
676 e_book_client_cursor_class_init (EBookClientCursorClass *class)
677 {
678 	GObjectClass *object_class;
679 
680 	object_class = G_OBJECT_CLASS (class);
681 	object_class->dispose = book_client_cursor_dispose;
682 	object_class->finalize = book_client_cursor_finalize;
683 	object_class->set_property = book_client_cursor_set_property;
684 	object_class->get_property = book_client_cursor_get_property;
685 
686 	/**
687 	 * EBookClientCursor:sort-fields:
688 	 *
689 	 * The #EContactField names to sort this cursor with
690 	 *
691 	 * <note><para>This is an internal parameter for constructing the
692 	 * cursor, to construct the cursor use e_book_client_get_cursor().
693 	 * </para></note>
694 	 *
695 	 * Since: 3.12
696 	 */
697 	g_object_class_install_property (
698 		object_class,
699 		PROP_SORT_FIELDS,
700 		g_param_spec_boxed (
701 			"sort-fields",
702 			"Sort Fields",
703 			"The #EContactField names to sort this cursor with",
704 			G_TYPE_STRV,
705 			G_PARAM_WRITABLE |
706 			G_PARAM_CONSTRUCT_ONLY |
707 			G_PARAM_STATIC_STRINGS));
708 
709 	/**
710 	 * EBookClientCursor:client:
711 	 *
712 	 * The #EBookClient which this cursor was created for
713 	 *
714 	 * Since: 3.12
715 	 */
716 	g_object_class_install_property (
717 		object_class,
718 		PROP_CLIENT,
719 		g_param_spec_object (
720 			"client",
721 			"Client",
722 			"The EBookClient for the cursor",
723 			E_TYPE_BOOK_CLIENT,
724 			G_PARAM_READWRITE |
725 			G_PARAM_CONSTRUCT_ONLY |
726 			G_PARAM_STATIC_STRINGS));
727 
728 	/**
729 	 * EBookClientCursor:context:
730 	 *
731 	 * The #GMainContext in which the #EBookClient created this cursor.
732 	 *
733 	 * <note><para>This is an internal parameter for constructing the
734 	 * cursor, to construct the cursor use e_book_client_get_cursor().
735 	 * </para></note>
736 	 *
737 	 * Since: 3.12
738 	 */
739 	g_object_class_install_property (
740 		object_class,
741 		PROP_CONTEXT,
742 		g_param_spec_boxed (
743 			"context",
744 			"Context",
745 			"The GMainContext in which this cursor was created",
746 			G_TYPE_MAIN_CONTEXT,
747 			G_PARAM_WRITABLE |
748 			G_PARAM_CONSTRUCT_ONLY |
749 			G_PARAM_STATIC_STRINGS));
750 
751 	/**
752 	 * EBookClientCursor:connection:
753 	 *
754 	 * The #GDBusConnection to the addressbook server.
755 	 *
756 	 * <note><para>This is an internal parameter for constructing the
757 	 * cursor, to construct the cursor use e_book_client_get_cursor().
758 	 * </para></note>
759 	 *
760 	 * Since: 3.12
761 	 */
762 	g_object_class_install_property (
763 		object_class,
764 		PROP_CONNECTION,
765 		g_param_spec_object (
766 			"connection",
767 			"Connection",
768 			"The GDBusConnection used "
769 			"to create the D-Bus proxy",
770 			G_TYPE_DBUS_CONNECTION,
771 			G_PARAM_WRITABLE |
772 			G_PARAM_CONSTRUCT_ONLY |
773 			G_PARAM_STATIC_STRINGS));
774 
775 	/**
776 	 * EBookClientCursor:object-path:
777 	 *
778 	 * The D-Bus object path to find the server side cursor object.
779 	 *
780 	 * <note><para>This is an internal parameter for constructing the
781 	 * cursor, to construct the cursor use e_book_client_get_cursor().
782 	 * </para></note>
783 	 *
784 	 * Since: 3.12
785 	 */
786 	g_object_class_install_property (
787 		object_class,
788 		PROP_OBJECT_PATH,
789 		g_param_spec_string (
790 			"object-path",
791 			"Object Path",
792 			"The object path used "
793 			"to create the D-Bus proxy",
794 			NULL,
795 			G_PARAM_WRITABLE |
796 			G_PARAM_CONSTRUCT_ONLY |
797 			G_PARAM_STATIC_STRINGS));
798 
799 	/**
800 	 * EBookClientCursor:direct-cursor:
801 	 *
802 	 * The direct handle to the #EDataBookCursor for direct read access mode.
803 	 *
804 	 * <note><para>This is an internal parameter for constructing the
805 	 * cursor, to construct the cursor use e_book_client_get_cursor().
806 	 * </para></note>
807 	 *
808 	 * Since: 3.12
809 	 */
810 	g_object_class_install_property (
811 		object_class,
812 		PROP_DIRECT_CURSOR,
813 		g_param_spec_object (
814 			"direct-cursor",
815 			"Direct Cursor",
816 			"The EDataBookCursor for direct read access",
817 			E_TYPE_DATA_BOOK_CURSOR,
818 			G_PARAM_WRITABLE |
819 			G_PARAM_CONSTRUCT_ONLY |
820 			G_PARAM_STATIC_STRINGS));
821 
822 	/**
823 	 * EBookClientCursor:alphabet:
824 	 *
825 	 * The currently <link linkend="cursor-alphabet">active alphabet</link>.
826 	 *
827 	 * The value is a %NULL terminated array of strings,
828 	 * each string is suitable to display a specific letter
829 	 * in the active alphabet.
830 	 *
831 	 * Indexes from this array can later be used with
832 	 * e_book_client_cursor_set_alphabetic_index().
833 	 *
834 	 * This property will automatically change if the
835 	 * active locale of the addressbook server changes.
836 	 *
837 	 * Property change notifications are guaranteed to be
838 	 * delivered in the #GMainContext which was the thread
839 	 * default context at cursor creation time.
840 	 *
841 	 * Since: 3.12
842 	 */
843 	g_object_class_install_property (
844 		object_class,
845 		PROP_ALPHABET,
846 		g_param_spec_boxed (
847 			"alphabet",
848 			"Alphabet",
849 			"The active alphabet",
850 			G_TYPE_STRV,
851 			G_PARAM_READABLE |
852 			G_PARAM_STATIC_STRINGS));
853 
854 	/**
855 	 * EBookClientCursor:total:
856 	 *
857 	 * The total number of contacts which satisfy the cursor's query.
858 	 *
859 	 * Property change notifications are guaranteed to be
860 	 * delivered in the #GMainContext which was the thread
861 	 * default context at cursor creation time.
862 	 *
863 	 * Since: 3.12
864 	 */
865 	g_object_class_install_property (
866 		object_class,
867 		PROP_TOTAL,
868 		g_param_spec_int (
869 			"total",
870 			"Total",
871 			"The total contacts for this cursor's query",
872 			0, G_MAXINT, 0,
873 			G_PARAM_READABLE |
874 			G_PARAM_STATIC_STRINGS));
875 
876 	/**
877 	 * EBookClientCursor:position:
878 	 *
879 	 * The current cursor position in the cursor's result list.
880 	 *
881 	 * More specifically, the cursor position is defined as
882 	 * the number of contacts leading up to the current
883 	 * cursor position, inclusive of the current cursor
884 	 * position.
885 	 *
886 	 * If the position value is 0, then the cursor is positioned
887 	 * before the contact list in the symbolic %E_BOOK_CURSOR_ORIGIN_BEGIN
888 	 * position. If the position value is greater than
889 	 * #EBookClientCursor:total, this indicates that the cursor is
890 	 * positioned after the contact list in the symbolic
891 	 * %E_BOOK_CURSOR_ORIGIN_END position.
892 	 *
893 	 * Property change notifications are guaranteed to be
894 	 * delivered in the #GMainContext which was the thread
895 	 * default context at cursor creation time.
896 	 *
897 	 * Since: 3.12
898 	 */
899 	g_object_class_install_property (
900 		object_class,
901 		PROP_POSITION,
902 		g_param_spec_int (
903 			"position",
904 			"Position",
905 			"The current cursor position",
906 			0, G_MAXINT, 0,
907 			G_PARAM_READABLE |
908 			G_PARAM_STATIC_STRINGS));
909 
910 	/**
911 	 * EBookClientCursor::refresh:
912 	 * @cursor: The #EBookClientCursor which needs to be refreshed
913 	 *
914 	 * Indicates that the addressbook has been modified and
915 	 * that any content currently being displayed from the current
916 	 * cursor position should be reloaded.
917 	 *
918 	 * This signal is guaranteed to be delivered in the #GMainContext
919 	 * which was the thread default context at cursor creation time.
920 	 *
921 	 * Since: 3.12
922 	 */
923 	signals[REFRESH] = g_signal_new (
924 		"refresh",
925 		G_OBJECT_CLASS_TYPE (object_class),
926 		G_SIGNAL_RUN_LAST,
927 		G_STRUCT_OFFSET (EBookClientCursorClass, refresh),
928 		NULL, NULL, NULL,
929 		G_TYPE_NONE, 0);
930 }
931 
932 static void
e_book_client_cursor_init(EBookClientCursor * cursor)933 e_book_client_cursor_init (EBookClientCursor *cursor)
934 {
935 	cursor->priv = e_book_client_cursor_get_instance_private (cursor);
936 
937 	g_mutex_init (&cursor->priv->main_context_lock);
938 	g_mutex_init (&cursor->priv->notifications_lock);
939 }
940 
941 static void
book_client_cursor_dispose(GObject * object)942 book_client_cursor_dispose (GObject *object)
943 {
944 	EBookClientCursor *cursor = E_BOOK_CLIENT_CURSOR (object);
945 	EBookClientCursorPrivate *priv = cursor->priv;
946 	gint i;
947 
948 	book_client_cursor_set_direct_cursor (cursor, NULL);
949 	book_client_cursor_set_client (cursor, NULL);
950 	book_client_cursor_set_proxy (cursor, NULL);
951 	book_client_cursor_set_connection (cursor, NULL);
952 	book_client_cursor_set_context (cursor, NULL);
953 
954 	g_mutex_lock (&cursor->priv->notifications_lock);
955 	if (priv->notification_source) {
956 		g_source_destroy (priv->notification_source);
957 		g_source_unref (priv->notification_source);
958 		priv->notification_source = NULL;
959 	}
960 
961 	for (i = 0; i < N_NOTIFICATION_TYPES; i++) {
962 		notification_free (priv->notification[i]);
963 		priv->notification[i] = NULL;
964 	}
965 	g_mutex_unlock (&cursor->priv->notifications_lock);
966 
967 	/* Chain up to parent's dispose() method. */
968 	G_OBJECT_CLASS (e_book_client_cursor_parent_class)->dispose (object);
969 }
970 
971 static void
book_client_cursor_finalize(GObject * object)972 book_client_cursor_finalize (GObject *object)
973 {
974 	EBookClientCursor        *cursor = E_BOOK_CLIENT_CURSOR (object);
975 	EBookClientCursorPrivate *priv = cursor->priv;
976 
977 	g_free (priv->locale);
978 	g_free (priv->revision);
979 	g_free (priv->object_path);
980 	if (priv->sort_fields)
981 		g_strfreev (priv->sort_fields);
982 	if (priv->collator)
983 		e_collator_unref (priv->collator);
984 	g_mutex_clear (&priv->main_context_lock);
985 	g_mutex_clear (&cursor->priv->notifications_lock);
986 
987 	/* Chain up to parent's finalize() method. */
988 	G_OBJECT_CLASS (e_book_client_cursor_parent_class)->finalize (object);
989 }
990 
991 static void
book_client_cursor_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)992 book_client_cursor_set_property (GObject *object,
993                                  guint property_id,
994                                  const GValue *value,
995                                  GParamSpec *pspec)
996 {
997 	EBookClientCursor        *cursor = E_BOOK_CLIENT_CURSOR (object);
998 	EBookClientCursorPrivate *priv = cursor->priv;
999 
1000 	switch (property_id) {
1001 	case PROP_SORT_FIELDS:
1002 		priv->sort_fields = g_value_dup_boxed (value);
1003 		break;
1004 
1005 	case PROP_CLIENT:
1006 		book_client_cursor_set_client (
1007 			E_BOOK_CLIENT_CURSOR (object),
1008 			g_value_get_object (value));
1009 		break;
1010 
1011 	case PROP_CONTEXT:
1012 		book_client_cursor_set_context (
1013 			E_BOOK_CLIENT_CURSOR (object),
1014 			g_value_get_boxed (value));
1015 		break;
1016 
1017 	case PROP_CONNECTION:
1018 		book_client_cursor_set_connection (
1019 			E_BOOK_CLIENT_CURSOR (object),
1020 			g_value_get_object (value));
1021 		break;
1022 
1023 	case PROP_OBJECT_PATH:
1024 		book_client_cursor_set_object_path (
1025 			E_BOOK_CLIENT_CURSOR (object),
1026 			g_value_get_string (value));
1027 		break;
1028 
1029 	case PROP_DIRECT_CURSOR:
1030 		book_client_cursor_set_direct_cursor (
1031 			E_BOOK_CLIENT_CURSOR (object),
1032 			g_value_get_object (value));
1033 		break;
1034 
1035 	default:
1036 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1037 		break;
1038 	}
1039 }
1040 
1041 static void
book_client_cursor_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1042 book_client_cursor_get_property (GObject *object,
1043                                  guint property_id,
1044                                  GValue *value,
1045                                  GParamSpec *pspec)
1046 {
1047 	switch (property_id) {
1048 	case PROP_CLIENT:
1049 		g_value_take_object (
1050 			value,
1051 			e_book_client_cursor_ref_client (
1052 				E_BOOK_CLIENT_CURSOR (object)));
1053 		break;
1054 
1055 	case PROP_ALPHABET:
1056 		g_value_set_boxed (
1057 			value,
1058 			e_book_client_cursor_get_alphabet (
1059 				E_BOOK_CLIENT_CURSOR (object),
1060 				NULL, NULL, NULL, NULL));
1061 		break;
1062 
1063 	case PROP_TOTAL:
1064 		g_value_set_int (
1065 			value,
1066 			e_book_client_cursor_get_total (
1067 				E_BOOK_CLIENT_CURSOR (object)));
1068 		break;
1069 
1070 	case PROP_POSITION:
1071 		g_value_set_int (
1072 			value,
1073 			e_book_client_cursor_get_position (
1074 				E_BOOK_CLIENT_CURSOR (object)));
1075 		break;
1076 
1077 	default:
1078 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1079 		break;
1080 
1081 	}
1082 }
1083 
1084 /****************************************************
1085  *                    GInitable                     *
1086  ****************************************************/
1087 static void
e_book_client_cursor_initable_init(GInitableIface * iface)1088 e_book_client_cursor_initable_init (GInitableIface *iface)
1089 {
1090 	iface->init = book_client_cursor_initable_init;
1091 }
1092 
1093 static gboolean
book_client_cursor_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)1094 book_client_cursor_initable_init (GInitable *initable,
1095                                   GCancellable *cancellable,
1096                                   GError **error)
1097 {
1098 	EBookClientCursor        *cursor = E_BOOK_CLIENT_CURSOR (initable);
1099 	EBookClientCursorPrivate *priv = cursor->priv;
1100 	EDBusAddressBookCursor   *proxy;
1101 	gchar                    *bus_name;
1102 
1103 	/* We only need a proxy for regular access, no need in DRA mode */
1104 	if (priv->direct_cursor)
1105 		return TRUE;
1106 
1107 	bus_name = e_client_dup_bus_name (E_CLIENT (priv->client));
1108 
1109 	proxy = e_dbus_address_book_cursor_proxy_new_sync (
1110 		priv->connection,
1111 		G_DBUS_PROXY_FLAGS_NONE,
1112 		bus_name,
1113 		priv->object_path,
1114 		cancellable, error);
1115 
1116 	g_free (bus_name);
1117 
1118 	if (!proxy)
1119 		return FALSE;
1120 
1121 	book_client_cursor_set_proxy (cursor, proxy);
1122 	g_object_unref (proxy);
1123 
1124 	return TRUE;
1125 }
1126 
1127 /****************************************************
1128  *                Private Mutators                  *
1129  ****************************************************
1130  *
1131  * All private mutators are called either in the thread
1132  * which e_book_client_get_cursor() was originally called,
1133  * or in the object construction process where there is
1134  * a well known strong reference to the EBookClientCursor
1135  * instance.
1136  */
1137 static void
book_client_cursor_set_client(EBookClientCursor * cursor,EBookClient * client)1138 book_client_cursor_set_client (EBookClientCursor *cursor,
1139                                EBookClient *client)
1140 {
1141 	EBookClientCursorPrivate *priv = cursor->priv;
1142 
1143 	g_return_if_fail (client == NULL || E_IS_BOOK_CLIENT (client));
1144 
1145 	/* Clients can't really change, but we set up this
1146 	 * mutator style code just to manage the signal connections
1147 	 * we watch on the client, we need to disconnect them properly.
1148 	 */
1149 	if (priv->client != client) {
1150 
1151 		if (priv->client) {
1152 
1153 			/* Disconnect signals */
1154 			g_signal_handler_disconnect (priv->client, priv->revision_changed_id);
1155 			g_signal_handler_disconnect (priv->client, priv->locale_changed_id);
1156 			priv->revision_changed_id = 0;
1157 			priv->locale_changed_id = 0;
1158 			g_object_unref (priv->client);
1159 		}
1160 
1161 		/* Set the new client */
1162 		priv->client = client;
1163 
1164 		if (priv->client) {
1165 			gchar *revision = NULL;
1166 
1167 			/* Connect signals */
1168 			priv->revision_changed_id =
1169 				g_signal_connect_data (
1170 					client, "backend-property-changed",
1171 					G_CALLBACK (client_revision_changed_cb),
1172 					e_weak_ref_new (cursor),
1173 					(GClosureNotify) e_weak_ref_free,
1174 					0);
1175 			priv->locale_changed_id =
1176 				g_signal_connect_data (
1177 					client, "notify::locale",
1178 					G_CALLBACK (client_locale_changed_cb),
1179 					e_weak_ref_new (cursor),
1180 					(GClosureNotify) e_weak_ref_free,
1181 					0);
1182 
1183 			/* Load initial locale & revision */
1184 			book_client_cursor_set_locale (cursor, e_book_client_get_locale (priv->client));
1185 
1186 			/* This loads a cached D-Bus property, no D-Bus activity */
1187 			e_client_get_backend_property_sync (
1188 				E_CLIENT (priv->client),
1189 				CLIENT_BACKEND_PROPERTY_REVISION,
1190 				&revision, NULL, NULL);
1191 			book_client_cursor_set_revision (cursor, revision);
1192 			g_free (revision);
1193 
1194 			g_object_ref (priv->client);
1195 		}
1196 	}
1197 }
1198 
1199 static void
book_client_cursor_set_connection(EBookClientCursor * cursor,GDBusConnection * connection)1200 book_client_cursor_set_connection (EBookClientCursor *cursor,
1201                                    GDBusConnection *connection)
1202 {
1203 	EBookClientCursorPrivate *priv = cursor->priv;
1204 
1205 	g_return_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection));
1206 
1207 	if (priv->connection != connection) {
1208 
1209 		if (priv->connection)
1210 			g_object_unref (priv->connection);
1211 
1212 		priv->connection = connection;
1213 
1214 		if (priv->connection)
1215 			g_object_ref (priv->connection);
1216 	}
1217 }
1218 
1219 static void
proxy_dispose_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)1220 proxy_dispose_cb (GObject *source_object,
1221                   GAsyncResult *result,
1222                   gpointer user_data)
1223 {
1224 	GError *local_error = NULL;
1225 
1226 	e_dbus_address_book_cursor_call_dispose_finish (
1227 		E_DBUS_ADDRESS_BOOK_CURSOR (source_object), result, &local_error);
1228 
1229 	if (local_error != NULL) {
1230 		g_dbus_error_strip_remote_error (local_error);
1231 		g_warning ("%s: %s", G_STRFUNC, local_error->message);
1232 		g_error_free (local_error);
1233 	}
1234 }
1235 
1236 static void
book_client_cursor_set_proxy(EBookClientCursor * cursor,EDBusAddressBookCursor * proxy)1237 book_client_cursor_set_proxy (EBookClientCursor *cursor,
1238                               EDBusAddressBookCursor *proxy)
1239 {
1240 	EBookClientCursorPrivate *priv = cursor->priv;
1241 
1242 	g_return_if_fail (proxy == NULL || E_DBUS_IS_ADDRESS_BOOK_CURSOR (proxy));
1243 
1244 	if (priv->dbus_proxy != proxy) {
1245 
1246 		if (priv->dbus_proxy) {
1247 			g_signal_handler_disconnect (priv->dbus_proxy, priv->proxy_total_changed_id);
1248 			g_signal_handler_disconnect (priv->dbus_proxy, priv->proxy_position_changed_id);
1249 			priv->proxy_total_changed_id = 0;
1250 			priv->proxy_position_changed_id = 0;
1251 
1252 			/* Call D-Bus dispose() asynchronously
1253 			 * so we don't block in our dispose() phase.*/
1254 			e_dbus_address_book_cursor_call_dispose (
1255 				priv->dbus_proxy, NULL,
1256 				proxy_dispose_cb, NULL);
1257 
1258 			g_object_unref (priv->dbus_proxy);
1259 		}
1260 
1261 		priv->dbus_proxy = proxy;
1262 
1263 		if (priv->dbus_proxy) {
1264 			gint position, total;
1265 
1266 			priv->proxy_total_changed_id =
1267 				g_signal_connect_data (
1268 					priv->dbus_proxy, "notify::total",
1269 					G_CALLBACK (proxy_total_changed_cb),
1270 					e_weak_ref_new (cursor),
1271 					(GClosureNotify) e_weak_ref_free,
1272 					0);
1273 			priv->proxy_position_changed_id =
1274 				g_signal_connect_data (
1275 					priv->dbus_proxy, "notify::position",
1276 					G_CALLBACK (proxy_position_changed_cb),
1277 					e_weak_ref_new (cursor),
1278 					(GClosureNotify) e_weak_ref_free,
1279 					0);
1280 
1281 			/* Set initial values */
1282 			total = e_dbus_address_book_cursor_get_total (proxy);
1283 			position = e_dbus_address_book_cursor_get_position (proxy);
1284 			book_client_cursor_set_total (cursor, total);
1285 			book_client_cursor_set_position (cursor, position);
1286 
1287 			g_object_ref (priv->dbus_proxy);
1288 		}
1289 	}
1290 }
1291 
1292 static void
book_client_cursor_set_context(EBookClientCursor * cursor,GMainContext * context)1293 book_client_cursor_set_context (EBookClientCursor *cursor,
1294                                 GMainContext *context)
1295 {
1296 	EBookClientCursorPrivate *priv = cursor->priv;
1297 
1298 	g_mutex_lock (&cursor->priv->main_context_lock);
1299 
1300 	if (priv->main_context != context) {
1301 		if (priv->main_context)
1302 			g_main_context_unref (priv->main_context);
1303 
1304 		priv->main_context = context;
1305 
1306 		if (priv->main_context)
1307 			g_main_context_ref (priv->main_context);
1308 	}
1309 
1310 	g_mutex_unlock (&cursor->priv->main_context_lock);
1311 }
1312 
1313 static GMainContext *
book_client_cursor_ref_context(EBookClientCursor * cursor)1314 book_client_cursor_ref_context (EBookClientCursor *cursor)
1315 {
1316 	GMainContext *main_context = NULL;
1317 
1318 	/* This is called from D-Bus callbacks which will fire from
1319 	 * whichever thread the EBookClient created the EBookClientCursor
1320 	 * in, and also from EBookClient signal callbacks which get
1321 	 * fired in the thread that the EBookClient was created in,
1322 	 * which might not be the same thread that e_book_client_get_cursor()
1323 	 * was called from.
1324 	 */
1325 	g_mutex_lock (&cursor->priv->main_context_lock);
1326 
1327 	if (cursor->priv->main_context)
1328 		main_context = g_main_context_ref (cursor->priv->main_context);
1329 
1330 	g_mutex_unlock (&cursor->priv->main_context_lock);
1331 
1332 	return main_context;
1333 }
1334 
1335 static gboolean
book_client_cursor_context_is_current(EBookClientCursor * cursor)1336 book_client_cursor_context_is_current (EBookClientCursor *cursor)
1337 {
1338 	GMainContext *main_context, *current_context;
1339 	gboolean is_current = FALSE;
1340 
1341 	main_context = book_client_cursor_ref_context (cursor);
1342 	current_context = g_main_context_ref_thread_default ();
1343 
1344 	if (main_context) {
1345 
1346 		is_current = (main_context == current_context);
1347 
1348 		g_main_context_unref (main_context);
1349 	}
1350 
1351 	g_main_context_unref (current_context);
1352 
1353 	return is_current;
1354 }
1355 
1356 /* Secretly shared API */
1357 void book_client_delete_direct_cursor (EBookClient *client,
1358 				       EDataBookCursor *cursor);
1359 
1360 static void
book_client_cursor_set_direct_cursor(EBookClientCursor * cursor,EDataBookCursor * direct_cursor)1361 book_client_cursor_set_direct_cursor (EBookClientCursor *cursor,
1362                                       EDataBookCursor *direct_cursor)
1363 {
1364 	EBookClientCursorPrivate *priv = cursor->priv;
1365 
1366 	g_return_if_fail (direct_cursor == NULL || E_IS_DATA_BOOK_CURSOR (direct_cursor));
1367 
1368 	if (priv->direct_cursor != direct_cursor) {
1369 
1370 		if (priv->direct_cursor) {
1371 
1372 			g_signal_handler_disconnect (priv->direct_cursor, priv->dra_total_changed_id);
1373 			g_signal_handler_disconnect (priv->direct_cursor, priv->dra_position_changed_id);
1374 			priv->dra_total_changed_id = 0;
1375 			priv->dra_position_changed_id = 0;
1376 
1377 			/* Tell EBookClient to delete the cursor
1378 			 *
1379 			 * This should only happen in ->dispose()
1380 			 * before releasing our strong reference to the EBookClient
1381 			 */
1382 			g_warn_if_fail (priv->client != NULL);
1383 			book_client_delete_direct_cursor (
1384 				priv->client,
1385 				priv->direct_cursor);
1386 
1387 			g_object_unref (priv->direct_cursor);
1388 		}
1389 
1390 		priv->direct_cursor = direct_cursor;
1391 
1392 		if (priv->direct_cursor) {
1393 			GError *error = NULL;
1394 			gchar *freeme = NULL;
1395 			gint total, position;
1396 
1397 			priv->dra_total_changed_id =
1398 				g_signal_connect (
1399 					priv->direct_cursor, "notify::total",
1400 					G_CALLBACK (dra_total_changed_cb),
1401 					cursor);
1402 			priv->dra_position_changed_id =
1403 				g_signal_connect (
1404 					priv->direct_cursor, "notify::position",
1405 					G_CALLBACK (dra_position_changed_cb),
1406 					cursor);
1407 
1408 			/* Load initial locale */
1409 			if (priv->direct_cursor &&
1410 			    !e_data_book_cursor_load_locale (priv->direct_cursor,
1411 							     &freeme, NULL, &error)) {
1412 				g_warning (
1413 					"Error loading locale in direct read access cursor: %s",
1414 					error->message);
1415 				g_clear_error (&error);
1416 			}
1417 			g_free (freeme);
1418 
1419 			/* Set initial values */
1420 			total = e_data_book_cursor_get_total (priv->direct_cursor);
1421 			position = e_data_book_cursor_get_position (priv->direct_cursor);
1422 			book_client_cursor_set_total (cursor, total);
1423 			book_client_cursor_set_position (cursor, position);
1424 
1425 			g_object_ref (priv->direct_cursor);
1426 		}
1427 	}
1428 }
1429 
1430 static void
book_client_cursor_set_object_path(EBookClientCursor * cursor,const gchar * object_path)1431 book_client_cursor_set_object_path (EBookClientCursor *cursor,
1432                                     const gchar *object_path)
1433 {
1434 	g_return_if_fail (cursor->priv->object_path == NULL);
1435 
1436 	cursor->priv->object_path = g_strdup (object_path);
1437 }
1438 
1439 static void
book_client_cursor_set_locale(EBookClientCursor * cursor,const gchar * locale)1440 book_client_cursor_set_locale (EBookClientCursor *cursor,
1441                                const gchar *locale)
1442 {
1443 	EBookClientCursorPrivate *priv = cursor->priv;
1444 	GError                   *error = NULL;
1445 
1446 	if (g_strcmp0 (priv->locale, locale) == 0)
1447 		return;
1448 
1449 	g_free (priv->locale);
1450 	if (priv->collator)
1451 		e_collator_unref (priv->collator);
1452 
1453 	priv->locale = g_strdup (locale);
1454 	priv->collator = e_collator_new (locale, &error);
1455 
1456 	if (!priv->collator) {
1457 		g_warning (
1458 			"Error loading collator for locale '%s': %s",
1459 			locale, error->message);
1460 		g_clear_error (&error);
1461 		return;
1462 	}
1463 
1464 	e_collator_get_index_labels (
1465 		priv->collator,
1466 		&priv->n_labels,
1467 		NULL, NULL, NULL);
1468 
1469 	/* The server side EDataBookCursor should have already
1470 	 * reset its cursor values internally and notified
1471 	 * a new total & position value, however we need to
1472 	 * explicitly load the new locale for DRA cursors.
1473 	 */
1474 	if (priv->direct_cursor &&
1475 	    !e_data_book_cursor_load_locale (priv->direct_cursor, NULL, NULL, &error)) {
1476 		g_warning (
1477 			"Error loading locale in direct read access cursor: %s",
1478 			error->message);
1479 		g_clear_error (&error);
1480 	}
1481 
1482 	/* Notify the alphabet change */
1483 	g_object_notify (G_OBJECT (cursor), "alphabet");
1484 
1485 	/* The alphabet changing should have been enough,
1486 	 * but still trigger a refresh
1487 	 */
1488 	g_signal_emit (cursor, signals[REFRESH], 0);
1489 }
1490 
1491 static void
book_client_cursor_set_revision(EBookClientCursor * cursor,const gchar * revision)1492 book_client_cursor_set_revision (EBookClientCursor *cursor,
1493                                  const gchar *revision)
1494 {
1495 	EBookClientCursorPrivate *priv = cursor->priv;
1496 
1497 	if (g_strcmp0 (priv->revision, revision) != 0) {
1498 
1499 		g_free (priv->revision);
1500 		priv->revision = g_strdup (revision);
1501 
1502 		/* In DRA mode we need to reload our local
1503 		 * total / position calculations with EDataBookCursor APIs
1504 		 */
1505 		if (priv->direct_cursor) {
1506 			GError *error = NULL;
1507 
1508 			if (!e_data_book_cursor_recalculate (priv->direct_cursor, NULL, &error)) {
1509 				g_warning ("Error calcualting cursor position: %s", error->message);
1510 			} else {
1511 				g_object_freeze_notify (G_OBJECT (cursor));
1512 				book_client_cursor_set_total (cursor, e_data_book_cursor_get_total (priv->direct_cursor));
1513 				book_client_cursor_set_position (cursor, e_data_book_cursor_get_position (priv->direct_cursor));
1514 				g_object_thaw_notify (G_OBJECT (cursor));
1515 			}
1516 		}
1517 
1518 		/* The addressbook has changed, need a refresh */
1519 		g_signal_emit (cursor, signals[REFRESH], 0);
1520 	}
1521 }
1522 
1523 static void
book_client_cursor_set_total(EBookClientCursor * cursor,gint total)1524 book_client_cursor_set_total (EBookClientCursor *cursor,
1525                               gint total)
1526 {
1527 	EBookClientCursorPrivate *priv = cursor->priv;
1528 
1529 	if (priv->total != total) {
1530 		priv->total = total;
1531 		g_object_notify (G_OBJECT (cursor), "total");
1532 	}
1533 }
1534 
1535 static void
book_client_cursor_set_position(EBookClientCursor * cursor,gint position)1536 book_client_cursor_set_position (EBookClientCursor *cursor,
1537                                  gint position)
1538 {
1539 	EBookClientCursorPrivate *priv = cursor->priv;
1540 
1541 	if (priv->position != position) {
1542 		priv->position = position;
1543 		g_object_notify (G_OBJECT (cursor), "position");
1544 	}
1545 }
1546 
1547 /****************************************************
1548  *         Notifications from other threads         *
1549  ****************************************************
1550  *
1551  * The notification subsystem takes care of calling
1552  * our private mutator functions from the thread in
1553  * which e_book_client_get_cursor() was originally
1554  * called, where it's safe to emit signals on the
1555  * EBookClientCursor instance.
1556  *
1557  * The notification functions, notification_new_string()
1558  * and notification_new_int() must be called where
1559  * a strong reference to the EBookClientCursor exists.
1560  */
1561 
1562 static void
notification_new_string(EBookClientCursor * cursor,NotificationType type,const gchar * value)1563 notification_new_string (EBookClientCursor *cursor,
1564                          NotificationType type,
1565                          const gchar *value)
1566 {
1567 	Notification *notification = g_slice_new0 (Notification);
1568 
1569 	notification->type = type;
1570 	g_weak_ref_init (&notification->cursor, cursor);
1571 
1572 	g_value_init (&notification->value, G_TYPE_STRING);
1573 	g_value_set_string (&notification->value, value);
1574 
1575 	notification_queue (cursor, notification);
1576 }
1577 
1578 static void
notification_new_int(EBookClientCursor * cursor,NotificationType type,gint value)1579 notification_new_int (EBookClientCursor *cursor,
1580                       NotificationType type,
1581                       gint value)
1582 {
1583 	Notification *notification = g_slice_new0 (Notification);
1584 
1585 	notification->type = type;
1586 	g_weak_ref_init (&notification->cursor, cursor);
1587 
1588 	g_value_init (&notification->value, G_TYPE_INT);
1589 	g_value_set_int (&notification->value, value);
1590 
1591 	notification_queue (cursor, notification);
1592 }
1593 
1594 static void
notification_free(Notification * notification)1595 notification_free (Notification *notification)
1596 {
1597 	if (notification) {
1598 		g_weak_ref_clear (&notification->cursor);
1599 		g_value_unset (&notification->value);
1600 		g_slice_free (Notification, notification);
1601 	}
1602 }
1603 
1604 static void
notification_queue(EBookClientCursor * cursor,Notification * notification)1605 notification_queue (EBookClientCursor *cursor,
1606                     Notification *notification)
1607 {
1608 	EBookClientCursorPrivate *priv = cursor->priv;
1609 	GMainContext *context;
1610 
1611 	g_mutex_lock (&cursor->priv->notifications_lock);
1612 
1613 	notification_free (priv->notification[notification->type]);
1614 	priv->notification[notification->type] = notification;
1615 
1616 	context = book_client_cursor_ref_context (cursor);
1617 
1618 	if (context && priv->notification_source == NULL) {
1619 		/* Hold on to a reference, release our reference in dispatch() */
1620 		priv->notification_source = g_idle_source_new ();
1621 		g_source_set_callback (
1622 			priv->notification_source,
1623 			(GSourceFunc) notification_dispatch,
1624 			e_weak_ref_new (cursor),
1625 			(GDestroyNotify) e_weak_ref_free);
1626 		g_source_attach (priv->notification_source, context);
1627 		g_main_context_unref (context);
1628 	}
1629 
1630 	g_mutex_unlock (&cursor->priv->notifications_lock);
1631 }
1632 
1633 static gboolean
notification_dispatch(GWeakRef * weak_ref)1634 notification_dispatch (GWeakRef *weak_ref)
1635 {
1636 	EBookClientCursor *cursor;
1637 	EBookClientCursorPrivate *priv;
1638 	Notification *notification[N_NOTIFICATION_TYPES];
1639 	gint i;
1640 
1641 	cursor = g_weak_ref_get (weak_ref);
1642 	if (!cursor)
1643 		return FALSE;
1644 
1645 	priv = cursor->priv;
1646 
1647 	/* Collect notifications now and let notifications
1648 	 * be queued from other threads after this point
1649 	 */
1650 	g_mutex_lock (&cursor->priv->notifications_lock);
1651 
1652 	for (i = 0; i < N_NOTIFICATION_TYPES; i++) {
1653 		notification[i] = priv->notification[i];
1654 		priv->notification[i] = NULL;
1655 	}
1656 
1657 	g_source_unref (priv->notification_source);
1658 	priv->notification_source = NULL;
1659 	g_mutex_unlock (&cursor->priv->notifications_lock);
1660 
1661 	g_object_freeze_notify (G_OBJECT (cursor));
1662 
1663 	if (notification[TOTAL_CHANGED])
1664 		book_client_cursor_set_total (
1665 			cursor,
1666 			g_value_get_int (&(notification[TOTAL_CHANGED]->value)));
1667 
1668 	if (notification[POSITION_CHANGED])
1669 		book_client_cursor_set_position (
1670 			cursor,
1671 			g_value_get_int (&(notification[POSITION_CHANGED]->value)));
1672 
1673 	if (notification[REVISION_CHANGED])
1674 		book_client_cursor_set_revision (
1675 			cursor,
1676 			g_value_get_string (&(notification[REVISION_CHANGED]->value)));
1677 
1678 	if (notification[LOCALE_CHANGED])
1679 		book_client_cursor_set_locale (
1680 			cursor,
1681 			g_value_get_string (&(notification[LOCALE_CHANGED]->value)));
1682 
1683 	g_object_thaw_notify (G_OBJECT (cursor));
1684 
1685 	for (i = 0; i < N_NOTIFICATION_TYPES; i++)
1686 		notification_free (notification[i]);
1687 
1688 	g_object_unref (cursor);
1689 
1690 	return FALSE;
1691 }
1692 
1693 /****************************************************
1694  *             Callbacks from EBookClient           *
1695  ****************************************************/
1696 static void
client_revision_changed_cb(EClient * client,const gchar * prop_name,const gchar * prop_value,GWeakRef * weak_ref)1697 client_revision_changed_cb (EClient *client,
1698                             const gchar *prop_name,
1699                             const gchar *prop_value,
1700                             GWeakRef *weak_ref)
1701 {
1702 	EBookClientCursor *cursor;
1703 
1704 	if (g_strcmp0 (prop_name, CLIENT_BACKEND_PROPERTY_REVISION) != 0)
1705 		return;
1706 
1707 	cursor = g_weak_ref_get (weak_ref);
1708 	if (cursor) {
1709 		notification_new_string (cursor, REVISION_CHANGED, prop_value);
1710 		g_object_unref (cursor);
1711 	}
1712 }
1713 
1714 static void
client_locale_changed_cb(EBookClient * book_client,GParamSpec * pspec,GWeakRef * weak_ref)1715 client_locale_changed_cb (EBookClient *book_client,
1716                           GParamSpec *pspec,
1717                           GWeakRef *weak_ref)
1718 {
1719 	EBookClientCursor *cursor;
1720 
1721 	cursor = g_weak_ref_get (weak_ref);
1722 	if (cursor) {
1723 		notification_new_string (cursor, LOCALE_CHANGED, e_book_client_get_locale (book_client));
1724 		g_object_unref (cursor);
1725 	}
1726 }
1727 
1728 /****************************************************
1729  *       Callbacks from EDBusAddressBookCursor      *
1730  ****************************************************/
1731 static void
proxy_total_changed_cb(EDBusAddressBookCursor * proxy,GParamSpec * pspec,GWeakRef * weak_ref)1732 proxy_total_changed_cb (EDBusAddressBookCursor *proxy,
1733                          GParamSpec *pspec,
1734                          GWeakRef *weak_ref)
1735 {
1736 	EBookClientCursor *cursor;
1737 
1738 	cursor = g_weak_ref_get (weak_ref);
1739 	if (cursor) {
1740 		notification_new_int (cursor, TOTAL_CHANGED,
1741 				      e_dbus_address_book_cursor_get_total (proxy));
1742 		g_object_unref (cursor);
1743 	}
1744 }
1745 
1746 static void
proxy_position_changed_cb(EDBusAddressBookCursor * proxy,GParamSpec * pspec,GWeakRef * weak_ref)1747 proxy_position_changed_cb (EDBusAddressBookCursor *proxy,
1748                            GParamSpec *pspec,
1749                            GWeakRef *weak_ref)
1750 {
1751 	EBookClientCursor *cursor;
1752 
1753 	cursor = g_weak_ref_get (weak_ref);
1754 	if (cursor) {
1755 		notification_new_int (cursor, POSITION_CHANGED,
1756 				      e_dbus_address_book_cursor_get_position (proxy));
1757 		g_object_unref (cursor);
1758 	}
1759 }
1760 
1761 /****************************************************
1762  *       Callbacks from EDBusAddressBookCursor      *
1763  ****************************************************/
1764 static void
dra_total_changed_cb(EDataBookCursor * direct_cursor,GParamSpec * pspec,EBookClientCursor * cursor)1765 dra_total_changed_cb (EDataBookCursor *direct_cursor,
1766                       GParamSpec *pspec,
1767                       EBookClientCursor *cursor)
1768 {
1769 	notification_new_int (cursor, TOTAL_CHANGED,
1770 			      e_data_book_cursor_get_total (direct_cursor));
1771 }
1772 
1773 static void
dra_position_changed_cb(EDataBookCursor * direct_cursor,GParamSpec * pspec,EBookClientCursor * cursor)1774 dra_position_changed_cb (EDataBookCursor *direct_cursor,
1775                          GParamSpec *pspec,
1776                          EBookClientCursor *cursor)
1777 {
1778 	notification_new_int (cursor, POSITION_CHANGED,
1779 			      e_data_book_cursor_get_position (direct_cursor));
1780 }
1781 
1782 /****************************************************
1783  *           Threaded method call contexts          *
1784  ****************************************************
1785  *
1786  * This subsystem is simply a toolbox of helper functions
1787  * to execute synchronous D-Bus method calls while providing
1788  * an asynchronous API.
1789  *
1790  * We choose this method of asynchronous D-Bus calls only
1791  * to be consistent with the rest of the libebook library.
1792  */
1793 struct _StepContext {
1794 	gchar *revision;
1795 	EBookCursorStepFlags flags;
1796 	EBookCursorOrigin origin;
1797 	gint count;
1798 	GSList *contacts;
1799 	guint new_total;
1800 	guint new_position;
1801 	gint n_results;
1802 };
1803 
1804 struct _AlphabetIndexContext {
1805 	gint index;
1806 	gchar *locale;
1807 	guint new_total;
1808 	guint new_position;
1809 };
1810 
1811 struct _SetSexpContext {
1812 	gchar *sexp;
1813 	guint new_total;
1814 	guint new_position;
1815 };
1816 
1817 static SetSexpContext *
set_sexp_context_new(const gchar * sexp)1818 set_sexp_context_new (const gchar *sexp)
1819 {
1820 	SetSexpContext *context = g_slice_new0 (SetSexpContext);
1821 
1822 	context->sexp = g_strdup (sexp);
1823 
1824 	return context;
1825 }
1826 
1827 static void
set_sexp_context_free(SetSexpContext * context)1828 set_sexp_context_free (SetSexpContext *context)
1829 {
1830 	if (context) {
1831 		g_free (context->sexp);
1832 		g_slice_free (SetSexpContext, context);
1833 	}
1834 }
1835 
1836 static gboolean
set_sexp_sync_internal(EBookClientCursor * cursor,const gchar * sexp,guint * new_total,guint * new_position,GCancellable * cancellable,GError ** error)1837 set_sexp_sync_internal (EBookClientCursor *cursor,
1838                         const gchar *sexp,
1839                         guint *new_total,
1840                         guint *new_position,
1841                         GCancellable *cancellable,
1842                         GError **error)
1843 {
1844 	EBookClientCursorPrivate *priv;
1845 	gchar *utf8_sexp;
1846 	GError *local_error = NULL;
1847 
1848 	priv = cursor->priv;
1849 
1850 	if (priv->direct_cursor) {
1851 
1852 		if (!e_data_book_cursor_set_sexp (priv->direct_cursor,
1853 						  sexp, cancellable, error))
1854 			return FALSE;
1855 
1856 		*new_total = e_data_book_cursor_get_total (priv->direct_cursor);
1857 		*new_position = e_data_book_cursor_get_position (priv->direct_cursor);
1858 
1859 		return TRUE;
1860 	}
1861 
1862 	utf8_sexp = e_util_utf8_make_valid (sexp);
1863 	e_dbus_address_book_cursor_call_set_query_sync (
1864 		priv->dbus_proxy,
1865 		utf8_sexp,
1866 		new_total,
1867 		new_position,
1868 		cancellable,
1869 		&local_error);
1870 	g_free (utf8_sexp);
1871 
1872 	if (local_error != NULL) {
1873 		g_dbus_error_strip_remote_error (local_error);
1874 		g_propagate_error (error, local_error);
1875 		return FALSE;
1876 	}
1877 
1878 	return TRUE;
1879 }
1880 
1881 static void
set_sexp_thread(GSimpleAsyncResult * simple,GObject * source_object,GCancellable * cancellable)1882 set_sexp_thread (GSimpleAsyncResult *simple,
1883                  GObject *source_object,
1884                  GCancellable *cancellable)
1885 {
1886 	SetSexpContext *context;
1887 	GError *local_error = NULL;
1888 
1889 	context = g_simple_async_result_get_op_res_gpointer (simple);
1890 	set_sexp_sync_internal (
1891 		E_BOOK_CLIENT_CURSOR (source_object),
1892 		context->sexp,
1893 		&context->new_total,
1894 		&context->new_position,
1895 		cancellable,
1896 		&local_error);
1897 
1898 	if (local_error != NULL)
1899 		g_simple_async_result_take_error (simple, local_error);
1900 }
1901 
1902 static StepContext *
step_context_new(const gchar * revision,EBookCursorStepFlags flags,EBookCursorOrigin origin,gint count)1903 step_context_new (const gchar *revision,
1904                   EBookCursorStepFlags flags,
1905                   EBookCursorOrigin origin,
1906                   gint count)
1907 {
1908 	StepContext *context = g_slice_new0 (StepContext);
1909 
1910 	context->revision = g_strdup (revision);
1911 	context->flags = flags;
1912 	context->origin = origin;
1913 	context->count = count;
1914 	context->n_results = 0;
1915 
1916 	return context;
1917 }
1918 
1919 static void
step_context_free(StepContext * context)1920 step_context_free (StepContext *context)
1921 {
1922 	if (context) {
1923 		g_free (context->revision);
1924 		g_slist_free_full (context->contacts, g_object_unref);
1925 		g_slice_free (StepContext, context);
1926 	}
1927 }
1928 
1929 static gint
step_sync_internal(EBookClientCursor * cursor,const gchar * revision,EBookCursorStepFlags flags,EBookCursorOrigin origin,gint count,GSList ** out_contacts,guint * new_total,guint * new_position,GCancellable * cancellable,GError ** error)1930 step_sync_internal (EBookClientCursor *cursor,
1931                     const gchar *revision,
1932                     EBookCursorStepFlags flags,
1933                     EBookCursorOrigin origin,
1934                     gint count,
1935                     GSList **out_contacts,
1936                     guint *new_total,
1937                     guint *new_position,
1938                     GCancellable *cancellable,
1939                     GError **error)
1940 {
1941 	EBookClientCursorPrivate *priv;
1942 	GError *local_error = NULL;
1943 	gchar **vcards = NULL;
1944 	gint n_results = -1;
1945 
1946 	priv = cursor->priv;
1947 
1948 	if (priv->direct_cursor) {
1949 		GSList *results = NULL, *l;
1950 		GSList *contacts = NULL;
1951 
1952 		n_results = e_data_book_cursor_step (
1953 			priv->direct_cursor,
1954 			revision,
1955 			flags,
1956 			origin,
1957 			count,
1958 			&results,
1959 			cancellable,
1960 			error);
1961 		if (n_results < 0)
1962 			return n_results;
1963 
1964 		for (l = results; l; l = l->next) {
1965 			gchar *vcard = l->data;
1966 			EContact *contact = e_contact_new_from_vcard (vcard);
1967 
1968 			if (contact)
1969 				contacts = g_slist_prepend (contacts, contact);
1970 		}
1971 
1972 		g_slist_free_full (results, (GDestroyNotify) g_free);
1973 
1974 		if (out_contacts)
1975 			*out_contacts = g_slist_reverse (contacts);
1976 		else
1977 			g_slist_free_full (contacts, g_object_unref);
1978 
1979 		*new_total = e_data_book_cursor_get_total (priv->direct_cursor);
1980 		*new_position = e_data_book_cursor_get_position (priv->direct_cursor);
1981 
1982 		return n_results;
1983 	}
1984 
1985 	e_dbus_address_book_cursor_call_step_sync (
1986 		priv->dbus_proxy,
1987 		revision,
1988 		flags,
1989 		origin,
1990 		count,
1991 		&n_results,
1992 		&vcards,
1993 		new_total,
1994 		new_position,
1995 		cancellable,
1996 		&local_error);
1997 
1998 	if (local_error != NULL) {
1999 		g_dbus_error_strip_remote_error (local_error);
2000 		g_propagate_error (error, local_error);
2001 		return -1;
2002 	}
2003 
2004 	if (vcards != NULL) {
2005 		EContact *contact;
2006 		GSList *tmp = NULL;
2007 		gint i;
2008 
2009 		for (i = 0; vcards[i] != NULL; i++) {
2010 			contact = e_contact_new_from_vcard (vcards[i]);
2011 			tmp = g_slist_prepend (tmp, contact);
2012 		}
2013 
2014 		if (out_contacts)
2015 			*out_contacts = g_slist_reverse (tmp);
2016 		else
2017 			g_slist_free_full (tmp, g_object_unref);
2018 
2019 		g_strfreev (vcards);
2020 	}
2021 
2022 	return n_results;
2023 }
2024 
2025 static void
step_thread(GSimpleAsyncResult * simple,GObject * source_object,GCancellable * cancellable)2026 step_thread (GSimpleAsyncResult *simple,
2027              GObject *source_object,
2028              GCancellable *cancellable)
2029 {
2030 	StepContext *context;
2031 	GError *local_error = NULL;
2032 
2033 	context = g_simple_async_result_get_op_res_gpointer (simple);
2034 
2035 	context->n_results = step_sync_internal (
2036 		E_BOOK_CLIENT_CURSOR (source_object),
2037 		context->revision,
2038 		context->flags,
2039 		context->origin,
2040 		context->count,
2041 		&(context->contacts),
2042 		&context->new_total,
2043 		&context->new_position,
2044 		cancellable, &local_error);
2045 
2046 	if (local_error != NULL)
2047 		g_simple_async_result_take_error (simple, local_error);
2048 }
2049 
2050 static AlphabetIndexContext *
alphabet_index_context_new(gint index,const gchar * locale)2051 alphabet_index_context_new (gint index,
2052                             const gchar *locale)
2053 {
2054 	AlphabetIndexContext *context = g_slice_new0 (AlphabetIndexContext);
2055 
2056 	context->index = index;
2057 	context->locale = g_strdup (locale);
2058 
2059 	return context;
2060 }
2061 
2062 static void
alphabet_index_context_free(AlphabetIndexContext * context)2063 alphabet_index_context_free (AlphabetIndexContext *context)
2064 {
2065 	if (context) {
2066 		g_free (context->locale);
2067 		g_slice_free (AlphabetIndexContext, context);
2068 	}
2069 }
2070 
2071 static gboolean
set_alphabetic_index_sync_internal(EBookClientCursor * cursor,gint index,const gchar * locale,guint * new_total,guint * new_position,GCancellable * cancellable,GError ** error)2072 set_alphabetic_index_sync_internal (EBookClientCursor *cursor,
2073                                     gint index,
2074                                     const gchar *locale,
2075                                     guint *new_total,
2076                                     guint *new_position,
2077                                     GCancellable *cancellable,
2078                                     GError **error)
2079 {
2080 	EBookClientCursorPrivate *priv;
2081 	GError *local_error = NULL;
2082 
2083 	priv = cursor->priv;
2084 
2085 	if (priv->direct_cursor) {
2086 
2087 		if (!e_data_book_cursor_set_alphabetic_index (priv->direct_cursor,
2088 							      index,
2089 							      locale,
2090 							      cancellable,
2091 							      error))
2092 			return FALSE;
2093 
2094 		*new_total = e_data_book_cursor_get_total (priv->direct_cursor);
2095 		*new_position = e_data_book_cursor_get_position (priv->direct_cursor);
2096 
2097 		return TRUE;
2098 	}
2099 
2100 	e_dbus_address_book_cursor_call_set_alphabetic_index_sync (
2101 		cursor->priv->dbus_proxy,
2102 		index, locale,
2103 		new_total,
2104 		new_position,
2105 		cancellable,
2106 		&local_error);
2107 
2108 	if (local_error != NULL) {
2109 		g_dbus_error_strip_remote_error (local_error);
2110 		g_propagate_error (error, local_error);
2111 		return FALSE;
2112 	}
2113 
2114 	return TRUE;
2115 }
2116 
2117 static void
alphabet_index_thread(GSimpleAsyncResult * simple,GObject * source_object,GCancellable * cancellable)2118 alphabet_index_thread (GSimpleAsyncResult *simple,
2119                        GObject *source_object,
2120                        GCancellable *cancellable)
2121 {
2122 	AlphabetIndexContext *context;
2123 	GError *local_error = NULL;
2124 
2125 	context = g_simple_async_result_get_op_res_gpointer (simple);
2126 
2127 	set_alphabetic_index_sync_internal (
2128 		E_BOOK_CLIENT_CURSOR (source_object),
2129 		context->index,
2130 		context->locale,
2131 		&context->new_total,
2132 		&context->new_position,
2133 		cancellable,
2134 		&local_error);
2135 
2136 	if (local_error != NULL)
2137 		g_simple_async_result_take_error (simple, local_error);
2138 }
2139 
2140 /****************************************************
2141  *                         API                      *
2142  ****************************************************/
2143 /**
2144  * e_book_client_cursor_ref_client:
2145  * @cursor: an #EBookClientCursor
2146  *
2147  * Returns the #EBookClientCursor:client associated with @cursor.
2148  *
2149  * The returned #EBookClient is referenced because the cursor
2150  * does not keep a strong reference to the client.
2151  *
2152  * Unreference the #EBookClient with g_object_unref() when finished with it.
2153  *
2154  * Returns: (transfer full) (type EBookClient): an #EBookClient
2155  *
2156  * Since: 3.12
2157  */
2158 EBookClient *
e_book_client_cursor_ref_client(EBookClientCursor * cursor)2159 e_book_client_cursor_ref_client (EBookClientCursor *cursor)
2160 {
2161 	g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), NULL);
2162 
2163 	return g_object_ref (cursor->priv->client);
2164 }
2165 
2166 /**
2167  * e_book_client_cursor_get_alphabet:
2168  * @cursor: an #EBookClientCursor
2169  * @n_labels: (out) (optional): The number of labels in the active alphabet
2170  * @underflow: (optional) (out): The underflow index, for any words which sort below the active alphabet
2171  * @inflow: (optional) (out): The inflow index, for any words which sort between the active alphabets (if there is more than one)
2172  * @overflow: (optional) (out): The overflow index, for any words which sort above the active alphabet
2173  *
2174  * Fetches the array of displayable labels for the <link linkend="cursor-alphabet">active alphabet</link>.
2175  *
2176  * The active alphabet is based on the current locale configuration of the
2177  * addressbook, and can be a different alphabet for locales requiring non-Latin
2178  * language scripts. These UTF-8 labels are appropriate to display in a user
2179  * interface to represent the alphabetic position of the cursor in the user's
2180  * native alphabet.
2181  *
2182  * The @underflow, @inflow and @overflow parameters allow one to observe which
2183  * indexes Evolution Data Server is using to store words which sort outside
2184  * of the alphabet, for instance words from foreign language scripts and
2185  * words which start with numeric characters, or other types of character.
2186  *
2187  * While the @underflow and @overflow are for words which sort below or
2188  * above the active alphabets, the @inflow index is for words which sort
2189  * in between multiple concurrently active alphabets. The active alphabet
2190  * array might contain more than one alphabet for locales where it is
2191  * very common or expected to have names in Latin script as well as names
2192  * in another script.
2193  *
2194  * Returns: (array zero-terminated=1) (element-type utf8) (transfer none):
2195  *   The array of displayable labels for each index in the active alphabet.
2196  *
2197  * Since: 3.12
2198  */
2199 const gchar * const *
e_book_client_cursor_get_alphabet(EBookClientCursor * cursor,gint * n_labels,gint * underflow,gint * inflow,gint * overflow)2200 e_book_client_cursor_get_alphabet (EBookClientCursor *cursor,
2201                                    gint *n_labels,
2202                                    gint *underflow,
2203                                    gint *inflow,
2204                                    gint *overflow)
2205 {
2206 	EBookClientCursorPrivate *priv;
2207 
2208 	g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), NULL);
2209 
2210 	priv = cursor->priv;
2211 
2212 	return e_collator_get_index_labels (
2213 		priv->collator,
2214 		n_labels,
2215 		underflow,
2216 		inflow,
2217 		overflow);
2218 }
2219 
2220 /**
2221  * e_book_client_cursor_get_total:
2222  * @cursor: an #EBookClientCursor
2223  *
2224  * Fetches the total number of contacts in the addressbook
2225  * which match @cursor's query
2226  *
2227  * Returns: The total number of contacts matching @cursor's query
2228  *
2229  * Since: 3.12
2230  */
2231 gint
e_book_client_cursor_get_total(EBookClientCursor * cursor)2232 e_book_client_cursor_get_total (EBookClientCursor *cursor)
2233 {
2234 	g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), -1);
2235 
2236 	return cursor->priv->total;
2237 }
2238 
2239 /**
2240  * e_book_client_cursor_get_position:
2241  * @cursor: an #EBookClientCursor
2242  *
2243  * Fetches the number of contacts leading up to the current
2244  * cursor position, inclusive of the current cursor position.
2245  *
2246  * The position value can be anywhere from 0 to the total
2247  * number of contacts plus one. A value of 0 indicates
2248  * that the cursor is positioned before the contact list in
2249  * the symbolic %E_BOOK_CURSOR_ORIGIN_BEGIN state. If
2250  * the position is greater than the total, as returned by
2251  * e_book_client_cursor_get_total(), then the cursor is positioned
2252  * after the last contact in the symbolic %E_BOOK_CURSOR_ORIGIN_END position.
2253  *
2254  * Returns: The current cursor position
2255  *
2256  * Since: 3.12
2257  */
2258 gint
e_book_client_cursor_get_position(EBookClientCursor * cursor)2259 e_book_client_cursor_get_position (EBookClientCursor *cursor)
2260 {
2261 	g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), -1);
2262 
2263 	return cursor->priv->position;
2264 }
2265 
2266 /**
2267  * e_book_client_cursor_set_sexp:
2268  * @cursor: an #EBookClientCursor
2269  * @sexp: the new search expression for @cursor
2270  * @cancellable: a #GCancellable to optionally cancel this operation while in progress
2271  * @callback: callback to call when a result is ready
2272  * @user_data: user data for the @callback
2273  *
2274  * Sets the <link linkend="cursor-search">Search Expression</link> for the cursor.
2275  *
2276  * See: e_book_client_cursor_set_sexp_sync().
2277  *
2278  * This asynchronous call is completed with a call to
2279  * e_book_client_cursor_set_sexp_finish() from the specified @callback.
2280  *
2281  * Since: 3.12
2282  */
2283 void
e_book_client_cursor_set_sexp(EBookClientCursor * cursor,const gchar * sexp,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2284 e_book_client_cursor_set_sexp (EBookClientCursor *cursor,
2285                                const gchar *sexp,
2286                                GCancellable *cancellable,
2287                                GAsyncReadyCallback callback,
2288                                gpointer user_data)
2289 {
2290 
2291 	GSimpleAsyncResult *simple;
2292 	SetSexpContext *context;
2293 
2294 	g_return_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor));
2295 	g_return_if_fail (callback != NULL);
2296 
2297 	context = set_sexp_context_new (sexp);
2298 	simple = g_simple_async_result_new (
2299 		G_OBJECT (cursor),
2300 		callback, user_data,
2301 		e_book_client_cursor_set_sexp);
2302 
2303 	g_simple_async_result_set_check_cancellable (simple, cancellable);
2304 	g_simple_async_result_set_op_res_gpointer (
2305 		simple, context,
2306 		(GDestroyNotify) set_sexp_context_free);
2307 
2308 	g_simple_async_result_run_in_thread (
2309 		simple, set_sexp_thread,
2310 		G_PRIORITY_DEFAULT, cancellable);
2311 
2312 	g_object_unref (simple);
2313 }
2314 
2315 /**
2316  * e_book_client_cursor_set_sexp_finish:
2317  * @cursor: an #EBookClientCursor
2318  * @result: a #GAsyncResult
2319  * @error: return location for a #GError, or %NULL
2320  *
2321  * Completes an asynchronous call initiated by e_book_client_cursor_set_sexp(), reporting
2322  * whether the new search expression was accepted.
2323  *
2324  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
2325  *
2326  * Since: 3.12
2327  */
2328 gboolean
e_book_client_cursor_set_sexp_finish(EBookClientCursor * cursor,GAsyncResult * result,GError ** error)2329 e_book_client_cursor_set_sexp_finish (EBookClientCursor *cursor,
2330                                       GAsyncResult *result,
2331                                       GError **error)
2332 {
2333 	GSimpleAsyncResult *simple;
2334 	SetSexpContext *context;
2335 
2336 	g_return_val_if_fail (
2337 		g_simple_async_result_is_valid (
2338 		result, G_OBJECT (cursor),
2339 		e_book_client_cursor_set_sexp), FALSE);
2340 
2341 	simple = G_SIMPLE_ASYNC_RESULT (result);
2342 	context = g_simple_async_result_get_op_res_gpointer (simple);
2343 
2344 	if (g_simple_async_result_propagate_error (simple, error))
2345 		return FALSE;
2346 
2347 	/* If we are in the thread where the cursor was created,
2348 	 * then synchronize the new total & position right away
2349 	 */
2350 	if (book_client_cursor_context_is_current (cursor)) {
2351 		g_object_freeze_notify (G_OBJECT (cursor));
2352 		book_client_cursor_set_total (cursor, context->new_total);
2353 		book_client_cursor_set_position (cursor, context->new_position);
2354 		g_object_thaw_notify (G_OBJECT (cursor));
2355 	}
2356 
2357 	return TRUE;
2358 }
2359 
2360 /**
2361  * e_book_client_cursor_set_sexp_sync:
2362  * @cursor: an #EBookClientCursor
2363  * @sexp: the new search expression for @cursor
2364  * @cancellable: a #GCancellable to optionally cancel this operation while in progress
2365  * @error: return location for a #GError, or %NULL
2366  *
2367  * Sets the <link linkend="cursor-search">Search Expression</link> for the cursor.
2368  *
2369  * A side effect of setting the search expression is that the
2370  * <link linkend="cursor-pos-total">position and total</link>
2371  * properties will be updated.
2372  *
2373  * If this method is called from the same thread context in which
2374  * the cursor was created, then the updates to the #EBookClientCursor:position
2375  * and #EBookClientCursor:total properties are guaranteed to be delivered
2376  * synchronously upon successful completion of setting the search expression.
2377  * Otherwise, notifications will be delivered asynchronously in the cursor's
2378  * original thread context.
2379  *
2380  * If the backend does not support the given search expression,
2381  * an %E_CLIENT_ERROR_INVALID_QUERY error will be set.
2382  *
2383  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
2384  *
2385  * Since: 3.12
2386  */
2387 gboolean
e_book_client_cursor_set_sexp_sync(EBookClientCursor * cursor,const gchar * sexp,GCancellable * cancellable,GError ** error)2388 e_book_client_cursor_set_sexp_sync (EBookClientCursor *cursor,
2389                                     const gchar *sexp,
2390                                     GCancellable *cancellable,
2391                                     GError **error)
2392 {
2393 	gboolean success;
2394 	guint new_total = 0, new_position = 0;
2395 
2396 	g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), FALSE);
2397 
2398 	success = set_sexp_sync_internal (
2399 		cursor,
2400 		sexp,
2401 		&new_total,
2402 		&new_position,
2403 		cancellable,
2404 		error);
2405 
2406 	/* If we are in the thread where the cursor was created,
2407 	 * then synchronize the new total & position right away
2408 	 */
2409 	if (success && book_client_cursor_context_is_current (cursor)) {
2410 		g_object_freeze_notify (G_OBJECT (cursor));
2411 		book_client_cursor_set_total (cursor, new_total);
2412 		book_client_cursor_set_position (cursor, new_position);
2413 		g_object_thaw_notify (G_OBJECT (cursor));
2414 	}
2415 
2416 	return success;
2417 }
2418 
2419 /**
2420  * e_book_client_cursor_step:
2421  * @cursor: an #EBookClientCursor
2422  * @flags: The #EBookCursorStepFlags for this step
2423  * @origin: The #EBookCursorOrigin from whence to step
2424  * @count: a positive or negative amount of contacts to try and fetch
2425  * @cancellable: a #GCancellable to optionally cancel this operation while in progress
2426  * @callback: callback to call when a result is ready
2427  * @user_data: user data for the @callback
2428  *
2429  * <link linkend="cursor-iteration">Steps the cursor through the results</link> by
2430  * a maximum of @count and fetch the results traversed.
2431  *
2432  * See: e_book_client_cursor_step_sync().
2433  *
2434  * This asynchronous call is completed with a call to
2435  * e_book_client_cursor_step_finish() from the specified @callback.
2436  *
2437  * Since: 3.12
2438  */
2439 void
e_book_client_cursor_step(EBookClientCursor * cursor,EBookCursorStepFlags flags,EBookCursorOrigin origin,gint count,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2440 e_book_client_cursor_step (EBookClientCursor *cursor,
2441                            EBookCursorStepFlags flags,
2442                            EBookCursorOrigin origin,
2443                            gint count,
2444                            GCancellable *cancellable,
2445                            GAsyncReadyCallback callback,
2446                            gpointer user_data)
2447 {
2448 	GSimpleAsyncResult *simple;
2449 	StepContext *context;
2450 
2451 	g_return_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor));
2452 	g_return_if_fail (callback != NULL);
2453 
2454 	context = step_context_new (
2455 		cursor->priv->revision,
2456 		flags, origin, count);
2457 	simple = g_simple_async_result_new (
2458 		G_OBJECT (cursor),
2459 		callback, user_data,
2460 		e_book_client_cursor_step);
2461 
2462 	g_simple_async_result_set_check_cancellable (simple, cancellable);
2463 	g_simple_async_result_set_op_res_gpointer (
2464 		simple, context,
2465 		(GDestroyNotify) step_context_free);
2466 
2467 	g_simple_async_result_run_in_thread (
2468 		simple, step_thread,
2469 		G_PRIORITY_DEFAULT, cancellable);
2470 
2471 	g_object_unref (simple);
2472 }
2473 
2474 /**
2475  * e_book_client_cursor_step_finish:
2476  * @cursor: an #EBookClientCursor
2477  * @result: a #GAsyncResult
2478  * @out_contacts: (element-type EContact) (out) (transfer full) (optional): return location for a #GSList of #EContact
2479  * @error: return location for a #GError, or %NULL
2480  *
2481  * Completes an asynchronous call initiated by e_book_client_cursor_step(), fetching
2482  * any contacts which might have been returned by the call.
2483  *
2484  * Returns: The number of contacts traversed if successful, otherwise -1 is
2485  * returned and @error is set.
2486  *
2487  * Since: 3.12
2488  */
2489 gint
e_book_client_cursor_step_finish(EBookClientCursor * cursor,GAsyncResult * result,GSList ** out_contacts,GError ** error)2490 e_book_client_cursor_step_finish (EBookClientCursor *cursor,
2491                                   GAsyncResult *result,
2492                                   GSList **out_contacts,
2493                                   GError **error)
2494 {
2495 	GSimpleAsyncResult *simple;
2496 	StepContext *context;
2497 
2498 	g_return_val_if_fail (
2499 		g_simple_async_result_is_valid (
2500 		result, G_OBJECT (cursor),
2501 		e_book_client_cursor_step), FALSE);
2502 
2503 	simple = G_SIMPLE_ASYNC_RESULT (result);
2504 	context = g_simple_async_result_get_op_res_gpointer (simple);
2505 
2506 	if (g_simple_async_result_propagate_error (simple, error))
2507 		return -1;
2508 
2509 	if (out_contacts != NULL) {
2510 		*out_contacts = context->contacts;
2511 		context->contacts = NULL;
2512 	}
2513 
2514 	/* If we are in the thread where the cursor was created,
2515 	 * then synchronize the new total & position right away
2516 	 */
2517 	if (book_client_cursor_context_is_current (cursor)) {
2518 		g_object_freeze_notify (G_OBJECT (cursor));
2519 		book_client_cursor_set_total (cursor, context->new_total);
2520 		book_client_cursor_set_position (cursor, context->new_position);
2521 		g_object_thaw_notify (G_OBJECT (cursor));
2522 	}
2523 
2524 	return context->n_results;
2525 }
2526 
2527 /**
2528  * e_book_client_cursor_step_sync:
2529  * @cursor: an #EBookClientCursor
2530  * @flags: The #EBookCursorStepFlags for this step
2531  * @origin: The #EBookCursorOrigin from whence to step
2532  * @count: a positive or negative amount of contacts to try and fetch
2533  * @out_contacts: (element-type EContact) (out) (transfer full) (optional): return location for a #GSList of #EContact
2534  * @cancellable: a #GCancellable to optionally cancel this operation while in progress
2535  * @error: return location for a #GError, or %NULL
2536  *
2537  * <link linkend="cursor-iteration">Steps the cursor through the results</link> by
2538  * a maximum of @count and fetch the results traversed.
2539  *
2540  * If @count is negative, then the cursor will move backwards.
2541  *
2542  * If @cursor reaches the beginning or end of the query results, then the
2543  * returned list might not contain the amount of desired contacts, or might
2544  * return no results if the cursor currently points to the last contact.
2545  * Reaching the end of the list is not considered an error condition. Attempts
2546  * to step beyond the end of the list after having reached the end of the list
2547  * will however trigger an %E_CLIENT_ERROR_QUERY_REFUSED error.
2548  *
2549  * If %E_BOOK_CURSOR_STEP_FETCH is specified in @flags, a pointer to
2550  * a %NULL #GSList pointer should be provided for the @results parameter.
2551  *
2552  * If %E_BOOK_CURSOR_STEP_MOVE is specified in @flags, then the cursor's
2553  * state will be modified and the <link linkend="cursor-pos-total">position</link>
2554  * property will be updated as a result.
2555  *
2556  * If this method is called from the same thread context in which
2557  * the cursor was created, then the updates to the #EBookClientCursor:position
2558  * property are guaranteed to be delivered synchronously upon successful completion
2559  * of moving the cursor. Otherwise, notifications will be delivered asynchronously
2560  * in the cursor's original thread context.
2561  *
2562  * If this method completes with an %E_CLIENT_ERROR_OUT_OF_SYNC error, it is an
2563  * indication that the addressbook has been modified and it would be unsafe to
2564  * move the cursor at this time. Any %E_CLIENT_ERROR_OUT_OF_SYNC error is guaranteed
2565  * to be followed by an #EBookClientCursor::refresh signal at which point any content
2566  * should be reloaded.
2567  *
2568  * Returns: The number of contacts traversed if successful, otherwise -1 is
2569  * returned and @error is set.
2570  *
2571  * Since: 3.12
2572  */
2573 gint
e_book_client_cursor_step_sync(EBookClientCursor * cursor,EBookCursorStepFlags flags,EBookCursorOrigin origin,gint count,GSList ** out_contacts,GCancellable * cancellable,GError ** error)2574 e_book_client_cursor_step_sync (EBookClientCursor *cursor,
2575                                 EBookCursorStepFlags flags,
2576                                 EBookCursorOrigin origin,
2577                                 gint count,
2578                                 GSList **out_contacts,
2579                                 GCancellable *cancellable,
2580                                 GError **error)
2581 {
2582 	guint new_total = 0, new_position = 0;
2583 	gint retval;
2584 
2585 	g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), FALSE);
2586 
2587 	retval = step_sync_internal (
2588 		cursor, cursor->priv->revision,
2589 		flags, origin, count,
2590 		out_contacts, &new_total, &new_position,
2591 		cancellable, error);
2592 
2593 	/* If we are in the thread where the cursor was created,
2594 	 * then synchronize the new total & position right away
2595 	 */
2596 	if (retval >= 0 && book_client_cursor_context_is_current (cursor)) {
2597 		g_object_freeze_notify (G_OBJECT (cursor));
2598 		book_client_cursor_set_total (cursor, new_total);
2599 		book_client_cursor_set_position (cursor, new_position);
2600 		g_object_thaw_notify (G_OBJECT (cursor));
2601 	}
2602 
2603 	return retval;
2604 }
2605 
2606 /**
2607  * e_book_client_cursor_set_alphabetic_index:
2608  * @cursor: an #EBookClientCursor
2609  * @index: the alphabetic index
2610  * @cancellable: a #GCancellable to optionally cancel this operation while in progress
2611  * @callback: callback to call when a result is ready
2612  * @user_data: user data for the @callback
2613  *
2614  * Sets the current cursor position to point to an <link linkend="cursor-alphabet">Alphabetic Index</link>.
2615  *
2616  * See: e_book_client_cursor_set_alphabetic_index_sync().
2617  *
2618  * This asynchronous call is completed with a call to
2619  * e_book_client_cursor_set_alphabetic_index_finish() from the specified @callback.
2620  *
2621  * Since: 3.12
2622  */
2623 void
e_book_client_cursor_set_alphabetic_index(EBookClientCursor * cursor,gint index,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2624 e_book_client_cursor_set_alphabetic_index (EBookClientCursor *cursor,
2625                                            gint index,
2626                                            GCancellable *cancellable,
2627                                            GAsyncReadyCallback callback,
2628                                            gpointer user_data)
2629 {
2630 	GSimpleAsyncResult *simple;
2631 	AlphabetIndexContext *context;
2632 
2633 	g_return_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor));
2634 	g_return_if_fail (index >= 0 && index < cursor->priv->n_labels);
2635 	g_return_if_fail (callback != NULL);
2636 
2637 	context = alphabet_index_context_new (index, cursor->priv->locale);
2638 	simple = g_simple_async_result_new (
2639 		G_OBJECT (cursor),
2640 		callback, user_data,
2641 		e_book_client_cursor_set_alphabetic_index);
2642 
2643 	g_simple_async_result_set_check_cancellable (simple, cancellable);
2644 	g_simple_async_result_set_op_res_gpointer (
2645 		simple, context,
2646 		(GDestroyNotify) alphabet_index_context_free);
2647 
2648 	g_simple_async_result_run_in_thread (
2649 		simple, alphabet_index_thread,
2650 		G_PRIORITY_DEFAULT, cancellable);
2651 
2652 	g_object_unref (simple);
2653 }
2654 
2655 /**
2656  * e_book_client_cursor_set_alphabetic_index_finish:
2657  * @cursor: an #EBookClientCursor
2658  * @result: a #GAsyncResult
2659  * @error: return location for a #GError, or %NULL
2660  *
2661  * Completes an asynchronous call initiated by e_book_client_cursor_set_alphabetic_index().
2662  *
2663  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
2664  *
2665  * Since: 3.12
2666  */
2667 gboolean
e_book_client_cursor_set_alphabetic_index_finish(EBookClientCursor * cursor,GAsyncResult * result,GError ** error)2668 e_book_client_cursor_set_alphabetic_index_finish (EBookClientCursor *cursor,
2669                                                   GAsyncResult *result,
2670                                                   GError **error)
2671 {
2672 	GSimpleAsyncResult *simple;
2673 	AlphabetIndexContext *context;
2674 
2675 	g_return_val_if_fail (
2676 		g_simple_async_result_is_valid (
2677 		result, G_OBJECT (cursor),
2678 		e_book_client_cursor_set_alphabetic_index), FALSE);
2679 
2680 	simple = G_SIMPLE_ASYNC_RESULT (result);
2681 	context = g_simple_async_result_get_op_res_gpointer (simple);
2682 
2683 	if (g_simple_async_result_propagate_error (simple, error))
2684 		return FALSE;
2685 
2686 	/* If we are in the thread where the cursor was created,
2687 	 * then synchronize the new total & position right away
2688 	 */
2689 	if (book_client_cursor_context_is_current (cursor)) {
2690 		g_object_freeze_notify (G_OBJECT (cursor));
2691 		book_client_cursor_set_total (cursor, context->new_total);
2692 		book_client_cursor_set_position (cursor, context->new_position);
2693 		g_object_thaw_notify (G_OBJECT (cursor));
2694 	}
2695 
2696 	return TRUE;
2697 }
2698 
2699 /**
2700  * e_book_client_cursor_set_alphabetic_index_sync:
2701  * @cursor: an #EBookClientCursor
2702  * @index: the alphabetic index
2703  * @cancellable: a #GCancellable to optionally cancel this operation while in progress
2704  * @error: return location for a #GError, or %NULL
2705  *
2706  * Sets the cursor to point to an <link linkend="cursor-alphabet">Alphabetic Index</link>.
2707  *
2708  * After setting the alphabetic index, for example the
2709  * index for letter 'E', then further calls to e_book_client_cursor_step()
2710  * will return results starting with the letter 'E' (or results starting
2711  * with the last result in 'D' when navigating through cursor results
2712  * in reverse).
2713  *
2714  * The passed index must be a valid index into the alphabet parameters
2715  * returned by e_book_client_cursor_get_alphabet().
2716  *
2717  * If this method is called from the same thread context in which
2718  * the cursor was created, then the updates to the #EBookClientCursor:position
2719  * property are guaranteed to be delivered synchronously upon successful completion
2720  * of moving the cursor. Otherwise, notifications will be delivered asynchronously
2721  * in the cursor's original thread context.
2722  *
2723  * If this method completes with an %E_CLIENT_ERROR_OUT_OF_SYNC error, it is an
2724  * indication that the addressbook has been set into a new locale and it would be
2725  * unsafe to set the alphabetic index at this time. If you receive an out of sync
2726  * error from this method, then you should wait until an #EBookClientCursor:alphabet
2727  * property change notification is delivered and then proceed to load the new
2728  * alphabet before trying to set any alphabetic index.
2729  *
2730  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
2731  *
2732  * Since: 3.12
2733  */
2734 gboolean
e_book_client_cursor_set_alphabetic_index_sync(EBookClientCursor * cursor,gint index,GCancellable * cancellable,GError ** error)2735 e_book_client_cursor_set_alphabetic_index_sync (EBookClientCursor *cursor,
2736                                                 gint index,
2737                                                 GCancellable *cancellable,
2738                                                 GError **error)
2739 {
2740 	guint new_total = 0, new_position = 0;
2741 	gboolean success;
2742 
2743 	g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), FALSE);
2744 	g_return_val_if_fail (index >= 0 && index < cursor->priv->n_labels, FALSE);
2745 
2746 	success = set_alphabetic_index_sync_internal (
2747 		cursor, index, cursor->priv->locale,
2748 		&new_total, &new_position, cancellable, error);
2749 
2750 	/* If we are in the thread where the cursor was created,
2751 	 * then synchronize the new total & position right away
2752 	 */
2753 	if (success && book_client_cursor_context_is_current (cursor)) {
2754 		g_object_freeze_notify (G_OBJECT (cursor));
2755 		book_client_cursor_set_total (cursor, new_total);
2756 		book_client_cursor_set_position (cursor, new_position);
2757 		g_object_thaw_notify (G_OBJECT (cursor));
2758 	}
2759 
2760 	return success;
2761 }
2762 
2763 /**
2764  * e_book_client_cursor_get_contact_alphabetic_index:
2765  * @cursor: an #EBookClientCursor
2766  * @contact: the #EContact to check
2767  *
2768  * Checks which alphabetic index @contact would be sorted
2769  * into according to @cursor.
2770  *
2771  * So long as the active #EBookClientCursor:alphabet does
2772  * not change, the returned index will be a valid position
2773  * in the array of labels returned by e_book_client_cursor_get_alphabet().
2774  *
2775  * If the index returned by this function is needed for
2776  * any extended period of time, it should be recalculated
2777  * whenever the #EBookClientCursor:alphabet changes.
2778  *
2779  * Returns: The alphabetic index of @contact in @cursor.
2780  *
2781  * Since: 3.12
2782  */
2783 gint
e_book_client_cursor_get_contact_alphabetic_index(EBookClientCursor * cursor,EContact * contact)2784 e_book_client_cursor_get_contact_alphabetic_index (EBookClientCursor *cursor,
2785                                                    EContact *contact)
2786 {
2787 	EBookClientCursorPrivate *priv;
2788 	EContactField field;
2789 	const gchar *value;
2790 	gint index = 0;
2791 
2792 	g_return_val_if_fail (E_IS_BOOK_CLIENT_CURSOR (cursor), 0);
2793 	g_return_val_if_fail (E_IS_CONTACT (contact), 0);
2794 
2795 	priv = cursor->priv;
2796 
2797 	if (priv->collator && priv->sort_fields) {
2798 
2799 		/* Find the alphabetic index according to the primary
2800 		 * cursor sort key
2801 		 */
2802 		field = e_contact_field_id (priv->sort_fields[0]);
2803 		value = e_contact_get_const (contact, field);
2804 		index = e_collator_get_index (priv->collator, value ? value : "");
2805 	}
2806 
2807 	return index;
2808 }
2809