Camel: Allow SSL certificate resave and use detailed errors from SSL stream
[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 #include <libebook/e-contact.h>
29 #include "libedataserver/e-data-server-util.h"
30 #include "e-data-book-view.h"
31
32 #include "e-gdbus-book-view.h"
33
34 #define E_DATA_BOOK_VIEW_GET_PRIVATE(obj) \
35         (G_TYPE_INSTANCE_GET_PRIVATE \
36         ((obj), E_TYPE_DATA_BOOK_VIEW, EDataBookViewPrivate))
37
38 static void reset_array (GArray *array);
39 static void ensure_pending_flush_timeout (EDataBookView *view);
40
41 G_DEFINE_TYPE (EDataBookView, e_data_book_view, G_TYPE_OBJECT);
42 #define THRESHOLD_ITEMS   32    /* how many items can be hold in a cache, before propagated to UI */
43 #define THRESHOLD_SECONDS  2    /* how long to wait until notifications are propagated to UI; in seconds */
44
45 struct _EDataBookViewPrivate {
46         EGdbusBookView *gdbus_object;
47
48         EDataBook *book;
49         EBookBackend *backend;
50
51         gchar * card_query;
52         EBookBackendSExp *card_sexp;
53         EBookClientViewFlags flags;
54
55         gboolean running;
56         gboolean complete;
57         GMutex *pending_mutex;
58
59         GArray *adds;
60         GArray *changes;
61         GArray *removes;
62
63         GHashTable *ids;
64         guint idle_id;
65
66         guint flush_id;
67
68         /* which fields is listener interested in */
69         GHashTable *fields_of_interest;
70 };
71
72 static void e_data_book_view_dispose (GObject *object);
73 static void e_data_book_view_finalize (GObject *object);
74
75 static void
76 e_data_book_view_class_init (EDataBookViewClass *class)
77 {
78         GObjectClass *object_class = G_OBJECT_CLASS (class);
79
80         g_type_class_add_private (class, sizeof (EDataBookViewPrivate));
81
82         object_class->dispose = e_data_book_view_dispose;
83         object_class->finalize = e_data_book_view_finalize;
84 }
85
86 static guint
87 str_ic_hash (gconstpointer key)
88 {
89         guint32 hash = 5381;
90         const gchar *str = key;
91         gint ii;
92
93         if (!str)
94                 return hash;
95
96         for (ii = 0; str[ii]; ii++) {
97                 hash = hash * 33 + g_ascii_tolower (str[ii]);
98         }
99
100         return hash;
101 }
102
103 static gboolean
104 str_ic_equal (gconstpointer a,
105               gconstpointer b)
106 {
107         const gchar *stra = a, *strb = b;
108         gint ii;
109
110         if (!stra && !strb)
111                 return TRUE;
112
113         if (!stra || !strb)
114                 return FALSE;
115
116         for (ii = 0; stra[ii] && strb[ii]; ii++) {
117                 if (g_ascii_tolower (stra[ii]) != g_ascii_tolower (strb[ii]))
118                         return FALSE;
119         }
120
121         return stra[ii] == strb[ii];
122 }
123
124 /**
125  * e_data_book_view_register_gdbus_object:
126  *
127  * Since: 2.32
128  **/
129 guint
130 e_data_book_view_register_gdbus_object (EDataBookView *query,
131                                         GDBusConnection *connection,
132                                         const gchar *object_path,
133                                         GError **error)
134 {
135         g_return_val_if_fail (query != NULL, 0);
136         g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (query), 0);
137         g_return_val_if_fail (connection != NULL, 0);
138         g_return_val_if_fail (object_path != NULL, 0);
139
140         return e_gdbus_book_view_register_object (query->priv->gdbus_object, connection, object_path, error);
141 }
142
143 static void
144 book_destroyed_cb (gpointer data,
145                    GObject *dead)
146 {
147         EDataBookView *view = E_DATA_BOOK_VIEW (data);
148         EDataBookViewPrivate *priv = view->priv;
149
150         /* The book has just died, so unset the pointer so we don't try and remove a
151          * dead weak reference. */
152         view->priv->book = NULL;
153
154         /* If the view is running stop it here. */
155         if (priv->running) {
156                 e_book_backend_stop_book_view (priv->backend, view);
157                 priv->running = FALSE;
158                 priv->complete = FALSE;
159         }
160 }
161
162 static void
163 send_pending_adds (EDataBookView *view)
164 {
165         EDataBookViewPrivate *priv = view->priv;
166
167         if (priv->adds->len == 0)
168                 return;
169
170         e_gdbus_book_view_emit_objects_added (view->priv->gdbus_object, (const gchar * const *) priv->adds->data);
171         reset_array (priv->adds);
172 }
173
174 static void
175 send_pending_changes (EDataBookView *view)
176 {
177         EDataBookViewPrivate *priv = view->priv;
178
179         if (priv->changes->len == 0)
180                 return;
181
182         e_gdbus_book_view_emit_objects_modified (view->priv->gdbus_object, (const gchar * const *) priv->changes->data);
183         reset_array (priv->changes);
184 }
185
186 static void
187 send_pending_removes (EDataBookView *view)
188 {
189         EDataBookViewPrivate *priv = view->priv;
190
191         if (priv->removes->len == 0)
192                 return;
193
194         e_gdbus_book_view_emit_objects_removed (view->priv->gdbus_object, (const gchar * const *) priv->removes->data);
195         reset_array (priv->removes);
196 }
197
198 static gboolean
199 pending_flush_timeout_cb (gpointer data)
200 {
201         EDataBookView *view = data;
202         EDataBookViewPrivate *priv = view->priv;
203
204         g_mutex_lock (priv->pending_mutex);
205
206         priv->flush_id = 0;
207
208         send_pending_adds (view);
209         send_pending_changes (view);
210         send_pending_removes (view);
211
212         g_mutex_unlock (priv->pending_mutex);
213
214         return FALSE;
215 }
216
217 static void
218 ensure_pending_flush_timeout (EDataBookView *view)
219 {
220         EDataBookViewPrivate *priv = view->priv;
221
222         if (priv->flush_id)
223                 return;
224
225         priv->flush_id = g_timeout_add_seconds (THRESHOLD_SECONDS, pending_flush_timeout_cb, view);
226 }
227
228 /*
229  * Queue @vcard to be sent as a change notification.
230  */
231 static void
232 notify_change (EDataBookView *view,
233                const gchar *id,
234                const gchar *vcard)
235 {
236         EDataBookViewPrivate *priv = view->priv;
237         gchar *utf8_vcard, *utf8_id;
238
239         send_pending_adds (view);
240         send_pending_removes (view);
241
242         if (priv->changes->len == THRESHOLD_ITEMS * 2) {
243                 send_pending_changes (view);
244         }
245
246         utf8_vcard = e_util_utf8_make_valid (vcard);
247         utf8_id = e_util_utf8_make_valid (id);
248
249         g_array_append_val (priv->changes, utf8_vcard);
250         g_array_append_val (priv->changes, utf8_id);
251
252         ensure_pending_flush_timeout (view);
253 }
254
255 /*
256  * Queue @id to be sent as a change notification.
257  */
258 static void
259 notify_remove (EDataBookView *view,
260                const gchar *id)
261 {
262         EDataBookViewPrivate *priv = view->priv;
263         gchar *valid_id;
264
265         send_pending_adds (view);
266         send_pending_changes (view);
267
268         if (priv->removes->len == THRESHOLD_ITEMS) {
269                 send_pending_removes (view);
270         }
271
272         valid_id = e_util_utf8_make_valid (id);
273         g_array_append_val (priv->removes, valid_id);
274         g_hash_table_remove (priv->ids, valid_id);
275
276         ensure_pending_flush_timeout (view);
277 }
278
279 /*
280  * Queue @id and @vcard to be sent as a change notification.
281  */
282 static void
283 notify_add (EDataBookView *view,
284             const gchar *id,
285             const gchar *vcard)
286 {
287         EBookClientViewFlags flags;
288         EDataBookViewPrivate *priv = view->priv;
289         gchar *utf8_vcard, *utf8_id;
290
291         send_pending_changes (view);
292         send_pending_removes (view);
293
294         utf8_id = e_util_utf8_make_valid (id);
295
296         /* Do not send contact add notifications during initial stage */
297         flags = e_data_book_view_get_flags (view);
298         if (priv->complete || (flags & E_BOOK_CLIENT_VIEW_FLAGS_NOTIFY_INITIAL) != 0) {
299                 gchar *utf8_id_copy = g_strdup (utf8_id);
300
301                 if (priv->adds->len == THRESHOLD_ITEMS) {
302                         send_pending_adds (view);
303                 }
304
305                 utf8_vcard = e_util_utf8_make_valid (vcard);
306
307                 g_array_append_val (priv->adds, utf8_vcard);
308                 g_array_append_val (priv->adds, utf8_id_copy);
309
310                 ensure_pending_flush_timeout (view);
311         }
312
313         g_hash_table_insert (priv->ids, utf8_id,
314                              GUINT_TO_POINTER (1));
315 }
316
317 static gboolean
318 impl_DataBookView_set_fields_of_interest (EGdbusBookView *object,
319                                           GDBusMethodInvocation *invocation,
320                                           const gchar * const *in_fields_of_interest,
321                                           EDataBookView *view)
322 {
323         EDataBookViewPrivate *priv;
324         gint ii;
325
326         g_return_val_if_fail (in_fields_of_interest != NULL, TRUE);
327
328         priv = view->priv;
329
330         if (priv->fields_of_interest)
331                 g_hash_table_destroy (priv->fields_of_interest);
332         priv->fields_of_interest = NULL;
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 (!priv->fields_of_interest)
341                         priv->fields_of_interest = g_hash_table_new_full (str_ic_hash, str_ic_equal, g_free, NULL);
342
343                 g_hash_table_insert (priv->fields_of_interest, g_strdup (field), GINT_TO_POINTER (1));
344         }
345
346         e_gdbus_book_view_complete_set_fields_of_interest (object, invocation, NULL);
347
348         return TRUE;
349 }
350
351 static void
352 reset_array (GArray *array)
353 {
354         gint i = 0;
355         gchar *tmp = NULL;
356
357         /* Free stored strings */
358         for (i = 0; i < array->len; i++) {
359                 tmp = g_array_index (array, gchar *, i);
360                 g_free (tmp);
361         }
362
363         /* Force the array size to 0 */
364         g_array_set_size (array, 0);
365 }
366
367 static gboolean
368 id_is_in_view (EDataBookView *book_view,
369                const gchar *id)
370 {
371         gchar *valid_id;
372         gboolean res;
373
374         g_return_val_if_fail (book_view != NULL, FALSE);
375         g_return_val_if_fail (id != NULL, FALSE);
376
377         valid_id = e_util_utf8_make_valid (id);
378         res = g_hash_table_lookup (book_view->priv->ids, valid_id) != NULL;
379         g_free (valid_id);
380
381         return res;
382 }
383
384 /**
385  * e_data_book_view_notify_update:
386  * @book_view: an #EDataBookView
387  * @contact: an #EContact
388  *
389  * Notify listeners that @contact has changed. This can
390  * trigger an add, change or removal event depending on
391  * whether the change causes the contact to start matching,
392  * no longer match, or stay matching the query specified
393  * by @book_view.
394  **/
395 void
396 e_data_book_view_notify_update (EDataBookView *book_view,
397                                 const EContact *contact)
398 {
399         EDataBookViewPrivate *priv = book_view->priv;
400         gboolean currently_in_view, want_in_view;
401         const gchar *id;
402         gchar *vcard;
403
404         if (!priv->running)
405                 return;
406
407         g_mutex_lock (priv->pending_mutex);
408
409         id = e_contact_get_const ((EContact *) contact, E_CONTACT_UID);
410
411         currently_in_view = id_is_in_view (book_view, id);
412         want_in_view =
413                 e_book_backend_sexp_match_contact (priv->card_sexp, (EContact *) contact);
414
415         if (want_in_view) {
416                 vcard = e_vcard_to_string (E_VCARD (contact),
417                                            EVC_FORMAT_VCARD_30);
418
419                 if (currently_in_view)
420                         notify_change (book_view, id, vcard);
421                 else
422                         notify_add (book_view, id, vcard);
423
424                 g_free (vcard);
425         } else {
426                 if (currently_in_view)
427                         notify_remove (book_view, id);
428                 /* else nothing; we're removing a card that wasn't there */
429         }
430
431         g_mutex_unlock (priv->pending_mutex);
432 }
433
434 /**
435  * e_data_book_view_notify_update_vcard:
436  * @book_view: an #EDataBookView
437  * @vcard: a plain vCard
438  *
439  * Notify listeners that @vcard has changed. This can
440  * trigger an add, change or removal event depending on
441  * whether the change causes the contact to start matching,
442  * no longer match, or stay matching the query specified
443  * by @book_view.  This method should be preferred over
444  * #e_data_book_view_notify_update when the native
445  * representation of a contact is a vCard.
446  **/
447 void
448 e_data_book_view_notify_update_vcard (EDataBookView *book_view,
449                                       const gchar *id,
450                                       const gchar *vcard)
451 {
452         EDataBookViewPrivate *priv = book_view->priv;
453         gboolean currently_in_view, want_in_view;
454         EContact *contact;
455
456         if (!priv->running)
457                 return;
458
459         g_mutex_lock (priv->pending_mutex);
460
461         contact = e_contact_new_from_vcard_with_uid (vcard, id);
462         currently_in_view = id_is_in_view (book_view, id);
463         want_in_view =
464                 e_book_backend_sexp_match_contact (priv->card_sexp, contact);
465
466         if (want_in_view) {
467                 if (currently_in_view)
468                         notify_change (book_view, id, vcard);
469                 else
470                         notify_add (book_view, id, vcard);
471         } else {
472                 if (currently_in_view)
473                         notify_remove (book_view, id);
474         }
475
476         /* Do this last so that id is still valid when notify_ is called */
477         g_object_unref (contact);
478
479         g_mutex_unlock (priv->pending_mutex);
480 }
481
482 /**
483  * e_data_book_view_notify_update_prefiltered_vcard:
484  * @book_view: an #EDataBookView
485  * @id: the UID of this contact
486  * @vcard: a plain vCard
487  *
488  * Notify listeners that @vcard has changed. This can
489  * trigger an add, change or removal event depending on
490  * whether the change causes the contact to start matching,
491  * no longer match, or stay matching the query specified
492  * by @book_view.  This method should be preferred over
493  * #e_data_book_view_notify_update when the native
494  * representation of a contact is a vCard.
495  *
496  * The important difference between this method and
497  * #e_data_book_view_notify_update and #e_data_book_view_notify_update_vcard is
498  * that it doesn't match the contact against the book view query to see if it
499  * should be included, it assumes that this has been done and the contact is
500  * known to exist in the view.
501  **/
502 void
503 e_data_book_view_notify_update_prefiltered_vcard (EDataBookView *book_view,
504                                                   const gchar *id,
505                                                   const gchar *vcard)
506 {
507         EDataBookViewPrivate *priv = book_view->priv;
508         gboolean currently_in_view;
509
510         if (!priv->running)
511                 return;
512
513         g_mutex_lock (priv->pending_mutex);
514
515         currently_in_view = id_is_in_view (book_view, id);
516
517         if (currently_in_view)
518                 notify_change (book_view, id, vcard);
519         else
520                 notify_add (book_view, id, vcard);
521
522         g_mutex_unlock (priv->pending_mutex);
523 }
524
525 /**
526  * e_data_book_view_notify_remove:
527  * @book_view: an #EDataBookView
528  * @id: a unique contact ID
529  *
530  * Notify listeners that a contact specified by @id
531  * was removed from @book_view.
532  **/
533 void
534 e_data_book_view_notify_remove (EDataBookView *book_view,
535                                 const gchar *id)
536 {
537         if (!book_view->priv->running)
538                 return;
539
540         g_mutex_lock (book_view->priv->pending_mutex);
541
542         if (id_is_in_view (book_view, id))
543                 notify_remove (book_view, id);
544
545         g_mutex_unlock (book_view->priv->pending_mutex);
546 }
547
548 /**
549  * e_data_book_view_notify_complete:
550  * @book_view: an #EDataBookView
551  * @error: the error of the query, if any
552  *
553  * Notifies listeners that all pending updates on @book_view
554  * have been sent. The listener's information should now be
555  * in sync with the backend's.
556  **/
557 void
558 e_data_book_view_notify_complete (EDataBookView *book_view,
559                                   const GError *error)
560 {
561         EDataBookViewPrivate *priv = book_view->priv;
562         gchar **strv_error;
563
564         if (!priv->running)
565                 return;
566         /* View is complete */
567         priv->complete = TRUE;
568
569         g_mutex_lock (priv->pending_mutex);
570
571         send_pending_adds (book_view);
572         send_pending_changes (book_view);
573         send_pending_removes (book_view);
574
575         g_mutex_unlock (priv->pending_mutex);
576
577         strv_error = e_gdbus_templates_encode_error (error);
578         e_gdbus_book_view_emit_complete (priv->gdbus_object, (const gchar * const *) strv_error);
579         g_strfreev (strv_error);
580 }
581
582 /**
583  * e_data_book_view_notify_progress:
584  * @book_view: an #EDataBookView
585  * @percent: percent done; use -1 when not available
586  * @message: a text message
587  *
588  * Provides listeners with a human-readable text describing the
589  * current backend operation. This can be used for progress
590  * reporting.
591  *
592  * Since: 3.2
593  **/
594 void
595 e_data_book_view_notify_progress (EDataBookView *book_view,
596                                   guint percent,
597                                   const gchar *message)
598 {
599         gchar *gdbus_message = NULL;
600
601         if (!book_view->priv->running)
602                 return;
603
604         e_gdbus_book_view_emit_progress (
605                 book_view->priv->gdbus_object, percent,
606                 e_util_ensure_gdbus_string (message, &gdbus_message));
607
608         g_free (gdbus_message);
609 }
610
611 /**
612  * e_data_book_view_new:
613  * @book: The #EDataBook to search
614  * @card_query: The query as a string
615  * @card_sexp: The query as an #EBookBackendSExp
616  *
617  * Create a new #EDataBookView for the given #EBook, filtering on #card_sexp,
618  * and place it on DBus at the object path #path.
619  */
620 EDataBookView *
621 e_data_book_view_new (EDataBook *book,
622                       const gchar *card_query,
623                       EBookBackendSExp *card_sexp)
624 {
625         EDataBookView *view;
626         EDataBookViewPrivate *priv;
627
628         view = g_object_new (E_TYPE_DATA_BOOK_VIEW, NULL);
629         priv = view->priv;
630
631         priv->book = book;
632         /* Attach a weak reference to the book, so if it dies the book view is destroyed too */
633         g_object_weak_ref (G_OBJECT (priv->book), book_destroyed_cb, view);
634         priv->backend = g_object_ref (e_data_book_get_backend (book));
635         priv->card_query = e_util_utf8_make_valid (card_query);
636         priv->card_sexp = card_sexp;
637
638         return view;
639 }
640
641 static gboolean
642 bookview_idle_start (gpointer data)
643 {
644         EDataBookView *book_view = data;
645
646         book_view->priv->running = TRUE;
647         book_view->priv->complete = FALSE;
648         book_view->priv->idle_id = 0;
649
650         e_book_backend_start_book_view (book_view->priv->backend, book_view);
651
652         return FALSE;
653 }
654
655 static gboolean
656 impl_DataBookView_start (EGdbusBookView *object,
657                          GDBusMethodInvocation *invocation,
658                          EDataBookView *book_view)
659 {
660         book_view->priv->idle_id = g_idle_add (bookview_idle_start, book_view);
661
662         e_gdbus_book_view_complete_start (object, invocation, NULL);
663
664         return TRUE;
665 }
666
667 static gboolean
668 bookview_idle_stop (gpointer data)
669 {
670         EDataBookView *book_view = data;
671
672         e_book_backend_stop_book_view (book_view->priv->backend, book_view);
673
674         book_view->priv->running = FALSE;
675         book_view->priv->complete = FALSE;
676         book_view->priv->idle_id = 0;
677
678         return FALSE;
679 }
680
681 static gboolean
682 impl_DataBookView_stop (EGdbusBookView *object,
683                         GDBusMethodInvocation *invocation,
684                         EDataBookView *book_view)
685 {
686         if (book_view->priv->idle_id)
687                 g_source_remove (book_view->priv->idle_id);
688
689         book_view->priv->idle_id = g_idle_add (bookview_idle_stop, book_view);
690
691         e_gdbus_book_view_complete_stop (object, invocation, NULL);
692
693         return TRUE;
694 }
695
696 static gboolean
697 impl_DataBookView_setFlags (EGdbusBookView *object,
698                             GDBusMethodInvocation *invocation,
699                             EBookClientViewFlags flags,
700                             EDataBookView *book_view)
701 {
702         book_view->priv->flags = flags;
703
704         e_gdbus_book_view_complete_set_flags (object, invocation, NULL);
705
706         return TRUE;
707 }
708
709 static gboolean
710 impl_DataBookView_dispose (EGdbusBookView *object,
711                            GDBusMethodInvocation *invocation,
712                            EDataBookView *book_view)
713 {
714         e_gdbus_book_view_complete_dispose (object, invocation, NULL);
715
716         e_book_backend_stop_book_view (book_view->priv->backend, book_view);
717         book_view->priv->running = FALSE;
718         e_book_backend_remove_book_view (book_view->priv->backend, book_view);
719
720         g_object_unref (book_view);
721
722         return TRUE;
723 }
724
725 static void
726 e_data_book_view_init (EDataBookView *book_view)
727 {
728         book_view->priv = E_DATA_BOOK_VIEW_GET_PRIVATE (book_view);
729
730         book_view->priv->flags = E_BOOK_CLIENT_VIEW_FLAGS_NOTIFY_INITIAL;
731
732         book_view->priv->gdbus_object = e_gdbus_book_view_stub_new ();
733         g_signal_connect (
734                 book_view->priv->gdbus_object, "handle-start",
735                 G_CALLBACK (impl_DataBookView_start), book_view);
736         g_signal_connect (
737                 book_view->priv->gdbus_object, "handle-stop",
738                 G_CALLBACK (impl_DataBookView_stop), book_view);
739         g_signal_connect (
740                 book_view->priv->gdbus_object, "handle-set-flags",
741                 G_CALLBACK (impl_DataBookView_setFlags), book_view);
742         g_signal_connect (
743                 book_view->priv->gdbus_object, "handle-dispose",
744                 G_CALLBACK (impl_DataBookView_dispose), book_view);
745         g_signal_connect (
746                 book_view->priv->gdbus_object, "handle-set-fields-of-interest",
747                 G_CALLBACK (impl_DataBookView_set_fields_of_interest), book_view);
748
749         book_view->priv->fields_of_interest = NULL;
750         book_view->priv->running = FALSE;
751         book_view->priv->complete = FALSE;
752         book_view->priv->pending_mutex = g_mutex_new ();
753
754         /* THRESHOLD_ITEMS * 2 because we store UID and vcard */
755         book_view->priv->adds = g_array_sized_new (
756                 TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS * 2);
757         book_view->priv->changes = g_array_sized_new (
758                 TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS * 2);
759         book_view->priv->removes = g_array_sized_new (
760                 TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS);
761
762         book_view->priv->ids = g_hash_table_new_full (
763                 (GHashFunc) g_str_hash,
764                 (GEqualFunc) g_str_equal,
765                 (GDestroyNotify) g_free,
766                 (GDestroyNotify) NULL);
767
768         book_view->priv->flush_id = 0;
769 }
770
771 static void
772 e_data_book_view_dispose (GObject *object)
773 {
774         EDataBookView *book_view = E_DATA_BOOK_VIEW (object);
775         EDataBookViewPrivate *priv = book_view->priv;
776
777         if (priv->book) {
778                 /* Remove the weak reference */
779                 g_object_weak_unref (G_OBJECT (priv->book), book_destroyed_cb, book_view);
780                 priv->book = NULL;
781         }
782
783         if (priv->backend) {
784                 g_object_unref (priv->backend);
785                 priv->backend = NULL;
786         }
787
788         if (priv->card_sexp) {
789                 g_object_unref (priv->card_sexp);
790                 priv->card_sexp = NULL;
791         }
792
793         if (priv->idle_id) {
794                 g_source_remove (priv->idle_id);
795                 priv->idle_id = 0;
796         }
797
798         g_mutex_lock (priv->pending_mutex);
799
800         if (priv->flush_id) {
801                 g_source_remove (priv->flush_id);
802                 priv->flush_id = 0;
803         }
804
805         g_mutex_unlock (priv->pending_mutex);
806
807         G_OBJECT_CLASS (e_data_book_view_parent_class)->dispose (object);
808 }
809
810 static void
811 e_data_book_view_finalize (GObject *object)
812 {
813         EDataBookView *book_view = E_DATA_BOOK_VIEW (object);
814         EDataBookViewPrivate *priv = book_view->priv;
815
816         reset_array (priv->adds);
817         reset_array (priv->changes);
818         reset_array (priv->removes);
819         g_array_free (priv->adds, TRUE);
820         g_array_free (priv->changes, TRUE);
821         g_array_free (priv->removes, TRUE);
822         g_free (priv->card_query);
823
824         if (priv->fields_of_interest)
825                 g_hash_table_destroy (priv->fields_of_interest);
826
827         g_mutex_free (priv->pending_mutex);
828
829         g_hash_table_destroy (priv->ids);
830
831         G_OBJECT_CLASS (e_data_book_view_parent_class)->finalize (object);
832 }
833
834 /**
835  * e_data_book_view_get_card_query:
836  * @book_view: an #EDataBookView
837  *
838  * Gets the text representation of the s-expression used
839  * for matching contacts to @book_view.
840  *
841  * Returns: The textual s-expression used.
842  **/
843 const gchar *
844 e_data_book_view_get_card_query (EDataBookView *book_view)
845 {
846         g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), NULL);
847
848         return book_view->priv->card_query;
849 }
850
851 /**
852  * e_data_book_view_get_card_sexp:
853  * @book_view: an #EDataBookView
854  *
855  * Gets the s-expression used for matching contacts to
856  * @book_view.
857  *
858  * Returns: The #EBookBackendSExp used.
859  **/
860 EBookBackendSExp *
861 e_data_book_view_get_card_sexp (EDataBookView *book_view)
862 {
863         g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), NULL);
864
865         return book_view->priv->card_sexp;
866 }
867
868 /**
869  * e_data_book_view_get_backend:
870  * @book_view: an #EDataBookView
871  *
872  * Gets the backend that @book_view is querying.
873  *
874  * Returns: The associated #EBookBackend.
875  **/
876 EBookBackend *
877 e_data_book_view_get_backend (EDataBookView *book_view)
878 {
879         g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), NULL);
880
881         return book_view->priv->backend;
882 }
883
884 /**
885  * e_data_book_view_get_flags:
886  * @book_view: an #EDataBookView
887  *
888  * Gets the #EBookClientViewFlags that control the behaviour of @book_view.
889  *
890  * Returns: the flags for @book_view.
891  *
892  * Since: 3.4
893  **/
894 EBookClientViewFlags
895 e_data_book_view_get_flags (EDataBookView *book_view)
896 {
897         g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), 0);
898
899         return book_view->priv->flags;
900 }
901
902 /**
903  * e_data_book_view_get_fields_of_interest:
904  * @view: A view object.
905  *
906  * Returns: Hash table of field names which the listener is interested in.
907  * Backends can return fully populated objects, but the listener advertised
908  * that it will use only these. Returns %NULL for all available fields.
909  *
910  * Note: The data pointer in the hash table has no special meaning, it's
911  * only GINT_TO_POINTER(1) for easier checking. Also, field names are
912  * compared case insensitively.
913  **/
914 /* const */ GHashTable *
915 e_data_book_view_get_fields_of_interest (EDataBookView *view)
916 {
917         g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
918
919         return view->priv->fields_of_interest;
920 }
921
922 /**
923  * e_data_book_view_ref
924  * @book_view: an #EBookView
925  *
926  * Increase the reference count of the book view. This is a function to aid
927  * the transition from Bonobo to DBUS.
928  *
929  * Since: 2.26
930  */
931 void
932 e_data_book_view_ref (EDataBookView *book_view)
933 {
934         g_object_ref (book_view);
935 }
936
937 /**
938  * e_data_book_view_unref
939  * @book_view: an #EBookView
940  *
941  * Decrease the reference count of the book view. This is a function to aid
942  * the transition from Bonobo to DBUS.
943  *
944  * Since: 2.26
945  */
946 void
947 e_data_book_view_unref (EDataBookView *book_view)
948 {
949         g_object_unref (book_view);
950 }