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