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