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