1 /* Evolution calendar - Live search query implementation
3 * Copyright (C) 2001 Ximian, Inc.
5 * Author: Federico Mena-Quintero <federico@ximian.com>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 #include <bonobo/bonobo-exception.h>
28 #include "libedataserver/e-component-listener.h"
29 #include "e-cal-backend-sexp.h"
30 #include "e-data-cal-view.h"
35 GNOME_Evolution_Calendar_CalViewListener listener;
36 EComponentListener *component_listener;
38 gboolean notified_start;
39 gboolean notified_done;
42 /* Private part of the Query structure */
43 struct _EDataCalViewPrivate {
44 /* The backend we are monitoring */
49 GNOME_Evolution_Calendar_CallStatus done_status;
51 GHashTable *matched_objects;
53 /* The listener we report to */
56 /* Sexp that defines the query */
57 ECalBackendSExp *sexp;
63 static void e_data_cal_view_class_init (EDataCalViewClass *class);
64 static void e_data_cal_view_init (EDataCalView *query, EDataCalViewClass *class);
65 static void e_data_cal_view_finalize (GObject *object);
67 static BonoboObjectClass *parent_class;
71 BONOBO_TYPE_FUNC_FULL (EDataCalView,
72 GNOME_Evolution_Calendar_CalView,
86 add_object_to_cache (EDataCalView *query, const char *calobj)
91 EDataCalViewPrivate *priv;
95 comp = e_cal_component_new_from_string (calobj);
99 e_cal_component_get_uid (comp, &uid);
101 g_object_unref (comp);
105 if (e_cal_component_is_instance (comp))
106 real_uid = g_strdup_printf ("%s@%s", uid, e_cal_component_get_recurid_as_string (comp));
108 real_uid = g_strdup (uid);
110 if (g_hash_table_lookup (priv->matched_objects, real_uid))
111 g_hash_table_replace (priv->matched_objects, real_uid, g_strdup (calobj));
113 g_hash_table_insert (priv->matched_objects, real_uid, g_strdup (calobj));
116 g_object_unref (comp);
120 uncache_with_id_cb (gpointer key, gpointer value, gpointer user_data)
124 const char *this_uid;
126 gboolean remove = FALSE;
131 comp = e_cal_component_new_from_string (object);
133 e_cal_component_get_uid (comp, &this_uid);
134 if (this_uid && !strcmp (id->uid, this_uid)) {
135 if (id->rid && *id->rid) {
136 const char *rid = e_cal_component_get_recurid_as_string (comp);
138 if (rid && !strcmp (id->rid, rid))
144 g_object_unref (comp);
151 remove_object_from_cache (EDataCalView *query, const ECalComponentId *id)
153 EDataCalViewPrivate *priv;
157 g_hash_table_foreach_remove (priv->matched_objects, (GHRFunc) uncache_with_id_cb, (gpointer) id);
161 listener_died_cb (EComponentListener *cl, gpointer data)
163 EDataCalView *query = QUERY (data);
164 EDataCalViewPrivate *priv;
169 for (l = priv->listeners; l != NULL; l = l->next) {
170 ListenerData *ld = l->data;
172 if (ld->component_listener == cl) {
173 g_object_unref (ld->component_listener);
174 ld->component_listener = NULL;
176 bonobo_object_release_unref (ld->listener, NULL);
179 priv->listeners = g_list_remove_link (priv->listeners, l);
188 notify_matched_object_cb (gpointer key, gpointer value, gpointer user_data)
192 EDataCalViewPrivate *priv;
200 for (l = priv->listeners; l != NULL; l = l->next) {
201 ListenerData *ld = l->data;
203 if (!ld->notified_start) {
204 GNOME_Evolution_Calendar_stringlist obj_list;
205 CORBA_Environment ev;
207 obj_list._buffer = GNOME_Evolution_Calendar_stringlist_allocbuf (1);
208 obj_list._maximum = 1;
209 obj_list._length = 1;
210 obj_list._buffer[0] = CORBA_string_dup (object);
212 CORBA_exception_init (&ev);
213 GNOME_Evolution_Calendar_CalViewListener_notifyObjectsAdded (ld->listener, &obj_list, &ev);
214 CORBA_exception_free (&ev);
216 CORBA_free (obj_list._buffer);
222 impl_EDataCalView_start (PortableServer_Servant servant, CORBA_Environment *ev)
225 EDataCalViewPrivate *priv;
228 query = QUERY (bonobo_object_from_servant (servant));
232 g_hash_table_foreach (priv->matched_objects, (GHFunc) notify_matched_object_cb, query);
234 /* notify all listeners correctly if the query is already done */
235 for (l = priv->listeners; l != NULL; l = l->next) {
236 ListenerData *ld = l->data;
238 if (!ld->notified_start) {
239 ld->notified_start = TRUE;
241 if (priv->done && !ld->notified_done) {
243 ld->notified_done = TRUE;
245 CORBA_exception_init (ev);
246 GNOME_Evolution_Calendar_CalViewListener_notifyQueryDone (
247 ld->listener, priv->done_status, ev);
248 CORBA_exception_free (ev);
253 priv->started = TRUE;
254 e_cal_backend_start_query (priv->backend, query);
256 for (l = priv->listeners; l != NULL; l = l->next) {
257 ListenerData *ld = l->data;
259 ld->notified_start = TRUE;
265 e_data_cal_view_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
268 EDataCalViewPrivate *priv;
270 query = QUERY (object);
273 switch (property_id) {
275 priv->backend = E_CAL_BACKEND (g_value_get_object (value));
278 e_data_cal_view_add_listener (query, g_value_get_pointer (value));
281 priv->sexp = E_CAL_BACKEND_SEXP (g_value_dup_object (value));
284 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
290 e_data_cal_view_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
293 EDataCalViewPrivate *priv;
295 query = QUERY (object);
298 switch (property_id) {
300 g_value_set_object (value, priv->backend);
303 if (priv->listeners) {
306 ld = priv->listeners->data;
307 g_value_set_pointer (value, ld->listener);
311 g_value_set_object (value, priv->sexp);
314 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
319 /* Class initialization function for the live search query */
321 e_data_cal_view_class_init (EDataCalViewClass *klass)
323 GObjectClass *object_class;
324 POA_GNOME_Evolution_Calendar_CalView__epv *epv = &klass->epv;
327 object_class = (GObjectClass *) klass;
329 parent_class = g_type_class_peek_parent (klass);
331 object_class->set_property = e_data_cal_view_set_property;
332 object_class->get_property = e_data_cal_view_get_property;
333 object_class->finalize = e_data_cal_view_finalize;
335 epv->start = impl_EDataCalView_start;
337 param = g_param_spec_object ("backend", NULL, NULL, E_TYPE_CAL_BACKEND,
338 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
339 g_object_class_install_property (object_class, PROP_BACKEND, param);
340 param = g_param_spec_pointer ("listener", NULL, NULL,
341 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
342 g_object_class_install_property (object_class, PROP_LISTENER, param);
343 param = g_param_spec_object ("sexp", NULL, NULL, E_TYPE_CAL_BACKEND_SEXP,
344 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
345 g_object_class_install_property (object_class, PROP_SEXP, param);
348 /* Object initialization function for the live search query */
350 e_data_cal_view_init (EDataCalView *query, EDataCalViewClass *class)
352 EDataCalViewPrivate *priv;
354 priv = g_new0 (EDataCalViewPrivate, 1);
357 priv->backend = NULL;
358 priv->started = FALSE;
360 priv->done_status = GNOME_Evolution_Calendar_Success;
361 priv->matched_objects = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
362 priv->listeners = NULL;
366 /* Finalize handler for the live search query */
368 e_data_cal_view_finalize (GObject *object)
371 EDataCalViewPrivate *priv;
373 g_return_if_fail (object != NULL);
374 g_return_if_fail (IS_QUERY (object));
376 query = QUERY (object);
380 g_object_unref (priv->backend);
382 while (priv->listeners) {
383 ListenerData *ld = priv->listeners->data;
386 bonobo_object_release_unref (ld->listener, NULL);
387 if (ld->component_listener)
388 g_object_unref (ld->component_listener);
389 priv->listeners = g_list_remove (priv->listeners, ld);
393 if (priv->matched_objects)
394 g_hash_table_destroy (priv->matched_objects);
397 g_object_unref (priv->sexp);
401 if (G_OBJECT_CLASS (parent_class)->finalize)
402 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
406 * e_data_cal_view_new:
407 * @backend: Calendar backend that the query object will monitor.
408 * @ql: Listener for query results.
409 * @sexp: Sexp that defines the query.
411 * Creates a new query engine object that monitors a calendar backend.
413 * Return value: A newly-created query object, or NULL on failure.
416 e_data_cal_view_new (ECalBackend *backend,
417 GNOME_Evolution_Calendar_CalViewListener ql,
418 ECalBackendSExp *sexp)
422 query = g_object_new (E_DATA_CAL_VIEW_TYPE, "backend", backend, "listener", ql,
429 * e_data_cal_view_add_listener:
430 * @query: A #EDataCalView object.
431 * @ql: A CORBA query listener to add to the list of listeners.
433 * Adds the given CORBA listener to a #EDataCalView object. This makes the view
434 * object notify that listener when notifying the other listeners already attached
438 e_data_cal_view_add_listener (EDataCalView *query, GNOME_Evolution_Calendar_CalViewListener ql)
441 EDataCalViewPrivate *priv;
442 CORBA_Environment ev;
444 g_return_if_fail (IS_QUERY (query));
445 g_return_if_fail (ql != CORBA_OBJECT_NIL);
449 ld = g_new0 (ListenerData, 1);
451 CORBA_exception_init (&ev);
452 ld->listener = CORBA_Object_duplicate (ql, &ev);
453 CORBA_exception_free (&ev);
455 ld->component_listener = e_component_listener_new (ld->listener);
456 g_signal_connect (G_OBJECT (ld->component_listener), "component_died",
457 G_CALLBACK (listener_died_cb), query);
459 priv->listeners = g_list_prepend (priv->listeners, ld);
463 * e_data_cal_view_get_text:
464 * @query: A #EDataCalView object.
466 * Get the expression used for the given query.
468 * Return value: the query expression used to search.
471 e_data_cal_view_get_text (EDataCalView *query)
473 g_return_val_if_fail (IS_QUERY (query), NULL);
475 return e_cal_backend_sexp_text (query->priv->sexp);
479 * e_data_cal_view_get_object_sexp:
480 * @query: A query object.
482 * Get the #ECalBackendSExp object used for the given query.
484 * Return value: The expression object used to search.
487 e_data_cal_view_get_object_sexp (EDataCalView *query)
489 g_return_val_if_fail (IS_QUERY (query), NULL);
491 return query->priv->sexp;
495 * e_data_cal_view_object_matches:
496 * @query: A query object.
497 * @object: Object to match.
499 * Compares the given @object to the regular expression used for the
502 * Return value: TRUE if the object matches the expression, FALSE if not.
505 e_data_cal_view_object_matches (EDataCalView *query, const char *object)
507 EDataCalViewPrivate *priv;
509 g_return_val_if_fail (query != NULL, FALSE);
510 g_return_val_if_fail (IS_QUERY (query), FALSE);
511 g_return_val_if_fail (object != NULL, FALSE);
515 return e_cal_backend_sexp_match_object (priv->sexp, object, priv->backend);
519 add_object_to_list (gpointer key, gpointer value, gpointer user_data)
521 GList **list = user_data;
523 *list = g_list_append (*list, value);
527 * e_data_cal_view_get_matched_objects:
528 * @query: A query object.
530 * Gets the list of objects already matched for the given query.
532 * Return value: A list of matched objects.
535 e_data_cal_view_get_matched_objects (EDataCalView *query)
537 EDataCalViewPrivate *priv;
540 g_return_val_if_fail (IS_QUERY (query), NULL);
544 g_hash_table_foreach (priv->matched_objects, (GHFunc) add_object_to_list, &list);
550 * e_data_cal_view_is_started:
551 * @query: A query object.
553 * Checks whether the given query has already been started.
555 * Return value: TRUE if the query has already been started, FALSE otherwise.
558 e_data_cal_view_is_started (EDataCalView *query)
560 EDataCalViewPrivate *priv;
562 g_return_val_if_fail (IS_QUERY (query), FALSE);
566 return priv->started;
570 * e_data_cal_view_is_done:
571 * @query: A query object.
573 * Checks whether the given query is already done. Being done means the initial
574 * matching of objects have been finished, not that no more notifications about
575 * changes will be sent. In fact, even after done, notifications will still be sent
576 * if there are changes in the objects matching the query search expression.
578 * Return value: TRUE if the query is done, FALSE if still in progress.
581 e_data_cal_view_is_done (EDataCalView *query)
583 EDataCalViewPrivate *priv;
585 g_return_val_if_fail (IS_QUERY (query), FALSE);
593 * e_data_cal_view_get_done_status:
594 * @query: A query object.
596 * Gets the status code obtained when the initial matching of objects was done
597 * for the given query.
599 * Return value: The query status.
601 GNOME_Evolution_Calendar_CallStatus
602 e_data_cal_view_get_done_status (EDataCalView *query)
604 EDataCalViewPrivate *priv;
606 g_return_val_if_fail (IS_QUERY (query), FALSE);
611 return priv->done_status;
613 return GNOME_Evolution_Calendar_Success;
617 * e_data_cal_view_notify_objects_added:
618 * @query: A query object.
619 * @objects: List of objects that have been added.
621 * Notifies all query listeners of the addition of a list of objects.
624 e_data_cal_view_notify_objects_added (EDataCalView *query, const GList *objects)
626 EDataCalViewPrivate *priv;
627 GNOME_Evolution_Calendar_stringlist obj_list;
628 CORBA_Environment ev;
632 g_return_if_fail (query != NULL);
633 g_return_if_fail (IS_QUERY (query));
636 g_return_if_fail (priv->listeners != CORBA_OBJECT_NIL);
638 num_objs = g_list_length ((GList*)objects);
642 obj_list._buffer = GNOME_Evolution_Calendar_stringlist_allocbuf (num_objs);
643 obj_list._maximum = num_objs;
644 obj_list._length = num_objs;
646 for (l = objects, i = 0; l; l = l->next, i++) {
647 obj_list._buffer[i] = CORBA_string_dup (l->data);
649 /* update our cache */
650 add_object_to_cache (query, l->data);
653 for (l = priv->listeners; l != NULL; l = l->next) {
654 ListenerData *ld = l->data;
656 CORBA_exception_init (&ev);
658 GNOME_Evolution_Calendar_CalViewListener_notifyObjectsAdded (ld->listener, &obj_list, &ev);
660 g_warning (G_STRLOC ": could not notify the listener of object addition");
662 CORBA_exception_free (&ev);
665 CORBA_free (obj_list._buffer);
669 * e_data_cal_view_notify_objects_added_1:
670 * @query: A query object.
671 * @object: The object that has been added.
673 * Notifies all the query listeners of the addition of a single object.
676 e_data_cal_view_notify_objects_added_1 (EDataCalView *query, const char *object)
678 EDataCalViewPrivate *priv;
681 g_return_if_fail (query != NULL);
682 g_return_if_fail (IS_QUERY (query));
683 g_return_if_fail (object != NULL);
686 g_return_if_fail (priv->listeners != CORBA_OBJECT_NIL);
688 objects.next = objects.prev = NULL;
689 objects.data = (gpointer)object;
691 e_data_cal_view_notify_objects_added (query, &objects);
695 * e_data_cal_view_notify_objects_modified:
696 * @query: A query object.
697 * @objects: List of modified objects.
699 * Notifies all query listeners of the modification of a list of objects.
702 e_data_cal_view_notify_objects_modified (EDataCalView *query, const GList *objects)
704 EDataCalViewPrivate *priv;
705 GNOME_Evolution_Calendar_CalObjUIDSeq obj_list;
706 CORBA_Environment ev;
710 g_return_if_fail (query != NULL);
711 g_return_if_fail (IS_QUERY (query));
714 g_return_if_fail (priv->listeners != CORBA_OBJECT_NIL);
716 num_objs = g_list_length ((GList*)objects);
720 obj_list._buffer = GNOME_Evolution_Calendar_stringlist_allocbuf (num_objs);
721 obj_list._maximum = num_objs;
722 obj_list._length = num_objs;
724 for (l = objects, i = 0; l; l = l->next, i++) {
725 obj_list._buffer[i] = CORBA_string_dup (l->data);
727 /* update our cache */
728 add_object_to_cache (query, l->data);
731 for (l = priv->listeners; l != NULL; l = l->next) {
732 ListenerData *ld = l->data;
734 CORBA_exception_init (&ev);
736 GNOME_Evolution_Calendar_CalViewListener_notifyObjectsModified (ld->listener, &obj_list, &ev);
738 g_warning (G_STRLOC ": could not notify the listener of object modification");
740 CORBA_exception_free (&ev);
743 CORBA_free (obj_list._buffer);
747 * e_data_cal_view_notify_objects_modified_1:
748 * @query: A query object.
749 * @object: The modified object.
751 * Notifies all query listeners of the modification of a single object.
754 e_data_cal_view_notify_objects_modified_1 (EDataCalView *query, const char *object)
756 EDataCalViewPrivate *priv;
759 g_return_if_fail (query != NULL);
760 g_return_if_fail (IS_QUERY (query));
761 g_return_if_fail (object != NULL);
764 g_return_if_fail (priv->listeners != CORBA_OBJECT_NIL);
766 objects.next = objects.prev = NULL;
767 objects.data = (gpointer)object;
769 e_data_cal_view_notify_objects_modified (query, &objects);
773 * e_data_cal_view_notify_objects_removed:
774 * @query: A query object.
775 * @ids: List of IDs for the objects that have been removed.
777 * Notifies all query listener of the removal of a list of objects.
780 e_data_cal_view_notify_objects_removed (EDataCalView *query, const GList *ids)
782 EDataCalViewPrivate *priv;
783 GNOME_Evolution_Calendar_CalObjIDSeq id_list;
784 CORBA_Environment ev;
788 g_return_if_fail (query != NULL);
789 g_return_if_fail (IS_QUERY (query));
792 g_return_if_fail (priv->listeners != CORBA_OBJECT_NIL);
794 num_ids = g_list_length ((GList*)ids);
798 id_list._buffer = GNOME_Evolution_Calendar_CalObjIDSeq_allocbuf (num_ids);
799 id_list._maximum = num_ids;
800 id_list._length = num_ids;
803 for (l = ids; l; l = l->next, i++) {
804 ECalComponentId *id = l->data;
805 GNOME_Evolution_Calendar_CalObjID *c_id = &id_list._buffer[i];
807 c_id->uid = CORBA_string_dup (id->uid);
810 c_id->rid = CORBA_string_dup (id->rid);
812 c_id->rid = CORBA_string_dup ("");
814 /* update our cache */
815 remove_object_from_cache (query, l->data);
818 for (l = priv->listeners; l != NULL; l = l->next) {
819 ListenerData *ld = l->data;
821 CORBA_exception_init (&ev);
823 GNOME_Evolution_Calendar_CalViewListener_notifyObjectsRemoved (ld->listener, &id_list, &ev);
825 g_warning (G_STRLOC ": could not notify the listener of object removal");
827 CORBA_exception_free (&ev);
830 CORBA_free (id_list._buffer);
834 * e_data_cal_view_notify_objects_removed:
835 * @query: A query object.
836 * @id: Id of the removed object.
838 * Notifies all query listener of the removal of a single object.
841 e_data_cal_view_notify_objects_removed_1 (EDataCalView *query, const ECalComponentId *id)
843 EDataCalViewPrivate *priv;
846 g_return_if_fail (query != NULL);
847 g_return_if_fail (IS_QUERY (query));
848 g_return_if_fail (id != NULL);
851 g_return_if_fail (priv->listeners != CORBA_OBJECT_NIL);
853 ids.next = ids.prev = NULL;
854 ids.data = (gpointer)id;
856 e_data_cal_view_notify_objects_removed (query, &ids);
860 * e_data_cal_view_notify_progress:
861 * @query: A query object.
862 * @message: Progress message to send to listeners.
863 * @percent: Percentage completed.
865 * Notifies all query listeners of progress messages.
868 e_data_cal_view_notify_progress (EDataCalView *query, const char *message, int percent)
870 EDataCalViewPrivate *priv;
871 CORBA_Environment ev;
874 g_return_if_fail (query != NULL);
875 g_return_if_fail (IS_QUERY (query));
878 g_return_if_fail (priv->listeners != CORBA_OBJECT_NIL);
880 for (l = priv->listeners; l != NULL; l = l->next) {
881 ListenerData *ld = l->data;
883 CORBA_exception_init (&ev);
885 GNOME_Evolution_Calendar_CalViewListener_notifyQueryProgress (ld->listener, message, percent, &ev);
887 g_warning (G_STRLOC ": could not notify the listener of query progress");
889 CORBA_exception_free (&ev);
894 * e_data_cal_view_notify_done:
895 * @query: A query object.
896 * @status: Query completion status code.
898 * Notifies all query listeners of the completion of the query, including a
902 e_data_cal_view_notify_done (EDataCalView *query, GNOME_Evolution_Calendar_CallStatus status)
904 EDataCalViewPrivate *priv;
905 CORBA_Environment ev;
908 g_return_if_fail (query != NULL);
909 g_return_if_fail (IS_QUERY (query));
912 g_return_if_fail (priv->listeners != CORBA_OBJECT_NIL);
915 priv->done_status = status;
917 for (l = priv->listeners; l != NULL; l = l->next) {
918 ListenerData *ld = l->data;
920 CORBA_exception_init (&ev);
922 GNOME_Evolution_Calendar_CalViewListener_notifyQueryDone (ld->listener, status, &ev);
924 g_warning (G_STRLOC ": could not notify the listener of query completion");
926 CORBA_exception_free (&ev);