1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  * Copyright (C) 2006 OpenedHand Ltd
5  * Copyright (C) 2009 Intel Corporation
6  *
7  * This library is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  * Authors: Ross Burton <ross@linux.intel.com>
20  */
21 
22 /**
23  * SECTION: e-data-book-view
24  * @include: libedata-book/libedata-book.h
25  * @short_description: A server side object for issuing view notifications
26  *
27  * This class communicates with #EBookClientViews over the bus.
28  *
29  * Addressbook backends can automatically own a number of views requested
30  * by the client, this API can be used by the backend to issue notifications
31  * which will be delivered to the #EBookClientView
32  **/
33 
34 #include "evolution-data-server-config.h"
35 
36 #include <string.h>
37 
38 #include "e-data-book-view.h"
39 
40 #include "e-data-book.h"
41 #include "e-book-backend.h"
42 
43 #include "e-dbus-address-book-view.h"
44 
45 /* how many items can be hold in a cache, before propagated to UI */
46 #define THRESHOLD_ITEMS 32
47 
48 /* how long to wait until notifications are propagated to UI; in seconds */
49 #define THRESHOLD_SECONDS 2
50 
51 struct _EDataBookViewPrivate {
52 	GDBusConnection *connection;
53 	EDBusAddressBookView *dbus_object;
54 	gchar *object_path;
55 
56 	GWeakRef backend_weakref; /* EBookBackend * */
57 
58 	EBookBackendSExp *sexp;
59 	EBookClientViewFlags flags;
60 
61 	gboolean running;
62 	gboolean complete;
63 	GMutex pending_mutex;
64 
65 	GArray *adds;
66 	GArray *changes;
67 	GArray *removes;
68 
69 	GHashTable *ids;
70 
71 	guint flush_id;
72 
73 	/* which fields is listener interested in */
74 	GHashTable *fields_of_interest;
75 	gboolean send_uids_only;
76 };
77 
78 enum {
79 	PROP_0,
80 	PROP_BACKEND,
81 	PROP_CONNECTION,
82 	PROP_OBJECT_PATH,
83 	PROP_SEXP
84 };
85 
86 /* Forward Declarations */
87 static void	e_data_book_view_initable_init	(GInitableIface *iface);
88 
G_DEFINE_TYPE_WITH_CODE(EDataBookView,e_data_book_view,G_TYPE_OBJECT,G_ADD_PRIVATE (EDataBookView)G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,e_data_book_view_initable_init))89 G_DEFINE_TYPE_WITH_CODE (
90 	EDataBookView,
91 	e_data_book_view,
92 	G_TYPE_OBJECT,
93 	G_ADD_PRIVATE (EDataBookView)
94 	G_IMPLEMENT_INTERFACE (
95 		G_TYPE_INITABLE,
96 		e_data_book_view_initable_init))
97 
98 static guint
99 str_ic_hash (gconstpointer key)
100 {
101 	guint32 hash = 5381;
102 	const gchar *str = key;
103 	gint ii;
104 
105 	if (str == NULL)
106 		return hash;
107 
108 	for (ii = 0; str[ii] != '\0'; ii++)
109 		hash = hash * 33 + g_ascii_tolower (str[ii]);
110 
111 	return hash;
112 }
113 
114 static gboolean
str_ic_equal(gconstpointer a,gconstpointer b)115 str_ic_equal (gconstpointer a,
116               gconstpointer b)
117 {
118 	const gchar *stra = a;
119 	const gchar *strb = b;
120 	gint ii;
121 
122 	if (stra == NULL && strb == NULL)
123 		return TRUE;
124 
125 	if (stra == NULL || strb == NULL)
126 		return FALSE;
127 
128 	for (ii = 0; stra[ii] != '\0' && strb[ii] != '\0'; ii++) {
129 		if (g_ascii_tolower (stra[ii]) != g_ascii_tolower (strb[ii]))
130 			return FALSE;
131 	}
132 
133 	return stra[ii] == strb[ii];
134 }
135 
136 static void
reset_array(GArray * array)137 reset_array (GArray *array)
138 {
139 	gint i = 0;
140 	gchar *tmp = NULL;
141 
142 	/* Free stored strings */
143 	for (i = 0; i < array->len; i++) {
144 		tmp = g_array_index (array, gchar *, i);
145 		g_free (tmp);
146 	}
147 
148 	/* Force the array size to 0 */
149 	g_array_set_size (array, 0);
150 }
151 
152 static void
send_pending_adds(EDataBookView * view)153 send_pending_adds (EDataBookView *view)
154 {
155 	if (view->priv->adds->len == 0)
156 		return;
157 
158 	e_dbus_address_book_view_emit_objects_added (
159 		view->priv->dbus_object,
160 		(const gchar * const *) view->priv->adds->data);
161 	reset_array (view->priv->adds);
162 }
163 
164 static void
send_pending_changes(EDataBookView * view)165 send_pending_changes (EDataBookView *view)
166 {
167 	if (view->priv->changes->len == 0)
168 		return;
169 
170 	e_dbus_address_book_view_emit_objects_modified (
171 		view->priv->dbus_object,
172 		(const gchar * const *) view->priv->changes->data);
173 	reset_array (view->priv->changes);
174 }
175 
176 static void
send_pending_removes(EDataBookView * view)177 send_pending_removes (EDataBookView *view)
178 {
179 	if (view->priv->removes->len == 0)
180 		return;
181 
182 	e_dbus_address_book_view_emit_objects_removed (
183 		view->priv->dbus_object,
184 		(const gchar * const *) view->priv->removes->data);
185 	reset_array (view->priv->removes);
186 }
187 
188 static gboolean
pending_flush_timeout_cb(gpointer data)189 pending_flush_timeout_cb (gpointer data)
190 {
191 	EDataBookView *view = data;
192 
193 	g_mutex_lock (&view->priv->pending_mutex);
194 
195 	view->priv->flush_id = 0;
196 
197 	if (!g_source_is_destroyed (g_main_current_source ())) {
198 		send_pending_adds (view);
199 		send_pending_changes (view);
200 		send_pending_removes (view);
201 	}
202 
203 	g_mutex_unlock (&view->priv->pending_mutex);
204 
205 	return FALSE;
206 }
207 
208 static void
ensure_pending_flush_timeout(EDataBookView * view)209 ensure_pending_flush_timeout (EDataBookView *view)
210 {
211 	if (view->priv->flush_id > 0)
212 		return;
213 
214 	view->priv->flush_id = e_named_timeout_add_seconds (
215 		THRESHOLD_SECONDS, pending_flush_timeout_cb, view);
216 }
217 
218 static gpointer
bookview_start_thread(gpointer data)219 bookview_start_thread (gpointer data)
220 {
221 	EDataBookView *view = data;
222 
223 	if (view->priv->running) {
224 		EBookBackend *backend = e_data_book_view_ref_backend (view);
225 
226 		if (backend) {
227 			/* To avoid race condition when one thread is starting the view, while
228 			   another thread wants to notify about created/modified/removed objects. */
229 			e_book_backend_sexp_lock (view->priv->sexp);
230 
231 			e_book_backend_start_view (backend, view);
232 
233 			e_book_backend_sexp_unlock (view->priv->sexp);
234 
235 			g_object_unref (backend);
236 		}
237 	}
238 
239 	g_object_unref (view);
240 
241 	return NULL;
242 }
243 
244 static gboolean
impl_DataBookView_start(EDBusAddressBookView * object,GDBusMethodInvocation * invocation,EDataBookView * view)245 impl_DataBookView_start (EDBusAddressBookView *object,
246                          GDBusMethodInvocation *invocation,
247                          EDataBookView *view)
248 {
249 	GThread *thread;
250 
251 	view->priv->running = TRUE;
252 	view->priv->complete = FALSE;
253 
254 	thread = g_thread_new (
255 		NULL, bookview_start_thread, g_object_ref (view));
256 	g_thread_unref (thread);
257 
258 	e_dbus_address_book_view_complete_start (object, invocation);
259 
260 	return TRUE;
261 }
262 
263 static gpointer
bookview_stop_thread(gpointer data)264 bookview_stop_thread (gpointer data)
265 {
266 	EDataBookView *view = data;
267 
268 	if (!view->priv->running) {
269 		EBookBackend *backend = e_data_book_view_ref_backend (view);
270 
271 		if (backend) {
272 			e_book_backend_stop_view (backend, view);
273 			g_object_unref (backend);
274 		}
275 	}
276 	g_object_unref (view);
277 
278 	return NULL;
279 }
280 
281 static gboolean
impl_DataBookView_stop(EDBusAddressBookView * object,GDBusMethodInvocation * invocation,EDataBookView * view)282 impl_DataBookView_stop (EDBusAddressBookView *object,
283                         GDBusMethodInvocation *invocation,
284                         EDataBookView *view)
285 {
286 	GThread *thread;
287 
288 	view->priv->running = FALSE;
289 	view->priv->complete = FALSE;
290 
291 	thread = g_thread_new (
292 		NULL, bookview_stop_thread, g_object_ref (view));
293 	g_thread_unref (thread);
294 
295 	e_dbus_address_book_view_complete_stop (object, invocation);
296 
297 	return TRUE;
298 }
299 
300 static gboolean
impl_DataBookView_setFlags(EDBusAddressBookView * object,GDBusMethodInvocation * invocation,EBookClientViewFlags flags,EDataBookView * view)301 impl_DataBookView_setFlags (EDBusAddressBookView *object,
302                             GDBusMethodInvocation *invocation,
303                             EBookClientViewFlags flags,
304                             EDataBookView *view)
305 {
306 	view->priv->flags = flags;
307 
308 	e_dbus_address_book_view_complete_set_flags (object, invocation);
309 
310 	return TRUE;
311 }
312 
313 static gboolean
impl_DataBookView_dispose(EDBusAddressBookView * object,GDBusMethodInvocation * invocation,EDataBookView * view)314 impl_DataBookView_dispose (EDBusAddressBookView *object,
315                            GDBusMethodInvocation *invocation,
316                            EDataBookView *view)
317 {
318 	EBookBackend *backend;
319 
320 	e_dbus_address_book_view_complete_dispose (object, invocation);
321 
322 	backend = e_data_book_view_ref_backend (view);
323 
324 	if (backend) {
325 		e_book_backend_stop_view (backend, view);
326 		view->priv->running = FALSE;
327 		e_book_backend_remove_view (backend, view);
328 
329 		g_object_unref (backend);
330 	} else {
331 		view->priv->running = FALSE;
332 	}
333 
334 	return TRUE;
335 }
336 
337 static gboolean
impl_DataBookView_set_fields_of_interest(EDBusAddressBookView * object,GDBusMethodInvocation * invocation,const gchar * const * in_fields_of_interest,EDataBookView * view)338 impl_DataBookView_set_fields_of_interest (EDBusAddressBookView *object,
339                                           GDBusMethodInvocation *invocation,
340                                           const gchar * const *in_fields_of_interest,
341                                           EDataBookView *view)
342 {
343 	gint ii;
344 
345 	g_return_val_if_fail (in_fields_of_interest != NULL, TRUE);
346 
347 	g_clear_pointer (&view->priv->fields_of_interest, g_hash_table_destroy);
348 
349 	view->priv->send_uids_only = FALSE;
350 
351 	for (ii = 0; in_fields_of_interest[ii]; ii++) {
352 		const gchar *field = in_fields_of_interest[ii];
353 
354 		if (!*field)
355 			continue;
356 
357 		if (strcmp (field, "x-evolution-uids-only") == 0) {
358 			view->priv->send_uids_only = TRUE;
359 			continue;
360 		}
361 
362 		if (view->priv->fields_of_interest == NULL)
363 			view->priv->fields_of_interest =
364 				g_hash_table_new_full (
365 					(GHashFunc) str_ic_hash,
366 					(GEqualFunc) str_ic_equal,
367 					(GDestroyNotify) g_free,
368 					(GDestroyNotify) NULL);
369 
370 		g_hash_table_insert (
371 			view->priv->fields_of_interest,
372 			g_strdup (field), GINT_TO_POINTER (1));
373 	}
374 
375 	e_dbus_address_book_view_complete_set_fields_of_interest (object, invocation);
376 
377 	return TRUE;
378 }
379 
380 static void
data_book_view_set_backend(EDataBookView * view,EBookBackend * backend)381 data_book_view_set_backend (EDataBookView *view,
382                             EBookBackend *backend)
383 {
384 	g_return_if_fail (E_IS_BOOK_BACKEND (backend));
385 
386 	g_weak_ref_set (&view->priv->backend_weakref, backend);
387 }
388 
389 static void
data_book_view_set_connection(EDataBookView * view,GDBusConnection * connection)390 data_book_view_set_connection (EDataBookView *view,
391                                GDBusConnection *connection)
392 {
393 	g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
394 	g_return_if_fail (view->priv->connection == NULL);
395 
396 	view->priv->connection = g_object_ref (connection);
397 }
398 
399 static void
data_book_view_set_object_path(EDataBookView * view,const gchar * object_path)400 data_book_view_set_object_path (EDataBookView *view,
401                                 const gchar *object_path)
402 {
403 	g_return_if_fail (object_path != NULL);
404 	g_return_if_fail (view->priv->object_path == NULL);
405 
406 	view->priv->object_path = g_strdup (object_path);
407 }
408 
409 static void
data_book_view_set_sexp(EDataBookView * view,EBookBackendSExp * sexp)410 data_book_view_set_sexp (EDataBookView *view,
411                          EBookBackendSExp *sexp)
412 {
413 	g_return_if_fail (E_IS_BOOK_BACKEND_SEXP (sexp));
414 	g_return_if_fail (view->priv->sexp == NULL);
415 
416 	view->priv->sexp = g_object_ref (sexp);
417 }
418 
419 static void
data_book_view_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)420 data_book_view_set_property (GObject *object,
421                              guint property_id,
422                              const GValue *value,
423                              GParamSpec *pspec)
424 {
425 	switch (property_id) {
426 		case PROP_BACKEND:
427 			data_book_view_set_backend (
428 				E_DATA_BOOK_VIEW (object),
429 				g_value_get_object (value));
430 			return;
431 
432 		case PROP_CONNECTION:
433 			data_book_view_set_connection (
434 				E_DATA_BOOK_VIEW (object),
435 				g_value_get_object (value));
436 			return;
437 
438 		case PROP_OBJECT_PATH:
439 			data_book_view_set_object_path (
440 				E_DATA_BOOK_VIEW (object),
441 				g_value_get_string (value));
442 			return;
443 
444 		case PROP_SEXP:
445 			data_book_view_set_sexp (
446 				E_DATA_BOOK_VIEW (object),
447 				g_value_get_object (value));
448 			return;
449 	}
450 
451 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
452 }
453 
454 static void
data_book_view_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)455 data_book_view_get_property (GObject *object,
456                              guint property_id,
457                              GValue *value,
458                              GParamSpec *pspec)
459 {
460 	switch (property_id) {
461 		case PROP_BACKEND:
462 			g_value_take_object (
463 				value,
464 				e_data_book_view_ref_backend (
465 				E_DATA_BOOK_VIEW (object)));
466 			return;
467 
468 		case PROP_CONNECTION:
469 			g_value_set_object (
470 				value,
471 				e_data_book_view_get_connection (
472 				E_DATA_BOOK_VIEW (object)));
473 			return;
474 
475 		case PROP_OBJECT_PATH:
476 			g_value_set_string (
477 				value,
478 				e_data_book_view_get_object_path (
479 				E_DATA_BOOK_VIEW (object)));
480 			return;
481 
482 		case PROP_SEXP:
483 			g_value_set_object (
484 				value,
485 				e_data_book_view_get_sexp (
486 				E_DATA_BOOK_VIEW (object)));
487 			return;
488 	}
489 
490 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
491 }
492 
493 static void
data_book_view_dispose(GObject * object)494 data_book_view_dispose (GObject *object)
495 {
496 	EDataBookViewPrivate *priv;
497 
498 	priv = E_DATA_BOOK_VIEW (object)->priv;
499 
500 	g_mutex_lock (&priv->pending_mutex);
501 
502 	if (priv->flush_id > 0) {
503 		g_source_remove (priv->flush_id);
504 		priv->flush_id = 0;
505 	}
506 
507 	g_mutex_unlock (&priv->pending_mutex);
508 
509 	g_clear_object (&priv->connection);
510 	g_clear_object (&priv->dbus_object);
511 	g_clear_object (&priv->sexp);
512 
513 	g_weak_ref_set (&priv->backend_weakref, NULL);
514 
515 	/* Chain up to parent's dispose() method. */
516 	G_OBJECT_CLASS (e_data_book_view_parent_class)->dispose (object);
517 }
518 
519 static void
data_book_view_finalize(GObject * object)520 data_book_view_finalize (GObject *object)
521 {
522 	EDataBookViewPrivate *priv;
523 
524 	priv = E_DATA_BOOK_VIEW (object)->priv;
525 
526 	g_free (priv->object_path);
527 
528 	reset_array (priv->adds);
529 	reset_array (priv->changes);
530 	reset_array (priv->removes);
531 	g_array_free (priv->adds, TRUE);
532 	g_array_free (priv->changes, TRUE);
533 	g_array_free (priv->removes, TRUE);
534 
535 	if (priv->fields_of_interest)
536 		g_hash_table_destroy (priv->fields_of_interest);
537 
538 	g_mutex_clear (&priv->pending_mutex);
539 	g_weak_ref_clear (&priv->backend_weakref);
540 
541 	g_hash_table_destroy (priv->ids);
542 
543 	/* Chain up to parent's finalize() method. */
544 	G_OBJECT_CLASS (e_data_book_view_parent_class)->finalize (object);
545 }
546 
547 static gboolean
data_book_view_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)548 data_book_view_initable_init (GInitable *initable,
549                               GCancellable *cancellable,
550                               GError **error)
551 {
552 	EDataBookView *view;
553 
554 	view = E_DATA_BOOK_VIEW (initable);
555 
556 	return g_dbus_interface_skeleton_export (
557 		G_DBUS_INTERFACE_SKELETON (view->priv->dbus_object),
558 		view->priv->connection,
559 		view->priv->object_path,
560 		error);
561 }
562 
563 static void
e_data_book_view_class_init(EDataBookViewClass * class)564 e_data_book_view_class_init (EDataBookViewClass *class)
565 {
566 	GObjectClass *object_class;
567 
568 	object_class = G_OBJECT_CLASS (class);
569 	object_class->set_property = data_book_view_set_property;
570 	object_class->get_property = data_book_view_get_property;
571 	object_class->dispose = data_book_view_dispose;
572 	object_class->finalize = data_book_view_finalize;
573 
574 	g_object_class_install_property (
575 		object_class,
576 		PROP_BACKEND,
577 		g_param_spec_object (
578 			"backend",
579 			"Backend",
580 			"The backend being monitored",
581 			E_TYPE_BOOK_BACKEND,
582 			G_PARAM_READWRITE |
583 			G_PARAM_CONSTRUCT_ONLY |
584 			G_PARAM_STATIC_STRINGS));
585 
586 	g_object_class_install_property (
587 		object_class,
588 		PROP_CONNECTION,
589 		g_param_spec_object (
590 			"connection",
591 			"Connection",
592 			"The GDBusConnection on which "
593 			"to export the view interface",
594 			G_TYPE_DBUS_CONNECTION,
595 			G_PARAM_READWRITE |
596 			G_PARAM_CONSTRUCT_ONLY |
597 			G_PARAM_STATIC_STRINGS));
598 
599 	g_object_class_install_property (
600 		object_class,
601 		PROP_OBJECT_PATH,
602 		g_param_spec_string (
603 			"object-path",
604 			"Object Path",
605 			"The object path at which to "
606 			"export the view interface",
607 			NULL,
608 			G_PARAM_READWRITE |
609 			G_PARAM_CONSTRUCT_ONLY |
610 			G_PARAM_STATIC_STRINGS));
611 
612 	g_object_class_install_property (
613 		object_class,
614 		PROP_SEXP,
615 		g_param_spec_object (
616 			"sexp",
617 			"S-Expression",
618 			"The query expression for this view",
619 			E_TYPE_BOOK_BACKEND_SEXP,
620 			G_PARAM_READWRITE |
621 			G_PARAM_CONSTRUCT_ONLY |
622 			G_PARAM_STATIC_STRINGS));
623 }
624 
625 static void
e_data_book_view_initable_init(GInitableIface * iface)626 e_data_book_view_initable_init (GInitableIface *iface)
627 {
628 	iface->init = data_book_view_initable_init;
629 }
630 
631 static void
e_data_book_view_init(EDataBookView * view)632 e_data_book_view_init (EDataBookView *view)
633 {
634 	view->priv = e_data_book_view_get_instance_private (view);
635 
636 	g_weak_ref_init (&view->priv->backend_weakref, NULL);
637 
638 	view->priv->flags = E_BOOK_CLIENT_VIEW_FLAGS_NOTIFY_INITIAL;
639 
640 	view->priv->dbus_object = e_dbus_address_book_view_skeleton_new ();
641 	g_signal_connect (
642 		view->priv->dbus_object, "handle-start",
643 		G_CALLBACK (impl_DataBookView_start), view);
644 	g_signal_connect (
645 		view->priv->dbus_object, "handle-stop",
646 		G_CALLBACK (impl_DataBookView_stop), view);
647 	g_signal_connect (
648 		view->priv->dbus_object, "handle-set-flags",
649 		G_CALLBACK (impl_DataBookView_setFlags), view);
650 	g_signal_connect (
651 		view->priv->dbus_object, "handle-dispose",
652 		G_CALLBACK (impl_DataBookView_dispose), view);
653 	g_signal_connect (
654 		view->priv->dbus_object, "handle-set-fields-of-interest",
655 		G_CALLBACK (impl_DataBookView_set_fields_of_interest), view);
656 
657 	view->priv->fields_of_interest = NULL;
658 	view->priv->running = FALSE;
659 	view->priv->complete = FALSE;
660 	g_mutex_init (&view->priv->pending_mutex);
661 
662 	/* THRESHOLD_ITEMS * 2 because we store UID and vcard */
663 	view->priv->adds = g_array_sized_new (
664 		TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS * 2);
665 	view->priv->changes = g_array_sized_new (
666 		TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS * 2);
667 	view->priv->removes = g_array_sized_new (
668 		TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS);
669 
670 	view->priv->ids = g_hash_table_new_full (
671 		(GHashFunc) g_str_hash,
672 		(GEqualFunc) g_str_equal,
673 		(GDestroyNotify) g_free,
674 		(GDestroyNotify) NULL);
675 
676 	view->priv->flush_id = 0;
677 }
678 
679 /**
680  * e_data_book_view_new:
681  * @backend: (type EBookBackend): an #EBookBackend
682  * @sexp: an #EBookBackendSExp
683  * @connection: a #GDBusConnection
684  * @object_path: an object path for the view
685  * @error: return location for a #GError, or %NULL
686  *
687  * Creates a new #EDataBookView and exports its D-Bus interface on
688  * @connection at @object_path.  If an error occurs while exporting,
689  * the function sets @error and returns %NULL.
690  *
691  * Returns: an #EDataBookView
692  */
693 EDataBookView *
e_data_book_view_new(EBookBackend * backend,EBookBackendSExp * sexp,GDBusConnection * connection,const gchar * object_path,GError ** error)694 e_data_book_view_new (EBookBackend *backend,
695                       EBookBackendSExp *sexp,
696                       GDBusConnection *connection,
697                       const gchar *object_path,
698                       GError **error)
699 {
700 	g_return_val_if_fail (E_IS_BOOK_BACKEND (backend), NULL);
701 	g_return_val_if_fail (E_IS_BOOK_BACKEND_SEXP (sexp), NULL);
702 	g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
703 	g_return_val_if_fail (object_path != NULL, NULL);
704 
705 	return g_initable_new (
706 		E_TYPE_DATA_BOOK_VIEW, NULL, error,
707 		"backend", backend,
708 		"connection", connection,
709 		"object-path", object_path,
710 		"sexp", sexp, NULL);
711 }
712 
713 /**
714  * e_data_book_view_ref_backend:
715  * @view: an #EDataBookView
716  *
717  * Refs the backend that @view is querying. Unref the returned backend,
718  * if not %NULL, with g_object_unref(), when no longer needed.
719  *
720  * Returns: (type EBookBackend) (transfer full) (nullable): The associated #EBookBackend.
721  *
722  * Since: 3.34
723  **/
724 EBookBackend *
e_data_book_view_ref_backend(EDataBookView * view)725 e_data_book_view_ref_backend (EDataBookView *view)
726 {
727 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
728 
729 	return g_weak_ref_get (&view->priv->backend_weakref);
730 }
731 
732 /**
733  * e_data_book_view_get_backend:
734  * @view: an #EDataBookView
735  *
736  * Gets the backend that @view is querying.
737  *
738  * Returns: (type EBookBackend) (transfer none): The associated #EBookBackend.
739  *
740  * Deprecated: 3.34: Use e_data_book_view_ref_backend() instead.
741  **/
742 EBookBackend *
e_data_book_view_get_backend(EDataBookView * view)743 e_data_book_view_get_backend (EDataBookView *view)
744 {
745 	EBookBackend *backend;
746 
747 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
748 
749 	backend = e_data_book_view_ref_backend (view);
750 	if (backend)
751 		g_object_unref (backend);
752 
753 	return backend;
754 }
755 
756 /**
757  * e_data_book_view_get_sexp:
758  * @view: an #EDataBookView
759  *
760  * Gets the s-expression used for matching contacts to @view.
761  *
762  * Returns: (transfer none): The #EBookBackendSExp used.
763  *
764  * Since: 3.8
765  **/
766 EBookBackendSExp *
e_data_book_view_get_sexp(EDataBookView * view)767 e_data_book_view_get_sexp (EDataBookView *view)
768 {
769 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
770 
771 	return view->priv->sexp;
772 }
773 
774 /**
775  * e_data_book_view_get_connection:
776  * @view: an #EDataBookView
777  *
778  * Returns the #GDBusConnection on which the AddressBookView D-Bus
779  * interface is exported.
780  *
781  * Returns: (transfer none): the #GDBusConnection
782  *
783  * Since: 3.8
784  **/
785 GDBusConnection *
e_data_book_view_get_connection(EDataBookView * view)786 e_data_book_view_get_connection (EDataBookView *view)
787 {
788 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
789 
790 	return view->priv->connection;
791 }
792 
793 /**
794  * e_data_book_view_get_object_path:
795  * @view: an #EDataBookView
796  *
797  * Returns the object path at which the AddressBookView D-Bus interface
798  * is exported.
799  *
800  * Returns: the object path
801  *
802  * Since: 3.8
803  **/
804 const gchar *
e_data_book_view_get_object_path(EDataBookView * view)805 e_data_book_view_get_object_path (EDataBookView *view)
806 {
807 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
808 
809 	return view->priv->object_path;
810 }
811 
812 /**
813  * e_data_book_view_get_flags:
814  * @view: an #EDataBookView
815  *
816  * Gets the #EBookClientViewFlags that control the behaviour of @view.
817  *
818  * Returns: the flags for @view.
819  *
820  * Since: 3.4
821  **/
822 EBookClientViewFlags
e_data_book_view_get_flags(EDataBookView * view)823 e_data_book_view_get_flags (EDataBookView *view)
824 {
825 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), 0);
826 
827 	return view->priv->flags;
828 }
829 
830 /**
831  * e_data_book_view_is_completed:
832  * @view: an #EDataBookView
833  *
834  * Returns: whether the @view had been completed; that is,
835  *    whether e_data_book_view_notify_complete() had been called
836  *    since the @view had been started.
837  *
838  * Since: 3.34
839  **/
840 gboolean
e_data_book_view_is_completed(EDataBookView * view)841 e_data_book_view_is_completed (EDataBookView *view)
842 {
843 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), FALSE);
844 
845 	return view->priv->complete;
846 }
847 
848 /*
849  * Queue @vcard to be sent as a change notification.
850  */
851 static void
notify_change(EDataBookView * view,const gchar * id,const gchar * vcard)852 notify_change (EDataBookView *view,
853                const gchar *id,
854                const gchar *vcard)
855 {
856 	gchar *utf8_vcard, *utf8_id;
857 
858 	send_pending_adds (view);
859 	send_pending_removes (view);
860 
861 	if (view->priv->changes->len == THRESHOLD_ITEMS * 2) {
862 		send_pending_changes (view);
863 	}
864 
865 	if (view->priv->send_uids_only == FALSE) {
866 		utf8_vcard = e_util_utf8_make_valid (vcard);
867 		g_array_append_val (view->priv->changes, utf8_vcard);
868 	}
869 
870 	utf8_id = e_util_utf8_make_valid (id);
871 	g_array_append_val (view->priv->changes, utf8_id);
872 
873 	ensure_pending_flush_timeout (view);
874 }
875 
876 /*
877  * Queue @id to be sent as a change notification.
878  */
879 static void
notify_remove(EDataBookView * view,const gchar * id)880 notify_remove (EDataBookView *view,
881                const gchar *id)
882 {
883 	gchar *valid_id;
884 
885 	send_pending_adds (view);
886 	send_pending_changes (view);
887 
888 	if (view->priv->removes->len == THRESHOLD_ITEMS) {
889 		send_pending_removes (view);
890 	}
891 
892 	valid_id = e_util_utf8_make_valid (id);
893 	g_array_append_val (view->priv->removes, valid_id);
894 	g_hash_table_remove (view->priv->ids, valid_id);
895 
896 	ensure_pending_flush_timeout (view);
897 }
898 
899 /*
900  * Queue @id and @vcard to be sent as a change notification.
901  */
902 static void
notify_add(EDataBookView * view,const gchar * id,const gchar * vcard)903 notify_add (EDataBookView *view,
904             const gchar *id,
905             const gchar *vcard)
906 {
907 	EBookClientViewFlags flags;
908 	gchar *utf8_vcard, *utf8_id;
909 
910 	send_pending_changes (view);
911 	send_pending_removes (view);
912 
913 	utf8_id = e_util_utf8_make_valid (id);
914 
915 	/* Do not send contact add notifications during initial stage */
916 	flags = e_data_book_view_get_flags (view);
917 	if (view->priv->complete || (flags & E_BOOK_CLIENT_VIEW_FLAGS_NOTIFY_INITIAL) != 0) {
918 		gchar *utf8_id_copy = g_strdup (utf8_id);
919 
920 		if (view->priv->adds->len == THRESHOLD_ITEMS) {
921 			send_pending_adds (view);
922 		}
923 
924 		if (view->priv->send_uids_only == FALSE) {
925 			utf8_vcard = e_util_utf8_make_valid (vcard);
926 			g_array_append_val (view->priv->adds, utf8_vcard);
927 		}
928 
929 		g_array_append_val (view->priv->adds, utf8_id_copy);
930 
931 		ensure_pending_flush_timeout (view);
932 	}
933 
934 	g_hash_table_insert (view->priv->ids, utf8_id, GUINT_TO_POINTER (1));
935 }
936 
937 static gboolean
id_is_in_view(EDataBookView * view,const gchar * id)938 id_is_in_view (EDataBookView *view,
939                const gchar *id)
940 {
941 	gchar *valid_id;
942 	gboolean res;
943 
944 	g_return_val_if_fail (view != NULL, FALSE);
945 	g_return_val_if_fail (id != NULL, FALSE);
946 
947 	valid_id = e_util_utf8_make_valid (id);
948 	res = g_hash_table_lookup (view->priv->ids, valid_id) != NULL;
949 	g_free (valid_id);
950 
951 	return res;
952 }
953 
954 /**
955  * e_data_book_view_notify_update:
956  * @view: an #EDataBookView
957  * @contact: an #EContact
958  *
959  * Notify listeners that @contact has changed. This can
960  * trigger an add, change or removal event depending on
961  * whether the change causes the contact to start matching,
962  * no longer match, or stay matching the query specified
963  * by @view.
964  **/
965 void
e_data_book_view_notify_update(EDataBookView * view,const EContact * contact)966 e_data_book_view_notify_update (EDataBookView *view,
967                                 const EContact *contact)
968 {
969 	gboolean currently_in_view, want_in_view;
970 	const gchar *id;
971 	gchar *vcard;
972 
973 	g_return_if_fail (E_IS_DATA_BOOK_VIEW (view));
974 	g_return_if_fail (E_IS_CONTACT (contact));
975 
976 	if (!view->priv->running)
977 		return;
978 
979 	g_mutex_lock (&view->priv->pending_mutex);
980 
981 	id = e_contact_get_const ((EContact *) contact, E_CONTACT_UID);
982 
983 	currently_in_view = id_is_in_view (view, id);
984 	want_in_view = e_book_backend_sexp_match_contact (
985 		view->priv->sexp, (EContact *) contact);
986 
987 	if (want_in_view) {
988 		vcard = e_vcard_to_string (
989 			E_VCARD (contact),
990 			EVC_FORMAT_VCARD_30);
991 
992 		if (currently_in_view)
993 			notify_change (view, id, vcard);
994 		else
995 			notify_add (view, id, vcard);
996 
997 		g_free (vcard);
998 	} else {
999 		if (currently_in_view)
1000 			notify_remove (view, id);
1001 		/* else nothing; we're removing a card that wasn't there */
1002 	}
1003 
1004 	g_mutex_unlock (&view->priv->pending_mutex);
1005 }
1006 
1007 /**
1008  * e_data_book_view_notify_update_vcard:
1009  * @view: an #EDataBookView
1010  * @id: a unique id of the @vcard
1011  * @vcard: a plain vCard
1012  *
1013  * Notify listeners that @vcard has changed. This can
1014  * trigger an add, change or removal event depending on
1015  * whether the change causes the contact to start matching,
1016  * no longer match, or stay matching the query specified
1017  * by @view.  This method should be preferred over
1018  * e_data_book_view_notify_update() when the native
1019  * representation of a contact is a vCard.
1020  **/
1021 void
e_data_book_view_notify_update_vcard(EDataBookView * view,const gchar * id,const gchar * vcard)1022 e_data_book_view_notify_update_vcard (EDataBookView *view,
1023                                       const gchar *id,
1024                                       const gchar *vcard)
1025 {
1026 	gboolean currently_in_view, want_in_view;
1027 	EContact *contact;
1028 
1029 	g_return_if_fail (E_IS_DATA_BOOK_VIEW (view));
1030 	g_return_if_fail (id != NULL);
1031 	g_return_if_fail (vcard != NULL);
1032 
1033 	if (!view->priv->running)
1034 		return;
1035 
1036 	g_mutex_lock (&view->priv->pending_mutex);
1037 
1038 	contact = e_contact_new_from_vcard_with_uid (vcard, id);
1039 	currently_in_view = id_is_in_view (view, id);
1040 	want_in_view = e_book_backend_sexp_match_contact (
1041 		view->priv->sexp, contact);
1042 
1043 	if (want_in_view) {
1044 		if (currently_in_view)
1045 			notify_change (view, id, vcard);
1046 		else
1047 			notify_add (view, id, vcard);
1048 	} else {
1049 		if (currently_in_view)
1050 			notify_remove (view, id);
1051 	}
1052 
1053 	/* Do this last so that id is still valid when notify_ is called */
1054 	g_object_unref (contact);
1055 
1056 	g_mutex_unlock (&view->priv->pending_mutex);
1057 }
1058 
1059 /**
1060  * e_data_book_view_notify_update_prefiltered_vcard:
1061  * @view: an #EDataBookView
1062  * @id: the UID of this contact
1063  * @vcard: a plain vCard
1064  *
1065  * Notify listeners that @vcard has changed. This can
1066  * trigger an add, change or removal event depending on
1067  * whether the change causes the contact to start matching,
1068  * no longer match, or stay matching the query specified
1069  * by @view.  This method should be preferred over
1070  * e_data_book_view_notify_update() when the native
1071  * representation of a contact is a vCard.
1072  *
1073  * The important difference between this method and
1074  * e_data_book_view_notify_update() and
1075  * e_data_book_view_notify_update_vcard() is
1076  * that it doesn't match the contact against the book view query to see if it
1077  * should be included, it assumes that this has been done and the contact is
1078  * known to exist in the view.
1079  **/
1080 void
e_data_book_view_notify_update_prefiltered_vcard(EDataBookView * view,const gchar * id,const gchar * vcard)1081 e_data_book_view_notify_update_prefiltered_vcard (EDataBookView *view,
1082                                                   const gchar *id,
1083                                                   const gchar *vcard)
1084 {
1085 	gboolean currently_in_view;
1086 
1087 	g_return_if_fail (E_IS_DATA_BOOK_VIEW (view));
1088 	g_return_if_fail (id != NULL);
1089 	g_return_if_fail (vcard != NULL);
1090 
1091 	if (!view->priv->running)
1092 		return;
1093 
1094 	g_mutex_lock (&view->priv->pending_mutex);
1095 
1096 	currently_in_view = id_is_in_view (view, id);
1097 
1098 	if (currently_in_view)
1099 		notify_change (view, id, vcard);
1100 	else
1101 		notify_add (view, id, vcard);
1102 
1103 	g_mutex_unlock (&view->priv->pending_mutex);
1104 }
1105 
1106 /**
1107  * e_data_book_view_notify_remove:
1108  * @view: an #EDataBookView
1109  * @id: a unique contact ID
1110  *
1111  * Notify listeners that a contact specified by @id
1112  * was removed from @view.
1113  **/
1114 void
e_data_book_view_notify_remove(EDataBookView * view,const gchar * id)1115 e_data_book_view_notify_remove (EDataBookView *view,
1116                                 const gchar *id)
1117 {
1118 	g_return_if_fail (E_IS_DATA_BOOK_VIEW (view));
1119 	g_return_if_fail (id != NULL);
1120 
1121 	if (!view->priv->running)
1122 		return;
1123 
1124 	g_mutex_lock (&view->priv->pending_mutex);
1125 
1126 	if (id_is_in_view (view, id))
1127 		notify_remove (view, id);
1128 
1129 	g_mutex_unlock (&view->priv->pending_mutex);
1130 }
1131 
1132 /**
1133  * e_data_book_view_notify_complete:
1134  * @view: an #EDataBookView
1135  * @error: the error of the query, if any
1136  *
1137  * Notifies listeners that all pending updates on @view
1138  * have been sent. The listener's information should now be
1139  * in sync with the backend's.
1140  **/
1141 void
e_data_book_view_notify_complete(EDataBookView * view,const GError * error)1142 e_data_book_view_notify_complete (EDataBookView *view,
1143                                   const GError *error)
1144 {
1145 	gchar *error_name, *error_message;
1146 
1147 	g_return_if_fail (E_IS_DATA_BOOK_VIEW (view));
1148 
1149 	if (!view->priv->running)
1150 		return;
1151 
1152 	/* View is complete */
1153 	view->priv->complete = TRUE;
1154 
1155 	g_mutex_lock (&view->priv->pending_mutex);
1156 
1157 	send_pending_adds (view);
1158 	send_pending_changes (view);
1159 	send_pending_removes (view);
1160 
1161 	g_mutex_unlock (&view->priv->pending_mutex);
1162 
1163 	if (error) {
1164 		gchar *dbus_error_name = g_dbus_error_encode_gerror (error);
1165 
1166 		error_name = e_util_utf8_make_valid (dbus_error_name ? dbus_error_name : "");
1167 		error_message = e_util_utf8_make_valid (error->message);
1168 
1169 		g_free (dbus_error_name);
1170 	} else {
1171 		error_name = g_strdup ("");
1172 		error_message = g_strdup ("");
1173 	}
1174 
1175 	e_dbus_address_book_view_emit_complete (
1176 		view->priv->dbus_object,
1177 		error_name,
1178 		error_message);
1179 
1180 	g_free (error_name);
1181 	g_free (error_message);
1182 }
1183 
1184 /**
1185  * e_data_book_view_notify_progress:
1186  * @view: an #EDataBookView
1187  * @percent: percent done; use -1 when not available
1188  * @message: a text message
1189  *
1190  * Provides listeners with a human-readable text describing the
1191  * current backend operation. This can be used for progress
1192  * reporting.
1193  *
1194  * Since: 3.2
1195  **/
1196 void
e_data_book_view_notify_progress(EDataBookView * view,guint percent,const gchar * message)1197 e_data_book_view_notify_progress (EDataBookView *view,
1198                                   guint percent,
1199                                   const gchar *message)
1200 {
1201 	gchar *dbus_message = NULL;
1202 
1203 	g_return_if_fail (E_IS_DATA_BOOK_VIEW (view));
1204 
1205 	if (!view->priv->running)
1206 		return;
1207 
1208 	e_dbus_address_book_view_emit_progress (
1209 		view->priv->dbus_object, percent,
1210 		e_util_ensure_gdbus_string (message, &dbus_message));
1211 
1212 	g_free (dbus_message);
1213 }
1214 
1215 /**
1216  * e_data_book_view_get_fields_of_interest:
1217  * @view: an #EDataBookView
1218  *
1219  * Returns: (transfer none) (element-type utf8 gint) (nullable): Hash table of field names which
1220  * the listener is interested in. Backends can return fully populated objects, but the listener
1221  * advertised that it will use only these. Returns %NULL for all available fields.
1222  *
1223  * Note: The data pointer in the hash table has no special meaning, it's
1224  * only GINT_TO_POINTER(1) for easier checking. Also, field names are
1225  * compared case insensitively.
1226  **/
1227 GHashTable *
e_data_book_view_get_fields_of_interest(EDataBookView * view)1228 e_data_book_view_get_fields_of_interest (EDataBookView *view)
1229 {
1230 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
1231 
1232 	return view->priv->fields_of_interest;
1233 }
1234 
1235