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