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 (¬ification->cursor, cursor);
1571
1572 g_value_init (¬ification->value, G_TYPE_STRING);
1573 g_value_set_string (¬ification->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 (¬ification->cursor, cursor);
1587
1588 g_value_init (¬ification->value, G_TYPE_INT);
1589 g_value_set_int (¬ification->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 (¬ification->cursor);
1599 g_value_unset (¬ification->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