aboutsummaryrefslogtreecommitdiffstats
path: root/calendar/pcs
diff options
context:
space:
mode:
authorFederico Mena Quintero <federico@ximian.com>2001-03-30 00:51:38 +0800
committerFederico Mena Quintero <federico@src.gnome.org>2001-03-30 00:51:38 +0800
commitfeaa9ddc81a8d4f0c3c037fd2822a56107bbab6b (patch)
tree3cfa33b74e4fac905b9aac2f5f346ab71555cdfe /calendar/pcs
parent1548cfd25910747414952bde0fd479222e7abdf5 (diff)
downloadgsoc2013-evolution-feaa9ddc81a8d4f0c3c037fd2822a56107bbab6b.tar
gsoc2013-evolution-feaa9ddc81a8d4f0c3c037fd2822a56107bbab6b.tar.gz
gsoc2013-evolution-feaa9ddc81a8d4f0c3c037fd2822a56107bbab6b.tar.bz2
gsoc2013-evolution-feaa9ddc81a8d4f0c3c037fd2822a56107bbab6b.tar.lz
gsoc2013-evolution-feaa9ddc81a8d4f0c3c037fd2822a56107bbab6b.tar.xz
gsoc2013-evolution-feaa9ddc81a8d4f0c3c037fd2822a56107bbab6b.tar.zst
gsoc2013-evolution-feaa9ddc81a8d4f0c3c037fd2822a56107bbab6b.zip
Engine for live queries to calendars. A query object watches a CalBackend
2001-03-29 Federico Mena Quintero <federico@ximian.com> 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
Diffstat (limited to 'calendar/pcs')
-rw-r--r--calendar/pcs/Makefile.am4
-rw-r--r--calendar/pcs/cal-backend-db.c17
-rw-r--r--calendar/pcs/cal-backend-file.c31
-rw-r--r--calendar/pcs/cal-backend.c125
-rw-r--r--calendar/pcs/cal-backend.h13
-rw-r--r--calendar/pcs/cal.c40
-rw-r--r--calendar/pcs/query.c780
-rw-r--r--calendar/pcs/query.h69
8 files changed, 1067 insertions, 12 deletions
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 <config.h>
#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 <federico@ximian.com>
+ *
+ * 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 <config.h>
+#endif
+
+#include <string.h>
+#include <glib.h>
+#include <libgnome/gnome-defs.h>
+#include <libgnome/gnome-i18n.h>
+#include <gtk/gtksignal.h>
+#include <e-util/e-sexp.h>
+#include <cal-util/cal-recur.h>
+#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 <federico@ximian.com>
+ *
+ * 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 <libgnome/gnome-defs.h>
+#include <bonobo/bonobo-xobject.h>
+#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