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-data-book-cursor
22 * @include: libedata-book/libedata-book.h
23 * @short_description: The abstract cursor API
24 *
25 * The #EDataBookCursor API is the high level cursor API on the
26 * addressbook server, it can respond to client requests directly
27 * when opened in direct read access mode, otherwise it will implement
28 * the org.gnome.evolution.dataserver.AddressBookCursor D-Bus interface
29 * when instantiated by the addressbook server.
30 *
31 * <note><para>EDataBookCursor is an implementation detail for backends who wish
32 * to implement cursors. If you need to use the client API to iterate over contacts
33 * stored in Evolution Data Server; you should be using #EBookClientCursor instead.
34 * </para></note>
35 *
36 * <refsect2 id="cursor-implementing">
37 * <title>Implementing Cursors</title>
38 * <para>
39 * In order for an addressbook backend to implement cursors, it must
40 * first be locale aware, persist a current locale setting and implement
41 * the #EBookBackendClass.set_locale() and #EBookBackendClass.dup_locale()
42 * methods.
43 * </para>
44 * <para>
45 * The backend indicates that it supports cursors by implementing the
46 * #EBookBackendClass.create_cursor() and returning an #EDataBookCursor,
47 * any backend implementing #EBookBackendClass.create_cursor() should also
48 * implement #EBookBackendClass.delete_cursor().
49 * </para>
50 * <para>
51 * For backends which use #EBookBackendSqliteDB to store contacts,
52 * an #EDataBookCursorSqlite can be used as a cursor implementation.
53 * </para>
54 * <para>
55 * Implementing a concrete cursor class for your own addressbook
56 * backend is a matter of implementing all of the virtual methods
57 * on the #EDataBookCursorClass vtable, each virtual method has
58 * documentation describing how each of the methods should be implemented.
59 * </para>
60 * </refsect2>
61 *
62 * <refsect2 id="cursor-track-state">
63 * <title>Tracking Cursor State</title>
64 * <para>
65 * The cursor state itself is defined as an array of sort keys
66 * and an %E_CONTACT_UID value. There should be one sort key
67 * stored for each contact field which was passed to
68 * #EBookBackendClass.create_cursor().
69 * </para>
70 * <para>
71 * Initially, the cursor state is assumed to be clear and
72 * positioned naturally at the beginning so that the first
73 * calls to #EDataBookCursorClass.step() using the
74 * %E_BOOK_CURSOR_ORIGIN_CURRENT origin would respond in the
75 * same way as the %E_BOOK_CURSOR_ORIGIN_BEGIN origin would.
76 * </para>
77 * <para>
78 * Unless the %E_BOOK_CURSOR_STEP_FETCH flag is not specified
79 * in calls to #EDataBookCursorClass.step(), the cursor state
80 * should be always be set to the last contact which was traversed
81 * in every call to #EDataBookCursorClass.step(). In the case
82 * that #EDataBookCursorClass.step() was asked to step beyond the
83 * bounderies of the list, i.e. when stepping passed the end
84 * of the list of before the beginning, then the cursor state
85 * can be cleared and the implementation must track whether
86 * the cursor is at the beginning or the end of the list.
87 * </para>
88 * <para>
89 * The task of actually collecting the cursor state from a
90 * contact should be done using an #ECollator created for
91 * the locale in which your #EBookBackend is configured for.
92 * </para>
93 * <example>
94 * <title>Collecting sort keys for a given contact</title>
95 * <programlisting><![CDATA[
96 * static void
97 * update_state_from_contact (EBookBackendSmth *smth,
98 * EBookBackendSmthCursor *cursor,
99 * EContact *contact)
100 * {
101 * gint i;
102 *
103 * clear_state (smth, cursor);
104 *
105 * // For each sort key the cursor was created for
106 * for (i = 0; i < cursor->n_sort_fields; i++) {
107 *
108 * // Using an ECollator created for the locale
109 * // set on your EBookBackend...
110 * const gchar *string = e_contact_get_const (contact, cursor->sort_fields[i]);
111 *
112 * // Generate a sort key for each value
113 * if (string)
114 * cursor->state->values[i] =
115 * e_collator_generate_key (smth->collator,
116 * string, NULL);
117 * else
118 * cursor->state->values[i] = g_strdup ("");
119 * }
120 *
121 * state->last_uid = e_contact_get (contact, E_CONTACT_UID);
122 * }
123 * ]]></programlisting>
124 * </example>
125 * <para>
126 * Using the strings collected above for a given contact,
127 * two contacts can easily be compared for equality in
128 * a locale sensitive way, using strcmp() directly on
129 * the generated sort keys.
130 * </para>
131 * <para>
132 * Calls to #EDataBookCursorClass.step() with the
133 * %E_BOOK_CURSOR_ORIGIN_BEGIN or %E_BOOK_CURSOR_ORIGIN_END reset
134 * the cursor state before fetching results from either the
135 * beginning or ending of the result list respectively.
136 * </para>
137 * </refsect2>
138 *
139 * <refsect2 id="cursor-localized-sorting">
140 * <title>Implementing Localized Sorting</title>
141 * <para>
142 * To implement localized sorting in an addressbook backend, an #ECollator
143 * can be used. The #ECollator provides all the functionality needed
144 * to respond to the cursor methods.
145 * </para>
146 * <para>
147 * When storing contacts in your backend, sort keys should be generated
148 * for any fields which might be used as sort key parameters for a cursor,
149 * keys for these fields should be generated with e_collator_generate_key()
150 * using an #ECollator created for the locale in which your addressbook is
151 * currently configured (undesired fields may be rejected at cursor creation
152 * time with an %E_CLIENT_ERROR_INVALID_QUERY error).
153 * </para>
154 * <para>
155 * The generated sort keys can then be used with strcmp() in order to
156 * compare results with the currently stored cursor state. These comparisons
157 * should compare contact fields in order of precedence in the array of
158 * sort fields which the cursor was configured with. If two contacts match
159 * exactly, then the %E_CONTACT_UID value is used to determine which
160 * contact sorts above or below the other.
161 * </para>
162 * <para>
163 * When your sort keys are generated using #ECollator, you can easily
164 * use e_collator_generate_key_for_index() to implement
165 * #EDataBookCursorClass.set_alphabetic_index() and set the cursor
166 * position before (below) a given letter in the active alphabet. The key
167 * generated for an alphabetic index is guaranteed to sort below any word
168 * starting with the given letter, and above any word starting with the
169 * preceeding letter.
170 * </para>
171 * </refsect2>
172 *
173 * <refsect2 id="cursor-dra">
174 * <title>Direct Read Access</title>
175 * <para>
176 * In order to support cursors in backends which support Direct Read Access
177 * mode, the underlying addressbook data must be written atomically along with each
178 * new revision attribute. The cursor mechanics rely on this atomicity in order
179 * to avoid any race conditions and ensure that data read back from the addressbook
180 * is current and up to date.
181 * </para>
182 * </refsect2>
183 *
184 * <refsect2 id="cursor-backends">
185 * <title>Backend Tasks</title>
186 * <para>
187 * Backends have ownership of the cursors which they create
188 * and have some responsibility when supporting cursors.
189 * </para>
190 * <para>
191 * As mentioned above, in Direct Read Access mode (if supported), all
192 * revision writes and addressbook modifications must be committed
193 * atomically.
194 * </para>
195 * <para>
196 * Beyond that, it is the responsibility of the backend to call
197 * e_data_book_cursor_contact_added() and e_data_book_cursor_contact_removed()
198 * whenever the addressbook is modified. When a contact is modified
199 * but not added or removed, then e_data_book_cursor_contact_removed()
200 * should be called with the old existing contact and then
201 * e_data_book_cursor_contact_added() should be called with
202 * the new contact. This will automatically update the cursor
203 * total and position status.
204 * </para>
205 * <para>
206 * Note that if it's too much trouble to load the existing
207 * contact data when a contact is modified, then
208 * e_data_book_cursor_recalculate() can be called instead. This
209 * will use the #EDataBookCursorClass.get_position() method
210 * recalculate current cursor position from scratch.
211 * </para>
212 * </refsect2>
213 */
214
215 #include "evolution-data-server-config.h"
216
217 #include <glib/gi18n.h>
218
219 #include "e-data-book-cursor.h"
220 #include "e-book-backend.h"
221
222 /* Private D-Bus class. */
223 #include <e-dbus-address-book-cursor.h>
224
225 /* GObjectClass */
226 static void e_data_book_cursor_constructed (GObject *object);
227 static void e_data_book_cursor_dispose (GObject *object);
228 static void e_data_book_cursor_finalize (GObject *object);
229 static void e_data_book_cursor_get_property (GObject *object,
230 guint property_id,
231 GValue *value,
232 GParamSpec *pspec);
233 static void e_data_book_cursor_set_property (GObject *object,
234 guint property_id,
235 const GValue *value,
236 GParamSpec *pspec);
237
238 /* Private Functions */
239 static void data_book_cursor_set_values (EDataBookCursor *cursor,
240 gint total,
241 gint position);
242 static gint data_book_cursor_compare_contact (EDataBookCursor *cursor,
243 EContact *contact,
244 gboolean *matches_sexp);
245 static void calculate_step_position (EDataBookCursor *cursor,
246 EBookCursorOrigin origin,
247 gint count,
248 gint results);
249
250 /* D-Bus callbacks */
251 static gint data_book_cursor_handle_step (EDBusAddressBookCursor *dbus_object,
252 GDBusMethodInvocation *invocation,
253 const gchar *revision,
254 EBookCursorStepFlags flags,
255 EBookCursorOrigin origin,
256 gint count,
257 EDataBookCursor *cursor);
258 static gboolean data_book_cursor_handle_set_alphabetic_index (EDBusAddressBookCursor *dbus_object,
259 GDBusMethodInvocation *invocation,
260 gint index,
261 const gchar *locale,
262 EDataBookCursor *cursor);
263 static gboolean data_book_cursor_handle_set_query (EDBusAddressBookCursor *dbus_object,
264 GDBusMethodInvocation *invocation,
265 const gchar *query,
266 EDataBookCursor *cursor);
267 static gboolean data_book_cursor_handle_dispose (EDBusAddressBookCursor *dbus_object,
268 GDBusMethodInvocation *invocation,
269 EDataBookCursor *cursor);
270
271 struct _EDataBookCursorPrivate {
272 EDBusAddressBookCursor *dbus_object;
273 EBookBackend *backend;
274
275 gchar *locale;
276 gint total;
277 gint position;
278 };
279
280 enum {
281 PROP_0,
282 PROP_BACKEND,
283 PROP_TOTAL,
284 PROP_POSITION,
285 };
286
287 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (
288 EDataBookCursor,
289 e_data_book_cursor,
290 G_TYPE_OBJECT);
291
292 /************************************************
293 * GObjectClass *
294 ************************************************/
295 static void
e_data_book_cursor_class_init(EDataBookCursorClass * class)296 e_data_book_cursor_class_init (EDataBookCursorClass *class)
297 {
298 GObjectClass *object_class = G_OBJECT_CLASS (class);
299
300 object_class->constructed = e_data_book_cursor_constructed;
301 object_class->finalize = e_data_book_cursor_finalize;
302 object_class->dispose = e_data_book_cursor_dispose;
303 object_class->get_property = e_data_book_cursor_get_property;
304 object_class->set_property = e_data_book_cursor_set_property;
305
306 g_object_class_install_property (
307 object_class,
308 PROP_BACKEND,
309 g_param_spec_object (
310 "backend",
311 "Backend",
312 "The backend which created this cursor",
313 E_TYPE_BOOK_BACKEND,
314 G_PARAM_READWRITE |
315 G_PARAM_CONSTRUCT_ONLY));
316
317 g_object_class_install_property (
318 object_class,
319 PROP_TOTAL,
320 g_param_spec_int (
321 "total", "Total",
322 "The total results for this cursor",
323 0, G_MAXINT, 0,
324 G_PARAM_READABLE));
325
326 g_object_class_install_property (
327 object_class,
328 PROP_POSITION,
329 g_param_spec_int (
330 "position", "Position",
331 "The current position of this cursor",
332 0, G_MAXINT, 0,
333 G_PARAM_READABLE));
334 }
335
336 static void
e_data_book_cursor_init(EDataBookCursor * cursor)337 e_data_book_cursor_init (EDataBookCursor *cursor)
338 {
339 cursor->priv = e_data_book_cursor_get_instance_private (cursor);
340 }
341
342 static void
e_data_book_cursor_constructed(GObject * object)343 e_data_book_cursor_constructed (GObject *object)
344 {
345 EDataBookCursor *cursor = E_DATA_BOOK_CURSOR (object);
346 GError *error = NULL;
347
348 /* Get the initial cursor values */
349 if (!e_data_book_cursor_recalculate (cursor, NULL, &error)) {
350 g_warning (
351 "Failed to calculate initial cursor position: %s",
352 error->message);
353 g_clear_error (&error);
354 }
355
356 G_OBJECT_CLASS (e_data_book_cursor_parent_class)->constructed (object);
357 }
358
359 static void
e_data_book_cursor_finalize(GObject * object)360 e_data_book_cursor_finalize (GObject *object)
361 {
362 EDataBookCursor *cursor = E_DATA_BOOK_CURSOR (object);
363 EDataBookCursorPrivate *priv = cursor->priv;
364
365 g_free (priv->locale);
366
367 G_OBJECT_CLASS (e_data_book_cursor_parent_class)->finalize (object);
368 }
369
370 static void
e_data_book_cursor_dispose(GObject * object)371 e_data_book_cursor_dispose (GObject *object)
372 {
373 EDataBookCursor *cursor = E_DATA_BOOK_CURSOR (object);
374 EDataBookCursorPrivate *priv = cursor->priv;
375
376 g_clear_object (&(priv->dbus_object));
377 g_clear_object (&(priv->backend));
378
379 G_OBJECT_CLASS (e_data_book_cursor_parent_class)->dispose (object);
380 }
381
382 static void
e_data_book_cursor_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)383 e_data_book_cursor_get_property (GObject *object,
384 guint property_id,
385 GValue *value,
386 GParamSpec *pspec)
387 {
388 EDataBookCursor *cursor = E_DATA_BOOK_CURSOR (object);
389 EDataBookCursorPrivate *priv = cursor->priv;
390
391 switch (property_id) {
392 case PROP_BACKEND:
393 g_value_set_object (value, priv->backend);
394 break;
395 case PROP_TOTAL:
396 g_value_set_int (value, priv->total);
397 break;
398 case PROP_POSITION:
399 g_value_set_int (value, priv->position);
400 break;
401 default:
402 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
403 break;
404 }
405 }
406
407 static void
e_data_book_cursor_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)408 e_data_book_cursor_set_property (GObject *object,
409 guint property_id,
410 const GValue *value,
411 GParamSpec *pspec)
412 {
413 EDataBookCursor *cursor = E_DATA_BOOK_CURSOR (object);
414 EDataBookCursorPrivate *priv = cursor->priv;
415
416 switch (property_id) {
417 case PROP_BACKEND:
418 /* Construct-only, can only be set once */
419 priv->backend = g_value_dup_object (value);
420 break;
421 default:
422 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
423 break;
424 }
425 }
426
427 /************************************************
428 * Private Functions *
429 ************************************************/
430 static void
data_book_cursor_set_values(EDataBookCursor * cursor,gint total,gint position)431 data_book_cursor_set_values (EDataBookCursor *cursor,
432 gint total,
433 gint position)
434 {
435 EDataBookCursorPrivate *priv;
436 gboolean changed = FALSE;
437
438 g_return_if_fail (E_IS_DATA_BOOK_CURSOR (cursor));
439
440 priv = cursor->priv;
441
442 g_object_freeze_notify (G_OBJECT (cursor));
443
444 if (priv->total != total) {
445 priv->total = total;
446 g_object_notify (G_OBJECT (cursor), "total");
447 changed = TRUE;
448 }
449
450 if (priv->position != position) {
451 priv->position = position;
452 g_object_notify (G_OBJECT (cursor), "position");
453 changed = TRUE;
454 }
455
456 g_object_thaw_notify (G_OBJECT (cursor));
457
458 if (changed && priv->dbus_object) {
459 e_dbus_address_book_cursor_set_total (priv->dbus_object, priv->total);
460 e_dbus_address_book_cursor_set_position (priv->dbus_object, priv->position);
461 }
462 }
463
464 static gint
data_book_cursor_compare_contact(EDataBookCursor * cursor,EContact * contact,gboolean * matches_sexp)465 data_book_cursor_compare_contact (EDataBookCursor *cursor,
466 EContact *contact,
467 gboolean *matches_sexp)
468 {
469 EDataBookCursorClass *klass;
470 gint result;
471
472 klass = E_DATA_BOOK_CURSOR_GET_CLASS (cursor);
473 g_return_val_if_fail (klass != NULL, 0);
474
475 if (!klass->compare_contact) {
476 g_critical (
477 "EDataBookCursor.compare_contact() unimplemented on type '%s'",
478 G_OBJECT_TYPE_NAME (cursor));
479 return 0;
480 }
481
482 g_object_ref (cursor);
483 result = klass->compare_contact (cursor, contact, matches_sexp);
484 g_object_unref (cursor);
485
486 return result;
487 }
488
489 static void
calculate_step_position(EDataBookCursor * cursor,EBookCursorOrigin origin,gint count,gint results)490 calculate_step_position (EDataBookCursor *cursor,
491 EBookCursorOrigin origin,
492 gint count,
493 gint results)
494 {
495 EDataBookCursorPrivate *priv = cursor->priv;
496 gint new_position = 0;
497 gint offset = results;
498
499 g_return_if_fail (origin == E_BOOK_CURSOR_ORIGIN_CURRENT ||
500 origin == E_BOOK_CURSOR_ORIGIN_BEGIN ||
501 origin == E_BOOK_CURSOR_ORIGIN_END);
502
503 /* If we didnt get as many contacts as asked for, it indicates that
504 * we've reached the end of the list (or beginning)... in this case
505 * we add 1 to the offset
506 * so that we land on the 0 position or the total + 1 position
507 * respectively.
508 */
509 if (offset < ABS (count)) {
510 offset += 1;
511 }
512
513 /* Convert our 'number of results' into a signed 'offset'
514 * to add to the cursor position.
515 */
516 if (count < 0)
517 offset = -offset;
518
519 /* Don't assert the boundaries of values here, we
520 * did that in e_data_book_cursor_step() already.
521 */
522 switch (origin) {
523 case E_BOOK_CURSOR_ORIGIN_CURRENT:
524 new_position = priv->position + offset;
525 break;
526
527 case E_BOOK_CURSOR_ORIGIN_BEGIN:
528 new_position = offset;
529 break;
530
531 case E_BOOK_CURSOR_ORIGIN_END:
532 new_position = (priv->total + 1) + offset;
533 break;
534 }
535
536 new_position = CLAMP (new_position, 0, priv->total + 1);
537 data_book_cursor_set_values (cursor, priv->total, new_position);
538 }
539
540 /************************************************
541 * D-Bus Callbacks *
542 ************************************************/
543 static gboolean
data_book_cursor_handle_step(EDBusAddressBookCursor * dbus_object,GDBusMethodInvocation * invocation,const gchar * revision,EBookCursorStepFlags flags,EBookCursorOrigin origin,gint count,EDataBookCursor * cursor)544 data_book_cursor_handle_step (EDBusAddressBookCursor *dbus_object,
545 GDBusMethodInvocation *invocation,
546 const gchar *revision,
547 EBookCursorStepFlags flags,
548 EBookCursorOrigin origin,
549 gint count,
550 EDataBookCursor *cursor)
551 {
552 GSList *results = NULL;
553 GError *error = NULL;
554 gint n_results;
555
556 n_results = e_data_book_cursor_step (
557 cursor, revision, flags, origin,
558 count, &results, NULL, &error);
559
560 if (n_results < 0) {
561 g_dbus_method_invocation_return_gerror (invocation, error);
562 g_clear_error (&error);
563 } else {
564 gchar **strv = NULL;
565 const gchar * const empty_str[] = { NULL };
566
567 if (results) {
568 GSList *l;
569 gint i = 0;
570
571 strv = g_new0 (gchar *, g_slist_length (results) + 1);
572
573 for (l = results; l; l = l->next) {
574 gchar *vcard = l->data;
575
576 strv[i++] = e_util_utf8_make_valid (vcard);
577 }
578
579 g_slist_free_full (results, g_free);
580 }
581
582 e_dbus_address_book_cursor_complete_step (
583 dbus_object,
584 invocation,
585 n_results,
586 strv ?
587 (const gchar *const *) strv :
588 empty_str,
589 cursor->priv->total,
590 cursor->priv->position);
591
592 g_strfreev (strv);
593 }
594
595 return TRUE;
596 }
597
598 static gboolean
data_book_cursor_handle_set_alphabetic_index(EDBusAddressBookCursor * dbus_object,GDBusMethodInvocation * invocation,gint index,const gchar * locale,EDataBookCursor * cursor)599 data_book_cursor_handle_set_alphabetic_index (EDBusAddressBookCursor *dbus_object,
600 GDBusMethodInvocation *invocation,
601 gint index,
602 const gchar *locale,
603 EDataBookCursor *cursor)
604 {
605 GError *error = NULL;
606
607 if (!e_data_book_cursor_set_alphabetic_index (cursor,
608 index,
609 locale,
610 NULL,
611 &error)) {
612 g_dbus_method_invocation_return_gerror (invocation, error);
613 g_clear_error (&error);
614 } else {
615 e_dbus_address_book_cursor_complete_set_alphabetic_index (
616 dbus_object,
617 invocation,
618 cursor->priv->total,
619 cursor->priv->position);
620 }
621
622 return TRUE;
623 }
624
625 static gboolean
data_book_cursor_handle_set_query(EDBusAddressBookCursor * dbus_object,GDBusMethodInvocation * invocation,const gchar * query,EDataBookCursor * cursor)626 data_book_cursor_handle_set_query (EDBusAddressBookCursor *dbus_object,
627 GDBusMethodInvocation *invocation,
628 const gchar *query,
629 EDataBookCursor *cursor)
630 {
631 GError *error = NULL;
632
633 if (!e_data_book_cursor_set_sexp (cursor, query, NULL, &error)) {
634 g_dbus_method_invocation_return_gerror (invocation, error);
635 g_clear_error (&error);
636 } else {
637 e_dbus_address_book_cursor_complete_set_query (
638 dbus_object,
639 invocation,
640 cursor->priv->total,
641 cursor->priv->position);
642 }
643
644 return TRUE;
645 }
646
647 static gboolean
data_book_cursor_handle_dispose(EDBusAddressBookCursor * dbus_object,GDBusMethodInvocation * invocation,EDataBookCursor * cursor)648 data_book_cursor_handle_dispose (EDBusAddressBookCursor *dbus_object,
649 GDBusMethodInvocation *invocation,
650 EDataBookCursor *cursor)
651 {
652 EDataBookCursorPrivate *priv = cursor->priv;
653 GError *error = NULL;
654
655 /* The backend will release the cursor, just make sure that
656 * we survive long enough to complete this method call
657 */
658 g_object_ref (cursor);
659
660 /* This should never really happen, but if it does, there is no
661 * we cannot expect the client to recover well from an error at
662 * dispose time, so let's just log the warning.
663 */
664 if (!e_book_backend_delete_cursor (priv->backend, cursor, &error)) {
665 g_warning ("Error trying to delete cursor: %s", error->message);
666 g_clear_error (&error);
667 }
668
669 e_dbus_address_book_cursor_complete_dispose (dbus_object, invocation);
670
671 g_object_unref (cursor);
672
673 return TRUE;
674 }
675
676 /************************************************
677 * API *
678 ************************************************/
679
680 /**
681 * e_data_book_cursor_get_backend:
682 * @cursor: an #EDataBookCursor
683 *
684 * Gets the backend which created and owns @cursor.
685 *
686 * Returns: (transfer none) (type EBookBackend): The #EBookBackend owning @cursor.
687 *
688 * Since: 3.12
689 */
690 struct _EBookBackend *
e_data_book_cursor_get_backend(EDataBookCursor * cursor)691 e_data_book_cursor_get_backend (EDataBookCursor *cursor)
692 {
693 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), NULL);
694
695 return cursor->priv->backend;
696 }
697
698 /**
699 * e_data_book_cursor_get_total:
700 * @cursor: an #EDataBookCursor
701 *
702 * Fetch the total number of contacts which match @cursor's query expression.
703 *
704 * Returns: the total contacts for @cursor
705 *
706 * Since: 3.12
707 */
708 gint
e_data_book_cursor_get_total(EDataBookCursor * cursor)709 e_data_book_cursor_get_total (EDataBookCursor *cursor)
710 {
711 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), -1);
712
713 return cursor->priv->total;
714 }
715
716 /**
717 * e_data_book_cursor_get_position:
718 * @cursor: an #EDataBookCursor
719 *
720 * Fetch the current position of @cursor in its result list.
721 *
722 * Returns: the current position of @cursor
723 *
724 * Since: 3.12
725 */
726 gint
e_data_book_cursor_get_position(EDataBookCursor * cursor)727 e_data_book_cursor_get_position (EDataBookCursor *cursor)
728 {
729 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), -1);
730
731 return cursor->priv->position;
732 }
733
734 /**
735 * e_data_book_cursor_set_sexp:
736 * @cursor: an #EDataBookCursor
737 * @sexp: (nullable): the search expression to set
738 * @cancellable: A #GCancellable
739 * @error: return location for a #GError, or %NULL
740 *
741 * Sets the search expression for the cursor
742 *
743 * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
744 *
745 * Since: 3.12
746 */
747 gboolean
e_data_book_cursor_set_sexp(EDataBookCursor * cursor,const gchar * sexp,GCancellable * cancellable,GError ** error)748 e_data_book_cursor_set_sexp (EDataBookCursor *cursor,
749 const gchar *sexp,
750 GCancellable *cancellable,
751 GError **error)
752 {
753 EDataBookCursorClass *klass;
754 GError *local_error = NULL;
755 gboolean success = FALSE;
756
757 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
758
759 klass = E_DATA_BOOK_CURSOR_GET_CLASS (cursor);
760 g_return_val_if_fail (klass != NULL, FALSE);
761
762 g_object_ref (cursor);
763
764 if (klass->set_sexp) {
765 success = klass->set_sexp (cursor, sexp, error);
766 } else {
767 g_set_error_literal (
768 error,
769 E_CLIENT_ERROR,
770 E_CLIENT_ERROR_NOT_SUPPORTED,
771 _("Cursor does not support setting the search expression"));
772 }
773
774 /* We already set the new search expression,
775 * we can't fail anymore so just fire a warning
776 */
777 if (success &&
778 !e_data_book_cursor_recalculate (cursor, cancellable, &local_error)) {
779 g_warning (
780 "Failed to recalculate the cursor value "
781 "after setting the search expression: %s",
782 local_error->message);
783 g_clear_error (&local_error);
784 }
785
786 g_object_unref (cursor);
787
788 return success;
789 }
790
791 /**
792 * e_data_book_cursor_step:
793 * @cursor: an #EDataBookCursor
794 * @revision_guard: The expected current addressbook revision, or %NULL
795 * @flags: The #EBookCursorStepFlags for this step
796 * @origin: The #EBookCursorOrigin from whence to step
797 * @count: a positive or negative amount of contacts to try and fetch
798 * @results: (out) (nullable) (element-type utf8) (transfer full):
799 * A return location to store the results, or %NULL if %E_BOOK_CURSOR_STEP_FETCH is not specified in @flags
800 * @cancellable: A #GCancellable
801 * @error: return location for a #GError, or %NULL
802 *
803 * Steps @cursor through its sorted query by a maximum of @count contacts
804 * starting from @origin.
805 *
806 * If @count is negative, then the cursor will move through the list in reverse.
807 *
808 * If @cursor reaches the beginning or end of the query results, then the
809 * returned list might not contain the amount of desired contacts, or might
810 * return no results if the cursor currently points to the last contact.
811 * Reaching the end of the list is not considered an error condition. Attempts
812 * to step beyond the end of the list after having reached the end of the list
813 * will however trigger an %E_CLIENT_ERROR_QUERY_REFUSED error.
814 *
815 * If %E_BOOK_CURSOR_STEP_FETCH is specified in @flags, a pointer to
816 * a %NULL #GSList pointer should be provided for the @results parameter.
817 *
818 * The result list will be stored to @results and should be freed with g_slist_free()
819 * and all elements freed with g_free().
820 *
821 * If a @revision_guard is specified, the cursor implementation will issue an
822 * %E_CLIENT_ERROR_OUT_OF_SYNC error if the @revision_guard does not match
823 * the current addressbook revision.
824 *
825 * An explanation of how stepping is expected to behave can be found
826 * in the <link linkend="cursor-iteration">user facing reference documentation</link>.
827 *
828 * Returns: The number of contacts traversed if successful, otherwise -1 is
829 * returned and @error is set.
830 *
831 * Since: 3.12
832 */
833 gint
e_data_book_cursor_step(EDataBookCursor * cursor,const gchar * revision_guard,EBookCursorStepFlags flags,EBookCursorOrigin origin,gint count,GSList ** results,GCancellable * cancellable,GError ** error)834 e_data_book_cursor_step (EDataBookCursor *cursor,
835 const gchar *revision_guard,
836 EBookCursorStepFlags flags,
837 EBookCursorOrigin origin,
838 gint count,
839 GSList **results,
840 GCancellable *cancellable,
841 GError **error)
842 {
843 EDataBookCursorClass *klass;
844 gint retval;
845
846 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), -1);
847 g_return_val_if_fail ((flags & E_BOOK_CURSOR_STEP_FETCH) == 0 ||
848 (results != NULL && *results == NULL), -1);
849
850 klass = E_DATA_BOOK_CURSOR_GET_CLASS (cursor);
851 g_return_val_if_fail (klass != NULL, -1);
852
853 if (!klass->step) {
854 g_set_error_literal (
855 error,
856 E_CLIENT_ERROR,
857 E_CLIENT_ERROR_NOT_SUPPORTED,
858 _("Cursor does not support step"));
859 return -1;
860 }
861
862 g_object_ref (cursor);
863 retval = klass->step (cursor, revision_guard, flags, origin, count, results, cancellable, error);
864 g_object_unref (cursor);
865
866 if (retval >= 0 && (flags & E_BOOK_CURSOR_STEP_MOVE) != 0) {
867 calculate_step_position (cursor, origin, count, retval);
868 }
869
870 return retval;
871 }
872
873 /**
874 * e_data_book_cursor_set_alphabetic_index:
875 * @cursor: an #EDataBookCursor
876 * @index: the alphabetic index
877 * @locale: the locale in which @index is expected to be a valid alphabetic index
878 * @cancellable: A #GCancellable
879 * @error: return location for a #GError, or %NULL
880 *
881 * Sets the @cursor position to an
882 * <link linkend="cursor-alphabet">Alphabetic Index</link>
883 * into the alphabet active in the @locale of the addressbook.
884 *
885 * After setting the target to an alphabetic index, for example the
886 * index for letter 'E', then further calls to e_data_book_cursor_step()
887 * will return results starting with the letter 'E' (or results starting
888 * with the last result in 'D', if moving in a negative direction).
889 *
890 * The passed index must be a valid index in @locale, if by some chance
891 * the addressbook backend has changed into a new locale after this
892 * call has been issued, an %E_CLIENT_ERROR_OUT_OF_SYNC error will be
893 * issued indicating that there was a locale mismatch.
894 *
895 * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
896 *
897 * Since: 3.12
898 */
899 gboolean
e_data_book_cursor_set_alphabetic_index(EDataBookCursor * cursor,gint index,const gchar * locale,GCancellable * cancellable,GError ** error)900 e_data_book_cursor_set_alphabetic_index (EDataBookCursor *cursor,
901 gint index,
902 const gchar *locale,
903 GCancellable *cancellable,
904 GError **error)
905 {
906 EDataBookCursorClass *klass;
907 GError *local_error = NULL;
908 gboolean success;
909
910 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
911
912 klass = E_DATA_BOOK_CURSOR_GET_CLASS (cursor);
913 g_return_val_if_fail (klass != NULL, FALSE);
914
915 g_object_ref (cursor);
916
917 if (klass->set_alphabetic_index) {
918 success = klass->set_alphabetic_index (cursor, index, locale, error);
919
920 /* We already set the new cursor value, we can't fail anymore so just fire a warning */
921 if (!e_data_book_cursor_recalculate (cursor, cancellable, &local_error)) {
922 g_warning (
923 "Failed to recalculate the cursor value "
924 "after setting the alphabetic index: %s",
925 local_error->message);
926 g_clear_error (&local_error);
927 }
928
929 } else {
930 g_set_error_literal (
931 error,
932 E_CLIENT_ERROR,
933 E_CLIENT_ERROR_NOT_SUPPORTED,
934 _("Cursor does not support alphabetic indexes"));
935 success = FALSE;
936 }
937
938 g_object_unref (cursor);
939
940 return success;
941 }
942
943 /**
944 * e_data_book_cursor_recalculate:
945 * @cursor: an #EDataBookCursor
946 * @cancellable: A #GCancellable
947 * @error: return location for a #GError, or %NULL
948 *
949 * Recalculates the cursor's total and position, this is meant
950 * for cursor created in Direct Read Access mode to synchronously
951 * recalculate the position and total values when the addressbook
952 * revision has changed.
953 *
954 * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
955 *
956 * Since: 3.12
957 */
958 gboolean
e_data_book_cursor_recalculate(EDataBookCursor * cursor,GCancellable * cancellable,GError ** error)959 e_data_book_cursor_recalculate (EDataBookCursor *cursor,
960 GCancellable *cancellable,
961 GError **error)
962 {
963 EDataBookCursorClass *klass;
964 gint total = 0;
965 gint position = 0;
966 gboolean success = FALSE;
967
968 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
969
970 klass = E_DATA_BOOK_CURSOR_GET_CLASS (cursor);
971 g_return_val_if_fail (klass != NULL, FALSE);
972
973 /* Bad programming error */
974 if (!klass->get_position) {
975 g_critical (
976 "EDataBookCursor.get_position() unimplemented on type '%s'",
977 G_OBJECT_TYPE_NAME (cursor));
978
979 return FALSE;
980 }
981
982 g_object_ref (cursor);
983 success = klass->get_position (cursor, &total, &position, cancellable, error);
984 g_object_unref (cursor);
985
986 if (success)
987 data_book_cursor_set_values (cursor, total, position);
988
989 return success;
990 }
991
992 /**
993 * e_data_book_cursor_load_locale:
994 * @cursor: an #EDataBookCursor
995 * @locale: (out) (optional): return location for the locale
996 * @cancellable: A #GCancellable
997 * @error: return location for a #GError, or %NULL
998 *
999 * Load the current locale setting from the cursor's underlying database.
1000 *
1001 * Addressbook backends implementing cursors should call this function on all active
1002 * cursor when the locale setting changes.
1003 *
1004 * This will implicitly reset @cursor's state and position.
1005 *
1006 * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
1007 *
1008 * Since: 3.12
1009 */
1010 gboolean
e_data_book_cursor_load_locale(EDataBookCursor * cursor,gchar ** locale,GCancellable * cancellable,GError ** error)1011 e_data_book_cursor_load_locale (EDataBookCursor *cursor,
1012 gchar **locale,
1013 GCancellable *cancellable,
1014 GError **error)
1015 {
1016 EDataBookCursorClass *klass;
1017 EDataBookCursorPrivate *priv;
1018 gboolean success;
1019 gchar *local_locale = NULL;
1020
1021 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
1022
1023 klass = E_DATA_BOOK_CURSOR_GET_CLASS (cursor);
1024 g_return_val_if_fail (klass != NULL, FALSE);
1025
1026 priv = cursor->priv;
1027
1028 if (!klass->load_locale) {
1029 g_critical (
1030 "EDataBookCursor.load_locale() unimplemented on type '%s'",
1031 G_OBJECT_TYPE_NAME (cursor));
1032 return FALSE;
1033 }
1034
1035 g_object_ref (cursor);
1036 success = klass->load_locale (cursor, &local_locale, error);
1037 g_object_unref (cursor);
1038
1039 /* Changed ! Reset the thing */
1040 if (g_strcmp0 (priv->locale, local_locale) != 0) {
1041 GError *local_error = NULL;
1042
1043 g_free (priv->locale);
1044 priv->locale = g_strdup (local_locale);
1045
1046 if (e_data_book_cursor_step (cursor, NULL,
1047 E_BOOK_CURSOR_STEP_MOVE,
1048 E_BOOK_CURSOR_ORIGIN_BEGIN,
1049 0, NULL, cancellable, &local_error) < 0) {
1050 g_warning (
1051 "Error resetting cursor position after locale change: %s",
1052 local_error->message);
1053 g_clear_error (&local_error);
1054 } else if (!e_data_book_cursor_recalculate (E_DATA_BOOK_CURSOR (cursor),
1055 cancellable, &local_error)) {
1056 g_warning (
1057 "Error recalculating cursor position after locale change: %s",
1058 local_error->message);
1059 g_clear_error (&local_error);
1060 }
1061 }
1062
1063 if (locale)
1064 *locale = local_locale;
1065 else
1066 g_free (local_locale);
1067
1068 return success;
1069 }
1070
1071 /**
1072 * e_data_book_cursor_contact_added:
1073 * @cursor: an #EDataBookCursor
1074 * @contact: the #EContact which was added to the addressbook
1075 *
1076 * Should be called by addressbook backends whenever a contact
1077 * is added.
1078 *
1079 * Since: 3.12
1080 */
1081 void
e_data_book_cursor_contact_added(EDataBookCursor * cursor,EContact * contact)1082 e_data_book_cursor_contact_added (EDataBookCursor *cursor,
1083 EContact *contact)
1084 {
1085 EDataBookCursorPrivate *priv;
1086 gint comparison = 0;
1087 gboolean matches_sexp = FALSE;
1088 gint new_total, new_position;
1089
1090 g_return_if_fail (E_IS_DATA_BOOK_CURSOR (cursor));
1091 g_return_if_fail (E_IS_CONTACT (contact));
1092
1093 priv = cursor->priv;
1094
1095 comparison = data_book_cursor_compare_contact (cursor, contact, &matches_sexp);
1096
1097 /* The added contact doesn't match the cursor search expression, no need
1098 * to change the position & total values
1099 */
1100 if (!matches_sexp)
1101 return;
1102
1103 new_total = priv->total;
1104 new_position = priv->position;
1105
1106 /* One new contact */
1107 new_total++;
1108
1109 /* New contact was added at cursor position or before cursor position */
1110 if (comparison <= 0)
1111 new_position++;
1112
1113 /* Notify total & position change */
1114 data_book_cursor_set_values (cursor, new_total, new_position);
1115 }
1116
1117 /**
1118 * e_data_book_cursor_contact_removed:
1119 * @cursor: an #EDataBookCursor
1120 * @contact: the #EContact which was removed from the addressbook
1121 *
1122 * Should be called by addressbook backends whenever a contact
1123 * is removed.
1124 *
1125 * Since: 3.12
1126 */
1127 void
e_data_book_cursor_contact_removed(EDataBookCursor * cursor,EContact * contact)1128 e_data_book_cursor_contact_removed (EDataBookCursor *cursor,
1129 EContact *contact)
1130 {
1131 EDataBookCursorPrivate *priv;
1132 gint comparison = 0;
1133 gboolean matches_sexp = FALSE;
1134 gint new_total, new_position;
1135
1136 g_return_if_fail (E_IS_DATA_BOOK_CURSOR (cursor));
1137 g_return_if_fail (E_IS_CONTACT (contact));
1138
1139 priv = cursor->priv;
1140
1141 comparison = data_book_cursor_compare_contact (cursor, contact, &matches_sexp);
1142
1143 /* The removed contact did not match the cursor search expression, no need
1144 * to change the position & total values
1145 */
1146 if (!matches_sexp)
1147 return;
1148
1149 new_total = priv->total;
1150 new_position = priv->position;
1151
1152 /* One less contact */
1153 new_total--;
1154
1155 /* Removed contact was the exact cursor position or before cursor position */
1156 if (comparison <= 0)
1157 new_position--;
1158
1159 /* Notify total & position change */
1160 data_book_cursor_set_values (cursor, new_total, new_position);
1161 }
1162
1163 /**
1164 * e_data_book_cursor_register_gdbus_object:
1165 * @cursor: an #EDataBookCursor
1166 * @connection: the #GDBusConnection to register with
1167 * @object_path: the object path to place the direct access configuration data
1168 * @error: a location to store any error which might occur while registering
1169 *
1170 * Places @cursor on the @connection at @object_path
1171 *
1172 * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set.
1173 *
1174 * Since: 3.12
1175 */
1176 gboolean
e_data_book_cursor_register_gdbus_object(EDataBookCursor * cursor,GDBusConnection * connection,const gchar * object_path,GError ** error)1177 e_data_book_cursor_register_gdbus_object (EDataBookCursor *cursor,
1178 GDBusConnection *connection,
1179 const gchar *object_path,
1180 GError **error)
1181 {
1182 EDataBookCursorPrivate *priv;
1183
1184 g_return_val_if_fail (E_IS_DATA_BOOK_CURSOR (cursor), FALSE);
1185 g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
1186 g_return_val_if_fail (object_path != NULL, FALSE);
1187
1188 priv = cursor->priv;
1189
1190 if (!priv->dbus_object) {
1191 priv->dbus_object = e_dbus_address_book_cursor_skeleton_new ();
1192
1193 g_signal_connect (
1194 priv->dbus_object, "handle-step",
1195 G_CALLBACK (data_book_cursor_handle_step), cursor);
1196 g_signal_connect (
1197 priv->dbus_object, "handle-set-alphabetic-index",
1198 G_CALLBACK (data_book_cursor_handle_set_alphabetic_index), cursor);
1199 g_signal_connect (
1200 priv->dbus_object, "handle-set-query",
1201 G_CALLBACK (data_book_cursor_handle_set_query), cursor);
1202 g_signal_connect (
1203 priv->dbus_object, "handle-dispose",
1204 G_CALLBACK (data_book_cursor_handle_dispose), cursor);
1205
1206 /* Set initial total / position */
1207 e_dbus_address_book_cursor_set_total (priv->dbus_object, priv->total);
1208 e_dbus_address_book_cursor_set_position (priv->dbus_object, priv->position);
1209 }
1210
1211 return g_dbus_interface_skeleton_export (
1212 G_DBUS_INTERFACE_SKELETON (priv->dbus_object),
1213 connection, object_path, error);
1214 }
1215