From feaa9ddc81a8d4f0c3c037fd2822a56107bbab6b Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 29 Mar 2001 16:51:38 +0000 Subject: Engine for live queries to calendars. A query object watches a CalBackend 2001-03-29 Federico Mena Quintero Engine for live queries to calendars. A query object watches a CalBackend in the PCS and is otherwise completely separate from it; backends need to do nothing to support live queries. Right now we have the following functions: (get-vtype) Returns a string indicating the type of component (VEVENT, VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, UNKNOWN). (occur-in-time-range? START END) START - int, time_t start of the time range END - int, time_t end of the time range Returns a boolean indicating whether the component has any occurrences in the specified time range. * idl/evolution-calendar.idl (Cal::getQuery): New method that initiates a live query. (Query): New interface for a handle to a live query. (QueryListener): New interface for a listener to changes in a live query. * pcs/query.[ch]: New files with the live query engine. * pcs/cal-backend.h (CalBackendClass): Added notification signals so that the query system can catch them. (CalBackendClass): New virtual method ::get_load_state(). * pcs/cal-backend.c (cal_backend_opened): (cal_backend_obj_updated): (cal_backend_obj_updated): New functions to emit the notification signals; to be used only by backend implementations. (cal_backend_get_load_state): New function. * pcs/cal-backend-file.c (notify_update): Call cal_backend_obj_updated(). (notify_remove): Call call_backend_obj_removed(). (open_cal): Free the icalcomp if it is not of the correct type. (cal_backend_file_get_load_state): Implemented new method. * pcs/cal-backend-db.c (cal_backend_db_update_object): Call cal_backend_obj_updated(). (cal_backend_db_remove_object): Call cal_backend_obj_removed(). (cal_backend_db_get_load_state): Implemented new method. * pcs/cal.c (Cal_get_query): Implementation of the ::getQuery() method. svn path=/trunk/; revision=9013 --- calendar/ChangeLog | 56 ++- calendar/cal-client/cal-client.c | 2 +- calendar/idl/evolution-calendar.idl | 51 ++- calendar/pcs/Makefile.am | 4 +- calendar/pcs/cal-backend-db.c | 17 +- calendar/pcs/cal-backend-file.c | 31 +- calendar/pcs/cal-backend.c | 125 +++++- calendar/pcs/cal-backend.h | 13 +- calendar/pcs/cal.c | 40 +- calendar/pcs/query.c | 780 ++++++++++++++++++++++++++++++++++++ calendar/pcs/query.h | 69 ++++ 11 files changed, 1173 insertions(+), 15 deletions(-) create mode 100644 calendar/pcs/query.c create mode 100644 calendar/pcs/query.h diff --git a/calendar/ChangeLog b/calendar/ChangeLog index 2ac5f276b7..92668ba1e8 100644 --- a/calendar/ChangeLog +++ b/calendar/ChangeLog @@ -1,9 +1,63 @@ +2001-03-29 Federico Mena Quintero + + Engine for live queries to calendars. A query object watches a + CalBackend in the PCS and is otherwise completely separate from + it; backends need to do nothing to support live queries. Right + now we have the following functions: + + (get-vtype) + + Returns a string indicating the type of component + (VEVENT, VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, + UNKNOWN). + + (occur-in-time-range? START END) + + START - int, time_t start of the time range + END - int, time_t end of the time range + + Returns a boolean indicating whether the component + has any occurrences in the specified time range. + + * idl/evolution-calendar.idl (Cal::getQuery): New method that + initiates a live query. + (Query): New interface for a handle to a live query. + (QueryListener): New interface for a listener to changes in a live + query. + + * pcs/query.[ch]: New files with the live query engine. + + * pcs/cal-backend.h (CalBackendClass): Added notification signals + so that the query system can catch them. + (CalBackendClass): New virtual method ::get_load_state(). + + * pcs/cal-backend.c (cal_backend_opened): + (cal_backend_obj_updated): + (cal_backend_obj_updated): New functions to emit the notification + signals; to be used only by backend implementations. + (cal_backend_get_load_state): New function. + + * pcs/cal-backend-file.c (notify_update): Call + cal_backend_obj_updated(). + (notify_remove): Call call_backend_obj_removed(). + (open_cal): Free the icalcomp if it is not of the correct type. + (cal_backend_file_get_load_state): Implemented new method. + + * pcs/cal-backend-db.c (cal_backend_db_update_object): Call + cal_backend_obj_updated(). + (cal_backend_db_remove_object): Call cal_backend_obj_removed(). + (cal_backend_db_get_load_state): Implemented new method. + + * pcs/cal.c (Cal_get_query): Implementation of the ::getQuery() + method. + 2001-03-27 Anna Marie Dirks + * gui/e-itip-control.c: fixed button placement to comply with gnome standards. - 2001-03-27 Anna Marie Dirks + * gui/e-itip-control.glade: fixed spacing and label alignment to comply with gnome standards. Also removed shadows from extraneous scrolled windows to avoid bevelitous. There are many more changes diff --git a/calendar/cal-client/cal-client.c b/calendar/cal-client/cal-client.c index 43a881f50f..e81ff9afa3 100644 --- a/calendar/cal-client/cal-client.c +++ b/calendar/cal-client/cal-client.c @@ -737,7 +737,7 @@ cal_client_get_uids (CalClient *client, CalObjType type) CORBA_exception_init (&ev); - seq = GNOME_Evolution_Calendar_Cal_getUIds (priv->cal, t, &ev); + seq = GNOME_Evolution_Calendar_Cal_getUIDs (priv->cal, t, &ev); if (ev._major != CORBA_NO_EXCEPTION) { g_message ("cal_client_get_uids(): could not get the list of UIDs"); CORBA_exception_free (&ev); diff --git a/calendar/idl/evolution-calendar.idl b/calendar/idl/evolution-calendar.idl index f8f727c196..e21f77fe2e 100644 --- a/calendar/idl/evolution-calendar.idl +++ b/calendar/idl/evolution-calendar.idl @@ -98,13 +98,18 @@ module Calendar { /* Used to represent a list of components plus their triggers */ typedef sequence CalComponentAlarmsSeq; + interface Query; + interface Listener; + interface QueryListener; + /* Calendar client interface */ interface Cal : Bonobo::Unknown { exception NotFound {}; exception InvalidRange {}; exception InvalidObject {}; + exception CouldNotCreate {}; /* A calendar is identified by its URI */ readonly attribute string uri; @@ -117,7 +122,7 @@ module Calendar { raises (NotFound); /* Gets a list of UIDs based on component type */ - CalObjUIDSeq getUIds (in CalObjType type); + CalObjUIDSeq getUIDs (in CalObjType type); /* Gets a list of components that changed based on object type */ CalObjChangeSeq getChanges (in CalObjType type, in string change_id); @@ -151,6 +156,13 @@ module Calendar { /* Removes a component */ void removeObject (in CalObjUID uid) raises (NotFound); + + /* Initiates a live query of the calendar. Returns a handle + * to the live query itself; changes to components that are + * present in the query are notified to the listener. + */ + Query getQuery (in string sexp, in QueryListener ql) + raises (CouldNotCreate); }; /* Listener for changes in a calendar */ @@ -177,6 +189,43 @@ module Calendar { void notifyObjRemoved (in CalObjUID uid); }; + /* Handle to a live query on a calendar */ + interface Query : Bonobo::Unknown { + }; + + /* Listener for changes in a query of a calendar */ + interface QueryListener : Bonobo::Unknown { + /* Called when a component is added or changed. If + * query_in_progress is true, then the initial query results are + * being populated and the other arguments indicate the + * percentage of completion Otherwise, the percent value is + * unspecified. */ + void notifyObjUpdated (in CalObjUID uid, + in boolean query_in_progress, + in long n_scanned, + in long total); + + /* Called when a component is removed */ + void notifyObjRemoved (in CalObjUID uid); + + /* Reported when a query ends */ + enum QueryDoneStatus { + SUCCESS, + PARSE_ERROR + }; + + /* Called when the query finishes populating itself some time + * after it is created. Before this is called, + * notifyObjUpdated() may have been called several times to + * indicate which objects are actually in the query, unless the + * status result is a parse error. + */ + void notifyQueryDone (in QueryDoneStatus status, in string error_str); + + /* Called when an evaluation error occurs while performing a query */ + void notifyEvalError (in string error_str); + }; + /* A calendar factory, can load and create calendars */ interface CalFactory : Bonobo::Unknown { exception NilListener {}; diff --git a/calendar/pcs/Makefile.am b/calendar/pcs/Makefile.am index 984dcde86a..18156b4881 100644 --- a/calendar/pcs/Makefile.am +++ b/calendar/pcs/Makefile.am @@ -35,7 +35,9 @@ libpcs_a_SOURCES = \ cal-factory.c \ cal-factory.h \ job.c \ - job.h + job.h \ + query.c \ + query.h BUILT_SOURCES = $(CORBA_GENERATED) diff --git a/calendar/pcs/cal-backend-db.c b/calendar/pcs/cal-backend-db.c index fca87bd9ab..d4773760f8 100644 --- a/calendar/pcs/cal-backend-db.c +++ b/calendar/pcs/cal-backend-db.c @@ -67,6 +67,7 @@ static void cal_backend_db_add_cal (CalBackend *backend, Cal *cal); static CalBackendOpenStatus cal_backend_db_open (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_exists); +static gboolean cal_backend_db_is_loaded (CalBackend *backend); static int cal_backend_db_get_n_objects (CalBackend *backend, CalObjType type); static char *cal_backend_db_get_object (CalBackend *backend, const char *uid); @@ -150,6 +151,7 @@ cal_backend_db_class_init (CalBackendDBClass *klass) backend_class->get_uri = cal_backend_db_get_uri; backend_class->add_cal = cal_backend_db_add_cal; backend_class->open = cal_backend_db_open; + backend_class->is_loaded = cal_backend_db_is_loaded; backend_class->get_n_objects = cal_backend_db_get_n_objects; backend_class->get_object = cal_backend_db_get_object; backend_class->get_type_by_uid = cal_backend_db_get_type_by_uid; @@ -589,6 +591,17 @@ cal_backend_db_open (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_exi return CAL_BACKEND_OPEN_SUCCESS; } +/* is_loaded handler for the DB backend */ +static gboolean +cal_backend_db_is_loaded (CalBackend *backend) +{ + CalBackendDB *cbdb; + + cbdb = CAL_BACKEND_DB (backend); + + return (cbdb->priv->uri != NULL); +} + /* get_n_objects handler for the DB backend */ static int cal_backend_db_get_n_objects (CalBackend *backend, CalObjType type) @@ -1445,7 +1458,8 @@ cal_backend_db_update_object (CalBackend *backend, const char *uid, const char * return FALSE; } commit_transaction(tid); - + + cal_backend_obj_updated (CAL_BACKEND (cbdb), uid); do_notify(cbdb, cal_notify_update, uid); return TRUE; } @@ -1482,6 +1496,7 @@ cal_backend_db_remove_object (CalBackend *backend, const char *uid) /* TODO: update history database */ commit_transaction(tid); + cal_backend_obj_removed (CAL_BACKEND (cbdb), uid); do_notify(cbdb, cal_notify_remove, uid); return TRUE; diff --git a/calendar/pcs/cal-backend-file.c b/calendar/pcs/cal-backend-file.c index b3b3196324..a624db9f12 100644 --- a/calendar/pcs/cal-backend-file.c +++ b/calendar/pcs/cal-backend-file.c @@ -69,6 +69,7 @@ static GnomeVFSURI *cal_backend_file_get_uri (CalBackend *backend); static void cal_backend_file_add_cal (CalBackend *backend, Cal *cal); static CalBackendOpenStatus cal_backend_file_open (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_exists); +static gboolean cal_backend_file_is_loaded (CalBackend *backend); static int cal_backend_file_get_n_objects (CalBackend *backend, CalObjType type); static char *cal_backend_file_get_object (CalBackend *backend, const char *uid); @@ -143,6 +144,7 @@ cal_backend_file_class_init (CalBackendFileClass *class) backend_class->get_uri = cal_backend_file_get_uri; backend_class->add_cal = cal_backend_file_add_cal; backend_class->open = cal_backend_file_open; + backend_class->is_loaded = cal_backend_file_is_loaded; backend_class->get_n_objects = cal_backend_file_get_n_objects; backend_class->get_object = cal_backend_file_get_object; backend_class->get_type_by_uid = cal_backend_file_get_type_by_uid; @@ -163,6 +165,14 @@ cal_backend_file_init (CalBackendFile *cbfile) priv = g_new0 (CalBackendFilePrivate, 1); cbfile->priv = priv; + + priv->uri = NULL; + priv->clients = NULL; + priv->icalcomp = NULL; + priv->comp_uid_hash = NULL; + priv->events = NULL; + priv->todos = NULL; + priv->journals = NULL; } /* g_hash_table_foreach() callback to destroy a CalComponent */ @@ -640,8 +650,10 @@ open_cal (CalBackendFile *cbfile, GnomeVFSURI *uri, FILE *file) * individual components as well? */ - if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) + if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) { + icalcomponent_free (icalcomp); return CAL_BACKEND_OPEN_ERROR; + } priv->icalcomp = icalcomp; @@ -734,6 +746,19 @@ cal_backend_file_open (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_e } } +/* is_loaded handler for the file backend */ +static gboolean +cal_backend_file_is_loaded (CalBackend *backend) +{ + CalBackendFile *cbfile; + CalBackendFilePrivate *priv; + + cbfile = CAL_BACKEND_FILE (backend); + priv = cbfile->priv; + + return (priv->icalcomp != NULL); +} + /* Get_n_objects handler for the file backend */ static int cal_backend_file_get_n_objects (CalBackend *backend, CalObjType type) @@ -1469,6 +1494,8 @@ notify_update (CalBackendFile *cbfile, const char *uid) priv = cbfile->priv; + cal_backend_obj_updated (CAL_BACKEND (cbfile), uid); + for (l = priv->clients; l; l = l->next) { Cal *cal; @@ -1486,6 +1513,8 @@ notify_remove (CalBackendFile *cbfile, const char *uid) priv = cbfile->priv; + cal_backend_obj_removed (CAL_BACKEND (cbfile), uid); + for (l = priv->clients; l; l = l->next) { Cal *cal; diff --git a/calendar/pcs/cal-backend.c b/calendar/pcs/cal-backend.c index ce53cda78a..350e6468e4 100644 --- a/calendar/pcs/cal-backend.c +++ b/calendar/pcs/cal-backend.c @@ -37,6 +37,9 @@ /* Signal IDs */ enum { LAST_CLIENT_GONE, + OPENED, + OBJ_UPDATED, + OBJ_REMOVED, LAST_SIGNAL }; @@ -96,8 +99,51 @@ cal_backend_class_init (CalBackendClass *class) GTK_SIGNAL_OFFSET (CalBackendClass, last_client_gone), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); + cal_backend_signals[OPENED] = + gtk_signal_new ("opened", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (CalBackendClass, opened), + gtk_marshal_NONE__ENUM, + GTK_TYPE_NONE, 1, + GTK_TYPE_ENUM); + cal_backend_signals[OBJ_UPDATED] = + gtk_signal_new ("obj_updated", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (CalBackendClass, obj_updated), + gtk_marshal_NONE__STRING, + GTK_TYPE_NONE, 1, + GTK_TYPE_STRING); + cal_backend_signals[OBJ_REMOVED] = + gtk_signal_new ("obj_removed", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (CalBackendClass, obj_removed), + gtk_marshal_NONE__STRING, + GTK_TYPE_NONE, 1, + GTK_TYPE_STRING); gtk_object_class_add_signals (object_class, cal_backend_signals, LAST_SIGNAL); + + class->last_client_gone = NULL; + class->opened = NULL; + class->obj_updated = NULL; + class->obj_removed = NULL; + + class->get_uri = NULL; + class->add_cal = NULL; + class->open = NULL; + class->get_n_objects = NULL; + class->get_object = NULL; + class->get_type_by_uid = NULL; + class->get_uids = NULL; + class->get_objects_in_range = NULL; + class->get_changes = NULL; + class->get_alarms_in_range = NULL; + class->get_alarms_for_object = NULL; + class->update_object = NULL; + class->remove_object = NULL; } @@ -162,7 +208,29 @@ cal_backend_open (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_exists g_return_val_if_fail (uri != NULL, CAL_BACKEND_OPEN_ERROR); g_assert (CLASS (backend)->open != NULL); - result = (* CLASS (backend)->open) (backend, uri, only_if_exists); + result = (* CLASS (backend)->open) (backend, uri, only_if_exists); + + return result; +} + +/** + * cal_backend_is_loaded: + * @backend: A calendar backend. + * + * Queries whether a calendar backend has been loaded yet. + * + * Return value: TRUE if the backend has been loaded with data, FALSE otherwise. + **/ +gboolean +cal_backend_is_loaded (CalBackend *backend) +{ + gboolean result; + + g_return_val_if_fail (backend != NULL, FALSE); + g_return_val_if_fail (IS_CAL_BACKEND (backend), FALSE); + + g_assert (CLASS (backend)->is_loaded != NULL); + result = (* CLASS (backend)->is_loaded) (backend); return result; } @@ -417,3 +485,58 @@ cal_backend_last_client_gone (CalBackend *backend) gtk_signal_emit (GTK_OBJECT (backend), cal_backend_signals[LAST_CLIENT_GONE]); } +/** + * cal_backend_opened: + * @backend: A calendar backend. + * @status: Open status code. + * + * Emits the "opened" signal of a calendar backend. This function is to be used + * only by backend implementations. + **/ +void +cal_backend_opened (CalBackend *backend, CalBackendOpenStatus status) +{ + g_return_if_fail (backend != NULL); + g_return_if_fail (IS_CAL_BACKEND (backend)); + + gtk_signal_emit (GTK_OBJECT (backend), cal_backend_signals[OPENED], + status); +} + +/** + * cal_backend_obj_updated: + * @backend: A calendar backend. + * @uid: Unique identifier of the component that was updated. + * + * Emits the "obj_updated" signal of a calendar backend. This function is to be + * used only by backend implementations. + **/ +void +cal_backend_obj_updated (CalBackend *backend, const char *uid) +{ + g_return_if_fail (backend != NULL); + g_return_if_fail (IS_CAL_BACKEND (backend)); + g_return_if_fail (uid != NULL); + + gtk_signal_emit (GTK_OBJECT (backend), cal_backend_signals[OBJ_UPDATED], + uid); +} + +/** + * cal_backend_obj_removed: + * @backend: A calendar backend. + * @uid: Unique identifier of the component that was removed. + * + * Emits the "obj_removed" signal of a calendar backend. This function is to be + * used only by backend implementations. + **/ +void +cal_backend_obj_removed (CalBackend *backend, const char *uid) +{ + g_return_if_fail (backend != NULL); + g_return_if_fail (IS_CAL_BACKEND (backend)); + g_return_if_fail (uid != NULL); + + gtk_signal_emit (GTK_OBJECT (backend), cal_backend_signals[OBJ_REMOVED], + uid); +} diff --git a/calendar/pcs/cal-backend.h b/calendar/pcs/cal-backend.h index 3784601ddf..dab06b21e8 100644 --- a/calendar/pcs/cal-backend.h +++ b/calendar/pcs/cal-backend.h @@ -43,7 +43,7 @@ BEGIN_GNOME_DECLS #define IS_CAL_BACKEND(obj) (GTK_CHECK_TYPE ((obj), CAL_BACKEND_TYPE)) #define IS_CAL_BACKEND_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), CAL_BACKEND_TYPE)) -/* Load status values */ +/* Open status values */ typedef enum { CAL_BACKEND_OPEN_SUCCESS, /* Loading OK */ CAL_BACKEND_OPEN_ERROR, /* We need better error reporting in libversit */ @@ -67,6 +67,10 @@ struct _CalBackendClass { /* Notification signals */ void (* last_client_gone) (CalBackend *backend); + void (* opened) (CalBackend *backend, CalBackendOpenStatus status); + void (* obj_updated) (CalBackend *backend, const char *uid); + void (* obj_removed) (CalBackend *backend, const char *uid); + /* Virtual methods */ GnomeVFSURI *(* get_uri) (CalBackend *backend); void (* add_cal) (CalBackend *backend, Cal *cal); @@ -74,6 +78,8 @@ struct _CalBackendClass { CalBackendOpenStatus (* open) (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_exists); + gboolean (* is_loaded) (CalBackend *backend); + /* General object acquirement and information related virtual methods */ int (* get_n_objects) (CalBackend *backend, CalObjType type); char *(* get_object) (CalBackend *backend, const char *uid); @@ -108,6 +114,8 @@ void cal_backend_add_cal (CalBackend *backend, Cal *cal); CalBackendOpenStatus cal_backend_open (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_exists); +gboolean cal_backend_is_loaded (CalBackend *backend); + int cal_backend_get_n_objects (CalBackend *backend, CalObjType type); char *cal_backend_get_object (CalBackend *backend, const char *uid); @@ -134,6 +142,9 @@ gboolean cal_backend_update_object (CalBackend *backend, const char *uid, const gboolean cal_backend_remove_object (CalBackend *backend, const char *uid); void cal_backend_last_client_gone (CalBackend *backend); +void cal_backend_opened (CalBackend *backend, CalBackendOpenStatus status); +void cal_backend_obj_updated (CalBackend *backend, const char *uid); +void cal_backend_obj_removed (CalBackend *backend, const char *uid); diff --git a/calendar/pcs/cal.c b/calendar/pcs/cal.c index 69eccd7f55..3129665466 100644 --- a/calendar/pcs/cal.c +++ b/calendar/pcs/cal.c @@ -22,7 +22,7 @@ #include #include "cal.h" -#include "cal-backend.h" +#include "query.h" @@ -446,6 +446,31 @@ Cal_remove_object (PortableServer_Servant servant, NULL); } +/* Cal::getQuery implementation */ +static GNOME_Evolution_Calendar_Query +Cal_get_query (PortableServer_Servant servant, + const CORBA_char *sexp, + GNOME_Evolution_Calendar_QueryListener ql, + CORBA_Environment *ev) +{ + Cal *cal; + CalPrivate *priv; + Query *query; + + cal = CAL (bonobo_object_from_servant (servant)); + priv = cal->priv; + + query = query_new (priv->backend, ql, sexp); + if (!query) { + CORBA_exception_set (ev, CORBA_USER_EXCEPTION, + ex_GNOME_Evolution_Calendar_Cal_CouldNotCreate, + NULL); + return CORBA_OBJECT_NIL; + } + + return BONOBO_OBJREF (query); +} + /** * cal_get_epv: * @void: @@ -460,16 +485,17 @@ cal_get_epv (void) POA_GNOME_Evolution_Calendar_Cal__epv *epv; epv = g_new0 (POA_GNOME_Evolution_Calendar_Cal__epv, 1); - epv->_get_uri = Cal_get_uri; + epv->_get_uri = Cal_get_uri; epv->countObjects = Cal_get_n_objects; - epv->getObject = Cal_get_object; - epv->getUIds = Cal_get_uids; - epv->getChanges = Cal_get_changes; - epv->getObjectsInRange = Cal_get_objects_in_range; - epv->getAlarmsInRange = Cal_get_alarms_in_range; + epv->getObject = Cal_get_object; + epv->getUIDs = Cal_get_uids; + epv->getChanges = Cal_get_changes; + epv->getObjectsInRange = Cal_get_objects_in_range; + epv->getAlarmsInRange = Cal_get_alarms_in_range; epv->getAlarmsForObject = Cal_get_alarms_for_object; epv->updateObject = Cal_update_object; epv->removeObject = Cal_remove_object; + epv->getQuery = Cal_get_query; return epv; } diff --git a/calendar/pcs/query.c b/calendar/pcs/query.c new file mode 100644 index 0000000000..708a160794 --- /dev/null +++ b/calendar/pcs/query.c @@ -0,0 +1,780 @@ +/* Evolution calendar - Live search query implementation + * + * Copyright (C) 2001 Ximian, Inc. + * + * Author: Federico Mena-Quintero + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "query.h" + + + +/* Private part of the Query structure */ +struct _QueryPrivate { + /* The backend we are monitoring */ + CalBackend *backend; + + /* Listener to which we report changes in the live query */ + GNOME_Evolution_Calendar_QueryListener ql; + + /* Sexp that defines the query */ + char *sexp; + ESExp *esexp; + + /* Idle handler ID for asynchronous queries */ + guint idle_id; + + /* List of UIDs that we still have to process */ + GList *pending_uids; + int n_pending; + int pending_total; + + /* Table of the UIDs we know do match the query */ + GHashTable *uids; + + /* The next component that will be handled in e_sexp_eval(); put here + * just because the query object itself is the esexp context. + */ + CalComponent *next_comp; +}; + + + +static void query_class_init (QueryClass *class); +static void query_init (Query *query); +static void query_destroy (GtkObject *object); + +static BonoboXObjectClass *parent_class; + + + +BONOBO_X_TYPE_FUNC_FULL (Query, + GNOME_Evolution_Calendar_Query, + BONOBO_X_OBJECT_TYPE, + query); + +/* Class initialization function for the live search query */ +static void +query_class_init (QueryClass *class) +{ + GtkObjectClass *object_class; + + object_class = (GtkObjectClass *) class; + + parent_class = gtk_type_class (BONOBO_X_OBJECT_TYPE); + + object_class->destroy = query_destroy; + + /* The Query interface (ha ha! query interface!) has no methods, so we + * don't need to fiddle with the epv. + */ +} + +/* Object initialization function for the live search query */ +static void +query_init (Query *query) +{ + QueryPrivate *priv; + + priv = g_new0 (QueryPrivate, 1); + query->priv = priv; + + priv->backend = NULL; + priv->ql = CORBA_OBJECT_NIL; + priv->sexp = NULL; + + priv->pending_uids = NULL; + priv->uids = g_hash_table_new (g_str_hash, g_str_equal); + + priv->next_comp = NULL; +} + +/* Used from g_hash_table_foreach(); frees a UID */ +static void +free_uid_cb (gpointer key, gpointer value, gpointer data) +{ + char *uid; + + uid = key; + g_free (uid); +} + +/* Destroy handler for the live search query */ +static void +query_destroy (GtkObject *object) +{ + Query *query; + QueryPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (IS_QUERY (object)); + + query = QUERY (object); + priv = query->priv; + + if (priv->backend) { + gtk_object_unref (GTK_OBJECT (priv->backend)); + priv->backend = NULL; + } + + if (priv->ql != CORBA_OBJECT_NIL) { + CORBA_Environment ev; + + CORBA_exception_init (&ev); + bonobo_object_release_unref (priv->ql, &ev); + + if (ev._major != CORBA_NO_EXCEPTION) + g_message ("query_destroy(): Could not unref the listener\n"); + + CORBA_exception_free (&ev); + + priv->ql = CORBA_OBJECT_NIL; + } + + if (priv->sexp) { + g_free (priv->sexp); + priv->sexp = NULL; + } + + if (priv->esexp) { + e_sexp_unref (priv->esexp); + priv->esexp = NULL; + } + + if (priv->idle_id) { + g_source_remove (priv->idle_id); + priv->idle_id = 0; + } + + if (priv->pending_uids) { + GList *l; + + for (l = priv->pending_uids; l; l = l->next) { + char *uid; + + uid = l->data; + g_assert (uid != NULL); + g_free (uid); + } + + g_list_free (priv->pending_uids); + priv->pending_uids = NULL; + priv->n_pending = 0; + } + + g_hash_table_foreach (priv->uids, free_uid_cb, NULL); + g_hash_table_destroy (priv->uids); + priv->uids = NULL; + + g_free (priv); + query->priv = NULL; + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + + + +/* Search engine functions */ + +/* (get-vtype) + * + * Returns a string indicating the type of component (VEVENT, VTODO, VJOURNAL, + * VFREEBUSY, VTIMEZONE, UNKNOWN). + */ +static ESExpResult * +func_get_vtype (ESExp *esexp, int argc, ESExpResult **argv, void *data) +{ + Query *query; + QueryPrivate *priv; + CalComponent *comp; + CalComponentVType vtype; + char *str; + ESExpResult *result; + + query = QUERY (data); + priv = query->priv; + + g_assert (priv->next_comp != NULL); + comp = priv->next_comp; + + /* Check argument types */ + + if (argc != 0) { + e_sexp_fatal_error (esexp, _("get-vtype expects 0 arguments")); + return NULL; + } + + /* Get the type */ + + vtype = cal_component_get_vtype (comp); + + switch (vtype) { + case CAL_COMPONENT_EVENT: + str = g_strdup ("VEVENT"); + break; + + case CAL_COMPONENT_TODO: + str = g_strdup ("VTODO"); + break; + + case CAL_COMPONENT_JOURNAL: + str = g_strdup ("VJOURNAL"); + break; + + case CAL_COMPONENT_FREEBUSY: + str = g_strdup ("VFREEBUSY"); + break; + + case CAL_COMPONENT_TIMEZONE: + str = g_strdup ("VTIMEZONE"); + break; + + default: + str = g_strdup ("UNKNOWN"); + break; + } + + result = e_sexp_result_new (esexp, ESEXP_RES_STRING); + result->value.string = str; + + return result; +} + +/* Sets a boolean value in the data to TRUE; called from + * cal_recur_generate_instances() to indicate that at least one instance occurs + * in the sought time range. We always return FALSE because we want the + * recurrence engine to finish as soon as possible. + */ +static gboolean +instance_occur_cb (CalComponent *comp, time_t start, time_t end, gpointer data) +{ + gboolean *occurs; + + occurs = data; + *occurs = TRUE; + + return FALSE; +} + +/* (occur-in-time-range? START END) + * + * START - int, time_t start of the time range + * END - int, time_t end of the time range + * + * Returns a boolean indicating whether the component has any occurrences in the + * specified time range. + * + * We may prefer to switch this to a string representation of times (ISO 8601, + * perhaps). */ +static ESExpResult * +func_occur_in_time_range (ESExp *esexp, int argc, ESExpResult **argv, void *data) +{ + Query *query; + QueryPrivate *priv; + CalComponent *comp; + time_t start, end; + gboolean occurs; + ESExpResult *result; + + query = QUERY (data); + priv = query->priv; + + g_assert (priv->next_comp != NULL); + comp = priv->next_comp; + + /* Check argument types */ + + if (argc != 2) { + e_sexp_fatal_error (esexp, _("occur-in-time-range? expects 2 arguments")); + return NULL; + } + + if (argv[0]->type != ESEXP_RES_INT) { + e_sexp_fatal_error (esexp, _("occur-in-time-range? expects argument 1 " + "to be an integer")); + return NULL; + } + start = argv[0]->value.number; + + if (argv[1]->type != ESEXP_RES_INT) { + e_sexp_fatal_error (esexp, _("occur-in-time-range? expects argument 2 " + "to be an integer")); + return NULL; + } + end = argv[1]->value.number; + + /* See if there is at least one instance in that range */ + + occurs = FALSE; + cal_recur_generate_instances (comp, start, end, instance_occur_cb, &occurs); + + result = e_sexp_result_new (esexp, ESEXP_RES_BOOL); + result->value.bool = occurs; + + return result; +} + + + +/* Adds a component to our the UIDs hash table and notifies the client */ +static void +add_component (Query *query, const char *uid, gboolean query_in_progress, int n_scanned, int total) +{ + QueryPrivate *priv; + char *old_uid; + CORBA_Environment ev; + + if (query_in_progress) + g_assert (n_scanned > 0 || n_scanned <= total); + + priv = query->priv; + + if (g_hash_table_lookup_extended (priv->uids, uid, (gpointer *) &old_uid, NULL)) { + g_hash_table_remove (priv->uids, old_uid); + g_free (old_uid); + } + + g_hash_table_insert (priv->uids, g_strdup (uid), NULL); + + CORBA_exception_init (&ev); + GNOME_Evolution_Calendar_QueryListener_notifyObjUpdated ( + priv->ql, + (char *) uid, + query_in_progress, + n_scanned, + total, + &ev); + + if (ev._major != CORBA_NO_EXCEPTION) + g_message ("add_component(): Could not notify the listener of an " + "updated component"); + + CORBA_exception_free (&ev); +} + +/* Removes a component from our the UIDs hash table and notifies the client */ +static void +remove_component (Query *query, const char *uid) +{ + QueryPrivate *priv; + char *old_uid; + CORBA_Environment ev; + + priv = query->priv; + + if (!g_hash_table_lookup_extended (priv->uids, uid, (gpointer *) &old_uid, NULL)) + return; + + /* The component did match the query before but it no longer does, so we + * have to notify the client. + */ + + g_hash_table_remove (priv->uids, old_uid); + g_free (old_uid); + + CORBA_exception_init (&ev); + GNOME_Evolution_Calendar_QueryListener_notifyObjRemoved ( + priv->ql, + (char *) uid, + &ev); + + if (ev._major != CORBA_NO_EXCEPTION) + g_message ("remove_component(): Could not notify the listener of a " + "removed component"); + + CORBA_exception_free (&ev); +} + +/* Removes a component from the list of pending UIDs */ +static void +remove_from_pending (Query *query, const char *remove_uid) +{ + QueryPrivate *priv; + GList *l; + + priv = query->priv; + + for (l = priv->pending_uids; l; l = l->next) { + char *uid; + + g_assert (priv->n_pending > 0); + + uid = l->data; + if (strcmp (remove_uid, uid)) + continue; + + g_free (uid); + + priv->pending_uids = g_list_remove_link (priv->pending_uids, l); + g_list_free_1 (l); + priv->n_pending--; + + g_assert ((priv->pending_uids && priv->n_pending != 0) + || (!priv->pending_uids && priv->n_pending == 0)); + + break; + } +} + +static struct { + char *name; + ESExpFunc *func; +} functions[] = { + { "get-vtype", func_get_vtype }, + { "occur-in-time-range?", func_occur_in_time_range } +}; + +/* Initializes a sexp by interning our own symbols */ +static ESExp * +create_sexp (Query *query) +{ + ESExp *esexp; + int i; + + esexp = e_sexp_new (); + + for (i = 0; i < (sizeof (functions) / sizeof (functions[0])); i++) + e_sexp_add_function (esexp, 0, functions[i].name, functions[i].func, query); + + return esexp; +} + +/* Evaluates the query sexp on the specified component and notifies the listener + * as appropriate. + */ +static void +match_component (Query *query, const char *uid, + gboolean query_in_progress, int n_scanned, int total) +{ + QueryPrivate *priv; + char *comp_str; + CalComponent *comp; + icalcomponent *icalcomp; + gboolean set_succeeded; + ESExpResult *result; + + priv = query->priv; + + comp_str = cal_backend_get_object (priv->backend, uid); + g_assert (comp_str != NULL); + + icalcomp = icalparser_parse_string (comp_str); + g_assert (icalcomp != NULL); + + comp = cal_component_new (); + set_succeeded = cal_component_set_icalcomponent (comp, icalcomp); + g_assert (set_succeeded); + + /* Eval the sexp */ + + g_assert (priv->next_comp == NULL); + + priv->next_comp = comp; + result = e_sexp_eval (priv->esexp); + gtk_object_unref (GTK_OBJECT (comp)); + priv->next_comp = NULL; + + if (!result) { + const char *error_str; + CORBA_Environment ev; + + error_str = e_sexp_error (priv->esexp); + g_assert (error_str != NULL); + + CORBA_exception_init (&ev); + GNOME_Evolution_Calendar_QueryListener_notifyEvalError ( + priv->ql, + error_str, + &ev); + + if (ev._major != CORBA_NO_EXCEPTION) + g_message ("process_component_cb(): Could not notify the listener of " + "an evaluation error"); + + CORBA_exception_free (&ev); + } else if (result->type != ESEXP_RES_BOOL) { + CORBA_Environment ev; + + CORBA_exception_init (&ev); + GNOME_Evolution_Calendar_QueryListener_notifyEvalError ( + priv->ql, + _("Evaluation of the search expression did not yield a boolean value"), + &ev); + + if (ev._major != CORBA_NO_EXCEPTION) + g_message ("process_component_cb(): Could not notify the listener of " + "an unexpected result value type when evaluating the " + "search expression"); + + CORBA_exception_free (&ev); + } else { + /* Success; process the component accordingly */ + + if (result->value.bool) + add_component (query, uid, query_in_progress, n_scanned, total); + else + remove_component (query, uid); + } +} + +/* Processes a single component that is queued in the list */ +static gboolean +process_component_cb (gpointer data) +{ + Query *query; + QueryPrivate *priv; + char *uid; + GList *l; + + query = QUERY (data); + priv = query->priv; + + /* No more components? */ + + if (!priv->pending_uids) { + g_assert (priv->n_pending == 0); + + g_source_remove (priv->idle_id); + priv->idle_id = 0; + return FALSE; + } + + g_assert (priv->n_pending > 0); + + /* Fetch the component */ + + l = priv->pending_uids; + priv->pending_uids = g_list_remove_link (priv->pending_uids, l); + priv->n_pending--; + + g_assert ((priv->pending_uids && priv->n_pending != 0) + || (!priv->pending_uids && priv->n_pending == 0)); + + uid = l->data; + g_assert (uid != NULL); + + g_list_free_1 (l); + + match_component (query, uid, + TRUE, + priv->pending_total - priv->n_pending, + priv->pending_total); + + g_free (uid); + + return TRUE; +} + +/* Populates the query with pending UIDs so that they can be processed + * asynchronously. + */ +static void +populate_query (Query *query) +{ + QueryPrivate *priv; + + priv = query->priv; + g_assert (priv->idle_id == 0); + + priv->pending_uids = cal_backend_get_uids (priv->backend, CALOBJ_TYPE_ANY); + priv->pending_total = g_list_length (priv->pending_uids); + priv->n_pending = priv->pending_total; + + priv->idle_id = g_idle_add (process_component_cb, query); +} + +/* Idle handler for starting a query */ +static gboolean +start_query_cb (gpointer data) +{ + Query *query; + QueryPrivate *priv; + CORBA_Environment ev; + + query = QUERY (data); + priv = query->priv; + + priv->idle_id = 0; + + priv->esexp = create_sexp (query); + + /* Compile the query string */ + + g_assert (priv->sexp != NULL); + e_sexp_input_text (priv->esexp, priv->sexp, strlen (priv->sexp)); + + if (e_sexp_parse (priv->esexp) == -1) { + const char *error_str; + + error_str = e_sexp_error (priv->esexp); + g_assert (error_str != NULL); + + CORBA_exception_init (&ev); + GNOME_Evolution_Calendar_QueryListener_notifyQueryDone ( + priv->ql, + GNOME_Evolution_Calendar_QueryListener_PARSE_ERROR, + error_str, + &ev); + + if (ev._major != CORBA_NO_EXCEPTION) + g_message ("start_query_cb(): Could not notify the listener of " + "a parse error"); + + CORBA_exception_free (&ev); + return FALSE; + } + + /* Populate the query with UIDs so that we can process them asynchronously */ + + populate_query (query); + + return FALSE; +} + +/* Callback used when the backend gets loaded; we just queue the query to be + * started later. + */ +static void +backend_opened_cb (CalBackend *backend, CalBackendOpenStatus status, gpointer data) +{ + Query *query; + QueryPrivate *priv; + + query = QUERY (data); + priv = query->priv; + + if (status == CAL_BACKEND_OPEN_SUCCESS) { + g_assert (cal_backend_is_loaded (backend)); + + priv->idle_id = g_idle_add (start_query_cb, query); + } +} + +/* Callback used when a component changes in the backend */ +static void +backend_obj_updated_cb (CalBackend *backend, const char *uid, gpointer data) +{ + Query *query; + + query = QUERY (data); + + match_component (query, uid, FALSE, 0, 0); + remove_from_pending (query, uid); +} + +/* Callback used when a component is removed from the backend */ +static void +backend_obj_removed_cb (CalBackend *backend, const char *uid, gpointer data) +{ + Query *query; + QueryPrivate *priv; + + query = QUERY (data); + priv = query->priv; + + remove_component (query, uid); + remove_from_pending (query, uid); +} + +/** + * query_construct: + * @query: A live search query. + * @backend: Calendar backend that the query object will monitor. + * @ql: Listener for query results. + * @sexp: Sexp that defines the query. + * + * Constructs a #Query object by binding it to a calendar backend and a query + * listener. The @query object will start to populate itself asynchronously and + * call the listener as appropriate. + * + * Return value: The same value as @query, or NULL if the query could not + * be constructed. + **/ +Query * +query_construct (Query *query, + CalBackend *backend, + GNOME_Evolution_Calendar_QueryListener ql, + const char *sexp) +{ + QueryPrivate *priv; + CORBA_Environment ev; + + g_return_val_if_fail (query != NULL, NULL); + g_return_val_if_fail (IS_QUERY (query), NULL); + g_return_val_if_fail (backend != NULL, NULL); + g_return_val_if_fail (IS_CAL_BACKEND (backend), NULL); + g_return_val_if_fail (ql != CORBA_OBJECT_NIL, NULL); + g_return_val_if_fail (sexp != NULL, NULL); + + priv = query->priv; + + CORBA_exception_init (&ev); + priv->ql = CORBA_Object_duplicate (ql, &ev); + if (ev._major != CORBA_NO_EXCEPTION) { + g_message ("query_construct(): Could not duplicate the listener"); + priv->ql = CORBA_OBJECT_NIL; + CORBA_exception_free (&ev); + return NULL; + } + CORBA_exception_free (&ev); + + priv->backend = backend; + gtk_object_ref (GTK_OBJECT (priv->backend)); + + gtk_signal_connect (GTK_OBJECT (priv->backend), "obj_updated", + GTK_SIGNAL_FUNC (backend_obj_updated_cb), + query); + gtk_signal_connect (GTK_OBJECT (priv->backend), "obj_removed", + GTK_SIGNAL_FUNC (backend_obj_removed_cb), + query); + + priv->sexp = g_strdup (sexp); + + /* Queue the query to be started asynchronously */ + + if (cal_backend_is_loaded (priv->backend)) + priv->idle_id = g_idle_add (start_query_cb, query); + else + gtk_signal_connect (GTK_OBJECT (priv->backend), "opened", + GTK_SIGNAL_FUNC (backend_opened_cb), + query); + + return query; +} + +Query * +query_new (CalBackend *backend, + GNOME_Evolution_Calendar_QueryListener ql, + const char *sexp) +{ + Query *query; + + query = QUERY (gtk_type_new (QUERY_TYPE)); + return query_construct (query, backend, ql, sexp); +} diff --git a/calendar/pcs/query.h b/calendar/pcs/query.h new file mode 100644 index 0000000000..bd10351fcb --- /dev/null +++ b/calendar/pcs/query.h @@ -0,0 +1,69 @@ +/* Evolution calendar - Live search query implementation + * + * Copyright (C) 2001 Ximian, Inc. + * + * Author: Federico Mena-Quintero + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef QUERY_H +#define QUERY_H + +#include +#include +#include "cal-backend.h" + +BEGIN_GNOME_DECLS + + + +#define QUERY_TYPE (query_get_type ()) +#define QUERY(obj) (GTK_CHECK_CAST ((obj), QUERY_TYPE, Query)) +#define QUERY_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), QUERY_TYPE, QueryClass)) +#define IS_QUERY(obj) (GTK_CHECK_TYPE ((obj), QUERY_TYPE)) +#define IS_QUERY_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), QUERY_TYPE)) + +typedef struct _QueryPrivate QueryPrivate; + +typedef struct { + BonoboXObject xobject; + + /* Private data */ + QueryPrivate *priv; +} Query; + +typedef struct { + BonoboXObjectClass parent_class; + + POA_GNOME_Evolution_Calendar_Query__epv epv; +} QueryClass; + +GtkType query_get_type (void); + +Query *query_construct (Query *query, + CalBackend *backend, + GNOME_Evolution_Calendar_QueryListener ql, + const char *sexp); + +Query *query_new (CalBackend *backend, + GNOME_Evolution_Calendar_QueryListener ql, + const char *sexp); + + + +END_GNOME_DECLS + +#endif -- cgit v1.2.3