1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4 * Copyright (C) 2006 OpenedHand Ltd
5 * Copyright (C) 2009 Intel Corporation
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.
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
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
20 * Author: Ross Burton <ross@linux.intel.com>
28 #include <libebook/e-contact.h>
29 #include "libedataserver/e-data-server-util.h"
30 #include "e-data-book-view.h"
32 #include "e-gdbus-book-view.h"
34 #define E_DATA_BOOK_VIEW_GET_PRIVATE(obj) \
35 (G_TYPE_INSTANCE_GET_PRIVATE \
36 ((obj), E_TYPE_DATA_BOOK_VIEW, EDataBookViewPrivate))
38 static void reset_array (GArray *array);
39 static void ensure_pending_flush_timeout (EDataBookView *view);
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 */
45 struct _EDataBookViewPrivate {
46 EGdbusBookView *gdbus_object;
49 EBookBackend *backend;
52 EBookBackendSExp *card_sexp;
53 EBookClientViewFlags flags;
57 GMutex *pending_mutex;
68 /* which fields is listener interested in */
69 GHashTable *fields_of_interest;
72 static void e_data_book_view_dispose (GObject *object);
73 static void e_data_book_view_finalize (GObject *object);
76 e_data_book_view_class_init (EDataBookViewClass *class)
78 GObjectClass *object_class = G_OBJECT_CLASS (class);
80 g_type_class_add_private (class, sizeof (EDataBookViewPrivate));
82 object_class->dispose = e_data_book_view_dispose;
83 object_class->finalize = e_data_book_view_finalize;
87 str_ic_hash (gconstpointer key)
90 const gchar *str = key;
96 for (ii = 0; str[ii]; ii++) {
97 hash = hash * 33 + g_ascii_tolower (str[ii]);
104 str_ic_equal (gconstpointer a,
107 const gchar *stra = a, *strb = b;
116 for (ii = 0; stra[ii] && strb[ii]; ii++) {
117 if (g_ascii_tolower (stra[ii]) != g_ascii_tolower (strb[ii]))
121 return stra[ii] == strb[ii];
125 * e_data_book_view_register_gdbus_object:
130 e_data_book_view_register_gdbus_object (EDataBookView *query,
131 GDBusConnection *connection,
132 const gchar *object_path,
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);
140 return e_gdbus_book_view_register_object (query->priv->gdbus_object, connection, object_path, error);
144 book_destroyed_cb (gpointer data,
147 EDataBookView *view = E_DATA_BOOK_VIEW (data);
148 EDataBookViewPrivate *priv = view->priv;
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;
154 /* If the view is running stop it here. */
156 e_book_backend_stop_book_view (priv->backend, view);
157 priv->running = FALSE;
158 priv->complete = FALSE;
163 send_pending_adds (EDataBookView *view)
165 EDataBookViewPrivate *priv = view->priv;
167 if (priv->adds->len == 0)
170 e_gdbus_book_view_emit_objects_added (view->priv->gdbus_object, (const gchar * const *) priv->adds->data);
171 reset_array (priv->adds);
175 send_pending_changes (EDataBookView *view)
177 EDataBookViewPrivate *priv = view->priv;
179 if (priv->changes->len == 0)
182 e_gdbus_book_view_emit_objects_modified (view->priv->gdbus_object, (const gchar * const *) priv->changes->data);
183 reset_array (priv->changes);
187 send_pending_removes (EDataBookView *view)
189 EDataBookViewPrivate *priv = view->priv;
191 if (priv->removes->len == 0)
194 e_gdbus_book_view_emit_objects_removed (view->priv->gdbus_object, (const gchar * const *) priv->removes->data);
195 reset_array (priv->removes);
199 pending_flush_timeout_cb (gpointer data)
201 EDataBookView *view = data;
202 EDataBookViewPrivate *priv = view->priv;
204 g_mutex_lock (priv->pending_mutex);
208 send_pending_adds (view);
209 send_pending_changes (view);
210 send_pending_removes (view);
212 g_mutex_unlock (priv->pending_mutex);
218 ensure_pending_flush_timeout (EDataBookView *view)
220 EDataBookViewPrivate *priv = view->priv;
225 priv->flush_id = g_timeout_add_seconds (THRESHOLD_SECONDS, pending_flush_timeout_cb, view);
229 * Queue @vcard to be sent as a change notification.
232 notify_change (EDataBookView *view,
236 EDataBookViewPrivate *priv = view->priv;
237 gchar *utf8_vcard, *utf8_id;
239 send_pending_adds (view);
240 send_pending_removes (view);
242 if (priv->changes->len == THRESHOLD_ITEMS * 2) {
243 send_pending_changes (view);
246 utf8_vcard = e_util_utf8_make_valid (vcard);
247 utf8_id = e_util_utf8_make_valid (id);
249 g_array_append_val (priv->changes, utf8_vcard);
250 g_array_append_val (priv->changes, utf8_id);
252 ensure_pending_flush_timeout (view);
256 * Queue @id to be sent as a change notification.
259 notify_remove (EDataBookView *view,
262 EDataBookViewPrivate *priv = view->priv;
265 send_pending_adds (view);
266 send_pending_changes (view);
268 if (priv->removes->len == THRESHOLD_ITEMS) {
269 send_pending_removes (view);
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);
276 ensure_pending_flush_timeout (view);
280 * Queue @id and @vcard to be sent as a change notification.
283 notify_add (EDataBookView *view,
287 EBookClientViewFlags flags;
288 EDataBookViewPrivate *priv = view->priv;
289 gchar *utf8_vcard, *utf8_id;
291 send_pending_changes (view);
292 send_pending_removes (view);
294 utf8_id = e_util_utf8_make_valid (id);
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);
301 if (priv->adds->len == THRESHOLD_ITEMS) {
302 send_pending_adds (view);
305 utf8_vcard = e_util_utf8_make_valid (vcard);
307 g_array_append_val (priv->adds, utf8_vcard);
308 g_array_append_val (priv->adds, utf8_id_copy);
310 ensure_pending_flush_timeout (view);
313 g_hash_table_insert (priv->ids, utf8_id,
314 GUINT_TO_POINTER (1));
318 impl_DataBookView_set_fields_of_interest (EGdbusBookView *object,
319 GDBusMethodInvocation *invocation,
320 const gchar * const *in_fields_of_interest,
323 EDataBookViewPrivate *priv;
326 g_return_val_if_fail (in_fields_of_interest != NULL, TRUE);
330 if (priv->fields_of_interest)
331 g_hash_table_destroy (priv->fields_of_interest);
332 priv->fields_of_interest = NULL;
334 for (ii = 0; in_fields_of_interest[ii]; ii++) {
335 const gchar *field = in_fields_of_interest[ii];
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);
343 g_hash_table_insert (priv->fields_of_interest, g_strdup (field), GINT_TO_POINTER (1));
346 e_gdbus_book_view_complete_set_fields_of_interest (object, invocation, NULL);
352 reset_array (GArray *array)
357 /* Free stored strings */
358 for (i = 0; i < array->len; i++) {
359 tmp = g_array_index (array, gchar *, i);
363 /* Force the array size to 0 */
364 g_array_set_size (array, 0);
368 id_is_in_view (EDataBookView *book_view,
374 g_return_val_if_fail (book_view != NULL, FALSE);
375 g_return_val_if_fail (id != NULL, FALSE);
377 valid_id = e_util_utf8_make_valid (id);
378 res = g_hash_table_lookup (book_view->priv->ids, valid_id) != NULL;
385 * e_data_book_view_notify_update:
386 * @book_view: an #EDataBookView
387 * @contact: an #EContact
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
396 e_data_book_view_notify_update (EDataBookView *book_view,
397 const EContact *contact)
399 EDataBookViewPrivate *priv = book_view->priv;
400 gboolean currently_in_view, want_in_view;
407 g_mutex_lock (priv->pending_mutex);
409 id = e_contact_get_const ((EContact *) contact, E_CONTACT_UID);
411 currently_in_view = id_is_in_view (book_view, id);
413 e_book_backend_sexp_match_contact (priv->card_sexp, (EContact *) contact);
416 vcard = e_vcard_to_string (E_VCARD (contact),
417 EVC_FORMAT_VCARD_30);
419 if (currently_in_view)
420 notify_change (book_view, id, vcard);
422 notify_add (book_view, id, vcard);
426 if (currently_in_view)
427 notify_remove (book_view, id);
428 /* else nothing; we're removing a card that wasn't there */
431 g_mutex_unlock (priv->pending_mutex);
435 * e_data_book_view_notify_update_vcard:
436 * @book_view: an #EDataBookView
437 * @vcard: a plain vCard
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.
448 e_data_book_view_notify_update_vcard (EDataBookView *book_view,
452 EDataBookViewPrivate *priv = book_view->priv;
453 gboolean currently_in_view, want_in_view;
459 g_mutex_lock (priv->pending_mutex);
461 contact = e_contact_new_from_vcard_with_uid (vcard, id);
462 currently_in_view = id_is_in_view (book_view, id);
464 e_book_backend_sexp_match_contact (priv->card_sexp, contact);
467 if (currently_in_view)
468 notify_change (book_view, id, vcard);
470 notify_add (book_view, id, vcard);
472 if (currently_in_view)
473 notify_remove (book_view, id);
476 /* Do this last so that id is still valid when notify_ is called */
477 g_object_unref (contact);
479 g_mutex_unlock (priv->pending_mutex);
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
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.
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.
503 e_data_book_view_notify_update_prefiltered_vcard (EDataBookView *book_view,
507 EDataBookViewPrivate *priv = book_view->priv;
508 gboolean currently_in_view;
513 g_mutex_lock (priv->pending_mutex);
515 currently_in_view = id_is_in_view (book_view, id);
517 if (currently_in_view)
518 notify_change (book_view, id, vcard);
520 notify_add (book_view, id, vcard);
522 g_mutex_unlock (priv->pending_mutex);
526 * e_data_book_view_notify_remove:
527 * @book_view: an #EDataBookView
528 * @id: a unique contact ID
530 * Notify listeners that a contact specified by @id
531 * was removed from @book_view.
534 e_data_book_view_notify_remove (EDataBookView *book_view,
537 if (!book_view->priv->running)
540 g_mutex_lock (book_view->priv->pending_mutex);
542 if (id_is_in_view (book_view, id))
543 notify_remove (book_view, id);
545 g_mutex_unlock (book_view->priv->pending_mutex);
549 * e_data_book_view_notify_complete:
550 * @book_view: an #EDataBookView
551 * @error: the error of the query, if any
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.
558 e_data_book_view_notify_complete (EDataBookView *book_view,
561 EDataBookViewPrivate *priv = book_view->priv;
566 /* View is complete */
567 priv->complete = TRUE;
569 g_mutex_lock (priv->pending_mutex);
571 send_pending_adds (book_view);
572 send_pending_changes (book_view);
573 send_pending_removes (book_view);
575 g_mutex_unlock (priv->pending_mutex);
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);
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
588 * Provides listeners with a human-readable text describing the
589 * current backend operation. This can be used for progress
595 e_data_book_view_notify_progress (EDataBookView *book_view,
597 const gchar *message)
599 gchar *gdbus_message = NULL;
601 if (!book_view->priv->running)
604 e_gdbus_book_view_emit_progress (
605 book_view->priv->gdbus_object, percent,
606 e_util_ensure_gdbus_string (message, &gdbus_message));
608 g_free (gdbus_message);
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
617 * Create a new #EDataBookView for the given #EBook, filtering on #card_sexp,
618 * and place it on DBus at the object path #path.
621 e_data_book_view_new (EDataBook *book,
622 const gchar *card_query,
623 EBookBackendSExp *card_sexp)
626 EDataBookViewPrivate *priv;
628 view = g_object_new (E_TYPE_DATA_BOOK_VIEW, NULL);
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;
642 bookview_idle_start (gpointer data)
644 EDataBookView *book_view = data;
646 book_view->priv->running = TRUE;
647 book_view->priv->complete = FALSE;
648 book_view->priv->idle_id = 0;
650 e_book_backend_start_book_view (book_view->priv->backend, book_view);
656 impl_DataBookView_start (EGdbusBookView *object,
657 GDBusMethodInvocation *invocation,
658 EDataBookView *book_view)
660 book_view->priv->idle_id = g_idle_add (bookview_idle_start, book_view);
662 e_gdbus_book_view_complete_start (object, invocation, NULL);
668 bookview_idle_stop (gpointer data)
670 EDataBookView *book_view = data;
672 e_book_backend_stop_book_view (book_view->priv->backend, book_view);
674 book_view->priv->running = FALSE;
675 book_view->priv->complete = FALSE;
676 book_view->priv->idle_id = 0;
682 impl_DataBookView_stop (EGdbusBookView *object,
683 GDBusMethodInvocation *invocation,
684 EDataBookView *book_view)
686 if (book_view->priv->idle_id)
687 g_source_remove (book_view->priv->idle_id);
689 book_view->priv->idle_id = g_idle_add (bookview_idle_stop, book_view);
691 e_gdbus_book_view_complete_stop (object, invocation, NULL);
697 impl_DataBookView_setFlags (EGdbusBookView *object,
698 GDBusMethodInvocation *invocation,
699 EBookClientViewFlags flags,
700 EDataBookView *book_view)
702 book_view->priv->flags = flags;
704 e_gdbus_book_view_complete_set_flags (object, invocation, NULL);
710 impl_DataBookView_dispose (EGdbusBookView *object,
711 GDBusMethodInvocation *invocation,
712 EDataBookView *book_view)
714 e_gdbus_book_view_complete_dispose (object, invocation, NULL);
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);
720 g_object_unref (book_view);
726 e_data_book_view_init (EDataBookView *book_view)
728 book_view->priv = E_DATA_BOOK_VIEW_GET_PRIVATE (book_view);
730 book_view->priv->flags = E_BOOK_CLIENT_VIEW_FLAGS_NOTIFY_INITIAL;
732 book_view->priv->gdbus_object = e_gdbus_book_view_stub_new ();
734 book_view->priv->gdbus_object, "handle-start",
735 G_CALLBACK (impl_DataBookView_start), book_view);
737 book_view->priv->gdbus_object, "handle-stop",
738 G_CALLBACK (impl_DataBookView_stop), book_view);
740 book_view->priv->gdbus_object, "handle-set-flags",
741 G_CALLBACK (impl_DataBookView_setFlags), book_view);
743 book_view->priv->gdbus_object, "handle-dispose",
744 G_CALLBACK (impl_DataBookView_dispose), book_view);
746 book_view->priv->gdbus_object, "handle-set-fields-of-interest",
747 G_CALLBACK (impl_DataBookView_set_fields_of_interest), book_view);
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 ();
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);
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);
768 book_view->priv->flush_id = 0;
772 e_data_book_view_dispose (GObject *object)
774 EDataBookView *book_view = E_DATA_BOOK_VIEW (object);
775 EDataBookViewPrivate *priv = book_view->priv;
778 /* Remove the weak reference */
779 g_object_weak_unref (G_OBJECT (priv->book), book_destroyed_cb, book_view);
784 g_object_unref (priv->backend);
785 priv->backend = NULL;
788 if (priv->card_sexp) {
789 g_object_unref (priv->card_sexp);
790 priv->card_sexp = NULL;
794 g_source_remove (priv->idle_id);
798 g_mutex_lock (priv->pending_mutex);
800 if (priv->flush_id) {
801 g_source_remove (priv->flush_id);
805 g_mutex_unlock (priv->pending_mutex);
807 G_OBJECT_CLASS (e_data_book_view_parent_class)->dispose (object);
811 e_data_book_view_finalize (GObject *object)
813 EDataBookView *book_view = E_DATA_BOOK_VIEW (object);
814 EDataBookViewPrivate *priv = book_view->priv;
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);
824 if (priv->fields_of_interest)
825 g_hash_table_destroy (priv->fields_of_interest);
827 g_mutex_free (priv->pending_mutex);
829 g_hash_table_destroy (priv->ids);
831 G_OBJECT_CLASS (e_data_book_view_parent_class)->finalize (object);
835 * e_data_book_view_get_card_query:
836 * @book_view: an #EDataBookView
838 * Gets the text representation of the s-expression used
839 * for matching contacts to @book_view.
841 * Returns: The textual s-expression used.
844 e_data_book_view_get_card_query (EDataBookView *book_view)
846 g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), NULL);
848 return book_view->priv->card_query;
852 * e_data_book_view_get_card_sexp:
853 * @book_view: an #EDataBookView
855 * Gets the s-expression used for matching contacts to
858 * Returns: The #EBookBackendSExp used.
861 e_data_book_view_get_card_sexp (EDataBookView *book_view)
863 g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), NULL);
865 return book_view->priv->card_sexp;
869 * e_data_book_view_get_backend:
870 * @book_view: an #EDataBookView
872 * Gets the backend that @book_view is querying.
874 * Returns: The associated #EBookBackend.
877 e_data_book_view_get_backend (EDataBookView *book_view)
879 g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), NULL);
881 return book_view->priv->backend;
885 * e_data_book_view_get_flags:
886 * @book_view: an #EDataBookView
888 * Gets the #EBookClientViewFlags that control the behaviour of @book_view.
890 * Returns: the flags for @book_view.
895 e_data_book_view_get_flags (EDataBookView *book_view)
897 g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), 0);
899 return book_view->priv->flags;
903 * e_data_book_view_get_fields_of_interest:
904 * @view: A view object.
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.
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.
914 /* const */ GHashTable *
915 e_data_book_view_get_fields_of_interest (EDataBookView *view)
917 g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
919 return view->priv->fields_of_interest;
923 * e_data_book_view_ref
924 * @book_view: an #EBookView
926 * Increase the reference count of the book view. This is a function to aid
927 * the transition from Bonobo to DBUS.
932 e_data_book_view_ref (EDataBookView *book_view)
934 g_object_ref (book_view);
938 * e_data_book_view_unref
939 * @book_view: an #EBookView
941 * Decrease the reference count of the book view. This is a function to aid
942 * the transition from Bonobo to DBUS.
947 e_data_book_view_unref (EDataBookView *book_view)
949 g_object_unref (book_view);