2002-11-07 Rodrigo Moya <rodrigo@ximian.com> * pcs/cal.[ch]: converted to BonoboObject. (impl_Cal_get_query): bonobo_object_unref the query returned by cal_backend_get_query if we can't duplicate it. * pcs/query.[ch]: * pcs/cal-factory.[ch]: converted to BonoboObject. * pcs/query-backend.[ch]: * pcs/cal-backend-file.[ch]: * pcs/cal-backend.[ch]: GObjectify. svn path=/trunk/; revision=18631
1734 lines
41 KiB
C
1734 lines
41 KiB
C
/* 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 version 2 of the GNU General Public
|
||
* License as published by the Free Software Foundation.
|
||
*
|
||
* 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 <gtk/gtkmain.h>
|
||
#include <libgnome/gnome-i18n.h>
|
||
#include <bonobo/bonobo-exception.h>
|
||
#include <gal/widgets/e-unicode.h>
|
||
#include <e-util/e-component-listener.h>
|
||
#include <e-util/e-sexp.h>
|
||
#include <cal-util/cal-recur.h>
|
||
#include <cal-util/timeutil.h>
|
||
#include "cal-backend.h"
|
||
#include "query.h"
|
||
#include "query-backend.h"
|
||
|
||
|
||
|
||
typedef struct {
|
||
Query *query;
|
||
GNOME_Evolution_Calendar_QueryListener ql;
|
||
guint tid;
|
||
} StartCachedQueryInfo;
|
||
|
||
/* States of a query */
|
||
typedef enum {
|
||
QUERY_WAIT_FOR_BACKEND, /* the query is not populated and the backend is not loaded */
|
||
QUERY_START_PENDING, /* the query is not populated yet, but the backend is loaded */
|
||
QUERY_IN_PROGRESS, /* the query is populated; components are still being processed */
|
||
QUERY_DONE, /* the query is done, but still accepts object changes */
|
||
QUERY_PARSE_ERROR /* a parse error occurred when initially creating the ESexp */
|
||
} QueryState;
|
||
|
||
/* Private part of the Query structure */
|
||
struct _QueryPrivate {
|
||
/* The backend we are monitoring */
|
||
CalBackend *backend;
|
||
|
||
/* The cache backend */
|
||
QueryBackend *qb;
|
||
|
||
/* The default timezone for the calendar. */
|
||
icaltimezone *default_zone;
|
||
|
||
/* Listeners to which we report changes in the live query */
|
||
GList *listeners;
|
||
GList *component_listeners;
|
||
|
||
/* Sexp that defines the query */
|
||
char *sexp;
|
||
ESExp *esexp;
|
||
|
||
/* Timeout handler ID for asynchronous queries and current state of the query */
|
||
guint timeout_id;
|
||
QueryState state;
|
||
|
||
GList *cached_timeouts;
|
||
|
||
/* 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, QueryClass *class);
|
||
static void query_finalize (GObject *object);
|
||
|
||
static BonoboObjectClass *parent_class;
|
||
static GList *cached_queries = NULL;
|
||
|
||
|
||
|
||
BONOBO_TYPE_FUNC_FULL (Query,
|
||
GNOME_Evolution_Calendar_Query,
|
||
BONOBO_TYPE_OBJECT,
|
||
query);
|
||
|
||
/* Class initialization function for the live search query */
|
||
static void
|
||
query_class_init (QueryClass *class)
|
||
{
|
||
GObjectClass *object_class;
|
||
|
||
object_class = (GObjectClass *) class;
|
||
|
||
parent_class = g_type_class_peek_parent (class);
|
||
|
||
object_class->finalize = query_finalize;
|
||
|
||
/* 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, QueryClass *class)
|
||
{
|
||
QueryPrivate *priv;
|
||
|
||
priv = g_new0 (QueryPrivate, 1);
|
||
query->priv = priv;
|
||
|
||
priv->backend = NULL;
|
||
priv->qb = NULL;
|
||
priv->default_zone = NULL;
|
||
priv->listeners = NULL;
|
||
priv->component_listeners = NULL;
|
||
priv->sexp = NULL;
|
||
|
||
priv->timeout_id = 0;
|
||
priv->state = QUERY_WAIT_FOR_BACKEND;
|
||
|
||
priv->cached_timeouts = 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);
|
||
}
|
||
|
||
/* Finalize handler for the live search query */
|
||
static void
|
||
query_finalize (GObject *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) {
|
||
/* If we are waiting for the backend to be opened, we'll be
|
||
* connected to its "opened" signal. If we are in the middle of
|
||
* a query or if we are just waiting for object update
|
||
* notifications, we'll have the "obj_removed" and "obj_updated"
|
||
* connections. Otherwise, we are either in a parse error state
|
||
* or waiting for the query to be populated, and in both cases
|
||
* we have no signal connections.
|
||
*/
|
||
if (priv->state == QUERY_WAIT_FOR_BACKEND
|
||
|| priv->state == QUERY_IN_PROGRESS || priv->state == QUERY_DONE)
|
||
g_signal_handlers_disconnect_matched (G_OBJECT (priv->backend),
|
||
G_SIGNAL_MATCH_DATA,
|
||
0, 0, NULL, NULL, query);
|
||
|
||
g_object_unref (priv->backend);
|
||
priv->backend = NULL;
|
||
}
|
||
|
||
priv->qb = NULL;
|
||
|
||
if (priv->listeners != NULL) {
|
||
CORBA_Environment ev;
|
||
GList *l;
|
||
|
||
CORBA_exception_init (&ev);
|
||
for (l = priv->listeners; l != NULL; l = l->next) {
|
||
bonobo_object_release_unref (l->data, &ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("query_destroy(): Could not unref the listener\n");
|
||
}
|
||
|
||
CORBA_exception_free (&ev);
|
||
|
||
g_list_free (priv->listeners);
|
||
priv->listeners = NULL;
|
||
}
|
||
|
||
if (priv->component_listeners != NULL) {
|
||
g_list_foreach (priv->component_listeners, (GFunc) g_object_unref, NULL);
|
||
g_list_free (priv->component_listeners);
|
||
priv->component_listeners = NULL;
|
||
}
|
||
|
||
if (priv->sexp) {
|
||
g_free (priv->sexp);
|
||
priv->sexp = NULL;
|
||
}
|
||
|
||
if (priv->esexp) {
|
||
e_sexp_unref (priv->esexp);
|
||
priv->esexp = NULL;
|
||
}
|
||
|
||
if (priv->timeout_id) {
|
||
g_source_remove (priv->timeout_id);
|
||
priv->timeout_id = 0;
|
||
}
|
||
|
||
if (priv->cached_timeouts) {
|
||
GList *l;
|
||
|
||
for (l = priv->cached_timeouts; l != NULL; l = l->next)
|
||
g_source_remove (GPOINTER_TO_INT (l->data));
|
||
|
||
g_list_free (priv->cached_timeouts);
|
||
priv->cached_timeouts = NULL;
|
||
}
|
||
|
||
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 (G_OBJECT_CLASS (parent_class)->finalize)
|
||
(* G_OBJECT_CLASS (parent_class)->finalize) (object);
|
||
}
|
||
|
||
|
||
|
||
/* E-Sexp functions */
|
||
|
||
/* (time-now)
|
||
*
|
||
* Returns a time_t of time (NULL).
|
||
*/
|
||
static ESExpResult *
|
||
func_time_now (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
ESExpResult *result;
|
||
|
||
if (argc != 0) {
|
||
e_sexp_fatal_error (esexp, _("time-now expects 0 arguments"));
|
||
return NULL;
|
||
}
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
|
||
result->value.time = time (NULL);
|
||
|
||
return result;
|
||
}
|
||
|
||
/* (make-time ISODATE)
|
||
*
|
||
* ISODATE - string, ISO 8601 date/time representation
|
||
*
|
||
* Constructs a time_t value for the specified date.
|
||
*/
|
||
static ESExpResult *
|
||
func_make_time (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
const char *str;
|
||
time_t t;
|
||
ESExpResult *result;
|
||
|
||
if (argc != 1) {
|
||
e_sexp_fatal_error (esexp, _("make-time expects 1 argument"));
|
||
return NULL;
|
||
}
|
||
|
||
if (argv[0]->type != ESEXP_RES_STRING) {
|
||
e_sexp_fatal_error (esexp, _("make-time expects argument 1 "
|
||
"to be a string"));
|
||
return NULL;
|
||
}
|
||
str = argv[0]->value.string;
|
||
|
||
t = time_from_isodate (str);
|
||
if (t == -1) {
|
||
e_sexp_fatal_error (esexp, _("make-time argument 1 must be an "
|
||
"ISO 8601 date/time string"));
|
||
return NULL;
|
||
}
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
|
||
result->value.time = t;
|
||
|
||
return result;
|
||
}
|
||
|
||
/* (time-add-day TIME N)
|
||
*
|
||
* TIME - time_t, base time
|
||
* N - int, number of days to add
|
||
*
|
||
* Adds the specified number of days to a time value.
|
||
*
|
||
* FIXME: TIMEZONES - need to use a timezone or daylight saving changes will
|
||
* make the result incorrect.
|
||
*/
|
||
static ESExpResult *
|
||
func_time_add_day (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
ESExpResult *result;
|
||
time_t t;
|
||
int n;
|
||
|
||
if (argc != 2) {
|
||
e_sexp_fatal_error (esexp, _("time-add-day expects 2 arguments"));
|
||
return NULL;
|
||
}
|
||
|
||
if (argv[0]->type != ESEXP_RES_TIME) {
|
||
e_sexp_fatal_error (esexp, _("time-add-day expects argument 1 "
|
||
"to be a time_t"));
|
||
return NULL;
|
||
}
|
||
t = argv[0]->value.time;
|
||
|
||
if (argv[1]->type != ESEXP_RES_INT) {
|
||
e_sexp_fatal_error (esexp, _("time-add-day expects argument 2 "
|
||
"to be an integer"));
|
||
return NULL;
|
||
}
|
||
n = argv[1]->value.number;
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
|
||
result->value.time = time_add_day (t, n);
|
||
|
||
return result;
|
||
}
|
||
|
||
/* (time-day-begin TIME)
|
||
*
|
||
* TIME - time_t, base time
|
||
*
|
||
* Returns the start of the day, according to the local time.
|
||
*
|
||
* FIXME: TIMEZONES - this uses the current Unix timezone.
|
||
*/
|
||
static ESExpResult *
|
||
func_time_day_begin (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
time_t t;
|
||
ESExpResult *result;
|
||
|
||
if (argc != 1) {
|
||
e_sexp_fatal_error (esexp, _("time-day-begin expects 1 argument"));
|
||
return NULL;
|
||
}
|
||
|
||
if (argv[0]->type != ESEXP_RES_TIME) {
|
||
e_sexp_fatal_error (esexp, _("time-day-begin expects argument 1 "
|
||
"to be a time_t"));
|
||
return NULL;
|
||
}
|
||
t = argv[0]->value.time;
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
|
||
result->value.time = time_day_begin (t);
|
||
|
||
return result;
|
||
}
|
||
|
||
/* (time-day-end TIME)
|
||
*
|
||
* TIME - time_t, base time
|
||
*
|
||
* Returns the end of the day, according to the local time.
|
||
*
|
||
* FIXME: TIMEZONES - this uses the current Unix timezone.
|
||
*/
|
||
static ESExpResult *
|
||
func_time_day_end (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
time_t t;
|
||
ESExpResult *result;
|
||
|
||
if (argc != 1) {
|
||
e_sexp_fatal_error (esexp, _("time-day-end expects 1 argument"));
|
||
return NULL;
|
||
}
|
||
|
||
if (argv[0]->type != ESEXP_RES_TIME) {
|
||
e_sexp_fatal_error (esexp, _("time-day-end expects argument 1 "
|
||
"to be a time_t"));
|
||
return NULL;
|
||
}
|
||
t = argv[0]->value.time;
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
|
||
result->value.time = time_day_end (t);
|
||
|
||
return result;
|
||
}
|
||
|
||
/* (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;
|
||
}
|
||
|
||
/* Call the backend function to get a timezone from a TZID. */
|
||
static icaltimezone*
|
||
resolve_tzid (const char *tzid, gpointer data)
|
||
{
|
||
Query *query = data;
|
||
|
||
if (!tzid || !tzid[0])
|
||
return NULL;
|
||
else
|
||
return cal_backend_get_timezone (query->priv->backend, tzid);
|
||
}
|
||
|
||
|
||
/* (occur-in-time-range? START END)
|
||
*
|
||
* START - time_t, start of the time range
|
||
* END - time_t, end of the time range
|
||
*
|
||
* Returns a boolean indicating whether the component has any occurrences in the
|
||
* specified time range.
|
||
*/
|
||
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_TIME) {
|
||
e_sexp_fatal_error (esexp, _("occur-in-time-range? expects argument 1 "
|
||
"to be a time_t"));
|
||
return NULL;
|
||
}
|
||
start = argv[0]->value.time;
|
||
|
||
if (argv[1]->type != ESEXP_RES_TIME) {
|
||
e_sexp_fatal_error (esexp, _("occur-in-time-range? expects argument 2 "
|
||
"to be a time_t"));
|
||
return NULL;
|
||
}
|
||
end = argv[1]->value.time;
|
||
|
||
/* See if there is at least one instance in that range */
|
||
|
||
occurs = FALSE;
|
||
|
||
cal_recur_generate_instances (comp, start, end,
|
||
instance_occur_cb, &occurs,
|
||
resolve_tzid, query, priv->default_zone);
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
|
||
result->value.bool = occurs;
|
||
|
||
return result;
|
||
}
|
||
|
||
/* Returns whether a list of CalComponentText items matches the specified string */
|
||
static gboolean
|
||
matches_text_list (GSList *text_list, const char *str)
|
||
{
|
||
GSList *l;
|
||
gboolean matches;
|
||
|
||
matches = FALSE;
|
||
|
||
for (l = text_list; l; l = l->next) {
|
||
CalComponentText *text;
|
||
|
||
text = l->data;
|
||
g_assert (text->value != NULL);
|
||
|
||
if (e_utf8_strstrcasedecomp (text->value, str) != NULL) {
|
||
matches = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return matches;
|
||
}
|
||
|
||
/* Returns whether the comments in a component matches the specified string */
|
||
static gboolean
|
||
matches_comment (CalComponent *comp, const char *str)
|
||
{
|
||
GSList *list;
|
||
gboolean matches;
|
||
|
||
cal_component_get_comment_list (comp, &list);
|
||
matches = matches_text_list (list, str);
|
||
cal_component_free_text_list (list);
|
||
|
||
return matches;
|
||
}
|
||
|
||
/* Returns whether the description in a component matches the specified string */
|
||
static gboolean
|
||
matches_description (CalComponent *comp, const char *str)
|
||
{
|
||
GSList *list;
|
||
gboolean matches;
|
||
|
||
cal_component_get_description_list (comp, &list);
|
||
matches = matches_text_list (list, str);
|
||
cal_component_free_text_list (list);
|
||
|
||
return matches;
|
||
}
|
||
|
||
/* Returns whether the summary in a component matches the specified string */
|
||
static gboolean
|
||
matches_summary (CalComponent *comp, const char *str)
|
||
{
|
||
CalComponentText text;
|
||
|
||
cal_component_get_summary (comp, &text);
|
||
|
||
if (!text.value)
|
||
return FALSE;
|
||
|
||
return e_utf8_strstrcasedecomp (text.value, str) != NULL;
|
||
}
|
||
|
||
/* Returns whether any text field in a component matches the specified string */
|
||
static gboolean
|
||
matches_any (CalComponent *comp, const char *str)
|
||
{
|
||
/* As an optimization, and to make life easier for the individual
|
||
* predicate functions, see if we are looking for the empty string right
|
||
* away.
|
||
*/
|
||
if (strlen (str) == 0)
|
||
return TRUE;
|
||
|
||
return (matches_comment (comp, str)
|
||
|| matches_description (comp, str)
|
||
|| matches_summary (comp, str));
|
||
}
|
||
|
||
/* (contains? FIELD STR)
|
||
*
|
||
* FIELD - string, name of field to match (any, comment, description, summary)
|
||
* STR - string, match string
|
||
*
|
||
* Returns a boolean indicating whether the specified field contains the
|
||
* specified string.
|
||
*/
|
||
static ESExpResult *
|
||
func_contains (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
Query *query;
|
||
QueryPrivate *priv;
|
||
CalComponent *comp;
|
||
const char *field;
|
||
const char *str;
|
||
gboolean matches;
|
||
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, _("contains? expects 2 arguments"));
|
||
return NULL;
|
||
}
|
||
|
||
if (argv[0]->type != ESEXP_RES_STRING) {
|
||
e_sexp_fatal_error (esexp, _("contains? expects argument 1 "
|
||
"to be a string"));
|
||
return NULL;
|
||
}
|
||
field = argv[0]->value.string;
|
||
|
||
if (argv[1]->type != ESEXP_RES_STRING) {
|
||
e_sexp_fatal_error (esexp, _("contains? expects argument 2 "
|
||
"to be a string"));
|
||
return NULL;
|
||
}
|
||
str = argv[1]->value.string;
|
||
|
||
/* See if it matches */
|
||
|
||
if (strcmp (field, "any") == 0)
|
||
matches = matches_any (comp, str);
|
||
else if (strcmp (field, "comment") == 0)
|
||
matches = matches_comment (comp, str);
|
||
else if (strcmp (field, "description") == 0)
|
||
matches = matches_description (comp, str);
|
||
else if (strcmp (field, "summary") == 0)
|
||
matches = matches_summary (comp, str);
|
||
else {
|
||
e_sexp_fatal_error (esexp, _("contains? expects argument 1 to "
|
||
"be one of \"any\", \"summary\", \"description\""));
|
||
return NULL;
|
||
}
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
|
||
result->value.bool = matches;
|
||
|
||
return result;
|
||
}
|
||
|
||
/* (has-categories? STR+)
|
||
* (has-categories? #f)
|
||
*
|
||
* STR - At least one string specifying a category
|
||
* Or you can specify a single #f (boolean false) value for components
|
||
* that have no categories assigned to them ("unfiled").
|
||
*
|
||
* Returns a boolean indicating whether the component has all the specified
|
||
* categories.
|
||
*/
|
||
static ESExpResult *
|
||
func_has_categories (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
Query *query;
|
||
QueryPrivate *priv;
|
||
CalComponent *comp;
|
||
gboolean unfiled;
|
||
int i;
|
||
GSList *categories;
|
||
gboolean matches;
|
||
ESExpResult *result;
|
||
|
||
query = QUERY (data);
|
||
priv = query->priv;
|
||
|
||
g_assert (priv->next_comp != NULL);
|
||
comp = priv->next_comp;
|
||
|
||
/* Check argument types */
|
||
|
||
if (argc < 1) {
|
||
e_sexp_fatal_error (esexp, _("has-categories? expects at least 1 argument"));
|
||
return NULL;
|
||
}
|
||
|
||
if (argc == 1 && argv[0]->type == ESEXP_RES_BOOL)
|
||
unfiled = TRUE;
|
||
else
|
||
unfiled = FALSE;
|
||
|
||
if (!unfiled)
|
||
for (i = 0; i < argc; i++)
|
||
if (argv[i]->type != ESEXP_RES_STRING) {
|
||
e_sexp_fatal_error (esexp, _("has-categories? expects all arguments "
|
||
"to be strings or one and only one "
|
||
"argument to be a boolean false (#f)"));
|
||
return NULL;
|
||
}
|
||
|
||
/* Search categories. First, if there are no categories we return
|
||
* whether unfiled components are supposed to match.
|
||
*/
|
||
|
||
cal_component_get_categories_list (comp, &categories);
|
||
if (!categories) {
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
|
||
result->value.bool = unfiled;
|
||
|
||
return result;
|
||
}
|
||
|
||
/* Otherwise, we *do* have categories but unfiled components were
|
||
* requested, so this component does not match.
|
||
*/
|
||
if (unfiled) {
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
|
||
result->value.bool = FALSE;
|
||
|
||
return result;
|
||
}
|
||
|
||
matches = TRUE;
|
||
|
||
for (i = 0; i < argc; i++) {
|
||
const char *sought;
|
||
GSList *l;
|
||
gboolean has_category;
|
||
|
||
sought = argv[i]->value.string;
|
||
|
||
has_category = FALSE;
|
||
|
||
for (l = categories; l; l = l->next) {
|
||
const char *category;
|
||
|
||
category = l->data;
|
||
|
||
if (strcmp (category, sought) == 0) {
|
||
has_category = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!has_category) {
|
||
matches = FALSE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
cal_component_free_categories_list (categories);
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
|
||
result->value.bool = matches;
|
||
|
||
return result;
|
||
}
|
||
|
||
/* (is-completed?)
|
||
*
|
||
* Returns a boolean indicating whether the component is completed (i.e. has
|
||
* a COMPLETED property. This is really only useful for TODO components.
|
||
*/
|
||
static ESExpResult *
|
||
func_is_completed (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
Query *query;
|
||
QueryPrivate *priv;
|
||
CalComponent *comp;
|
||
ESExpResult *result;
|
||
struct icaltimetype *t;
|
||
gboolean complete = FALSE;
|
||
|
||
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, _("is-completed? expects 0 arguments"));
|
||
return NULL;
|
||
}
|
||
|
||
cal_component_get_completed (comp, &t);
|
||
if (t) {
|
||
complete = TRUE;
|
||
cal_component_free_icaltimetype (t);
|
||
}
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
|
||
result->value.bool = complete;
|
||
|
||
return result;
|
||
}
|
||
|
||
/* (completed-before? TIME)
|
||
*
|
||
* TIME - time_t
|
||
*
|
||
* Returns a boolean indicating whether the component was completed on or
|
||
* before the given time (i.e. it checks the COMPLETED property).
|
||
* This is really only useful for TODO components.
|
||
*/
|
||
static ESExpResult *
|
||
func_completed_before (ESExp *esexp, int argc, ESExpResult **argv, void *data)
|
||
{
|
||
Query *query;
|
||
QueryPrivate *priv;
|
||
CalComponent *comp;
|
||
ESExpResult *result;
|
||
struct icaltimetype *tt;
|
||
icaltimezone *zone;
|
||
gboolean retval = FALSE;
|
||
time_t before_time, completed_time;
|
||
|
||
query = QUERY (data);
|
||
priv = query->priv;
|
||
|
||
g_assert (priv->next_comp != NULL);
|
||
comp = priv->next_comp;
|
||
|
||
/* Check argument types */
|
||
|
||
if (argc != 1) {
|
||
e_sexp_fatal_error (esexp, _("completed-before? expects 1 argument"));
|
||
return NULL;
|
||
}
|
||
|
||
if (argv[0]->type != ESEXP_RES_TIME) {
|
||
e_sexp_fatal_error (esexp, _("completed-before? expects argument 1 "
|
||
"to be a time_t"));
|
||
return NULL;
|
||
}
|
||
before_time = argv[0]->value.time;
|
||
|
||
cal_component_get_completed (comp, &tt);
|
||
if (tt) {
|
||
/* COMPLETED must be in UTC. */
|
||
zone = icaltimezone_get_utc_timezone ();
|
||
completed_time = icaltime_as_timet_with_zone (*tt, zone);
|
||
|
||
#if 0
|
||
g_print ("Query Time : %s", ctime (&before_time));
|
||
g_print ("Completed Time: %s", ctime (&completed_time));
|
||
#endif
|
||
|
||
/* We want to return TRUE if before_time is after
|
||
completed_time. */
|
||
if (difftime (before_time, completed_time) > 0) {
|
||
#if 0
|
||
g_print (" Returning TRUE\n");
|
||
#endif
|
||
retval = TRUE;
|
||
}
|
||
|
||
cal_component_free_icaltimetype (tt);
|
||
}
|
||
|
||
result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
|
||
result->value.bool = retval;
|
||
|
||
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;
|
||
GList *l;
|
||
|
||
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);
|
||
for (l = priv->listeners; l != NULL; l = l->next) {
|
||
GNOME_Evolution_Calendar_CalObjUIDSeq *corba_uids;
|
||
|
||
corba_uids = GNOME_Evolution_Calendar_CalObjUIDSeq__alloc ();
|
||
CORBA_sequence_set_release (corba_uids, TRUE);
|
||
corba_uids->_buffer = CORBA_sequence_GNOME_Evolution_Calendar_CalObjUID_allocbuf (1);
|
||
corba_uids->_length = 1;
|
||
corba_uids->_buffer[0] = CORBA_string_dup (uid);
|
||
|
||
GNOME_Evolution_Calendar_QueryListener_notifyObjUpdated (
|
||
l->data,
|
||
corba_uids,
|
||
query_in_progress,
|
||
n_scanned,
|
||
total,
|
||
&ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("add_component(): Could not notify the listener of an "
|
||
"updated component");
|
||
|
||
CORBA_free (corba_uids);
|
||
}
|
||
|
||
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;
|
||
GList *l;
|
||
|
||
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);
|
||
for (l = priv->listeners; l != NULL; l = l->next) {
|
||
GNOME_Evolution_Calendar_QueryListener_notifyObjRemoved (
|
||
l->data,
|
||
(char *) uid,
|
||
&ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
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[] = {
|
||
/* Time-related functions */
|
||
{ "time-now", func_time_now },
|
||
{ "make-time", func_make_time },
|
||
{ "time-add-day", func_time_add_day },
|
||
{ "time-day-begin", func_time_day_begin },
|
||
{ "time-day-end", func_time_day_end },
|
||
|
||
/* Component-related functions */
|
||
{ "get-vtype", func_get_vtype },
|
||
{ "occur-in-time-range?", func_occur_in_time_range },
|
||
{ "contains?", func_contains },
|
||
{ "has-categories?", func_has_categories },
|
||
{ "is-completed?", func_is_completed },
|
||
{ "completed-before?", func_completed_before }
|
||
};
|
||
|
||
/* 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;
|
||
}
|
||
|
||
/* Creates the ESexp and parses the esexp. If a parse error occurs, it sets the
|
||
* query state to QUERY_PARSE_ERROR and returns FALSE.
|
||
*/
|
||
static gboolean
|
||
parse_sexp (Query *query)
|
||
{
|
||
QueryPrivate *priv;
|
||
|
||
priv = query->priv;
|
||
|
||
/* Compile the query string */
|
||
|
||
priv->esexp = create_sexp (query);
|
||
|
||
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;
|
||
CORBA_Environment ev;
|
||
GList *l;
|
||
|
||
priv->state = QUERY_PARSE_ERROR;
|
||
|
||
/* Report the error to the listeners */
|
||
|
||
error_str = e_sexp_error (priv->esexp);
|
||
g_assert (error_str != NULL);
|
||
|
||
CORBA_exception_init (&ev);
|
||
for (l = priv->listeners; l != NULL; l = l->next) {
|
||
GNOME_Evolution_Calendar_QueryListener_notifyQueryDone (
|
||
l->data,
|
||
GNOME_Evolution_Calendar_QueryListener_PARSE_ERROR,
|
||
error_str,
|
||
&ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("parse_sexp(): Could not notify the listener of "
|
||
"a parse error");
|
||
}
|
||
|
||
CORBA_exception_free (&ev);
|
||
|
||
e_sexp_unref (priv->esexp);
|
||
priv->esexp = NULL;
|
||
|
||
/* remove the query from the list of cached queries */
|
||
cached_queries = g_list_remove (cached_queries, query);
|
||
bonobo_object_unref (BONOBO_OBJECT (query));
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* 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;
|
||
CalComponent *comp;
|
||
ESExpResult *result;
|
||
|
||
priv = query->priv;
|
||
|
||
g_assert (priv->state == QUERY_IN_PROGRESS || priv->state == QUERY_DONE);
|
||
g_assert (priv->esexp != NULL);
|
||
|
||
comp = query_backend_get_object_component (priv->qb, uid);
|
||
if (!comp)
|
||
return;
|
||
|
||
/* Eval the sexp */
|
||
|
||
g_assert (priv->next_comp == NULL);
|
||
|
||
priv->next_comp = comp;
|
||
result = e_sexp_eval (priv->esexp);
|
||
priv->next_comp = NULL;
|
||
|
||
if (!result) {
|
||
const char *error_str;
|
||
CORBA_Environment ev;
|
||
GList *l;
|
||
|
||
error_str = e_sexp_error (priv->esexp);
|
||
g_assert (error_str != NULL);
|
||
|
||
CORBA_exception_init (&ev);
|
||
for (l = priv->listeners; l != NULL; l = l->next) {
|
||
GNOME_Evolution_Calendar_QueryListener_notifyEvalError (
|
||
l->data,
|
||
error_str,
|
||
&ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("match_component(): Could not notify the listener of "
|
||
"an evaluation error");
|
||
}
|
||
|
||
CORBA_exception_free (&ev);
|
||
return;
|
||
} else if (result->type != ESEXP_RES_BOOL) {
|
||
CORBA_Environment ev;
|
||
GList *l;
|
||
|
||
CORBA_exception_init (&ev);
|
||
for (l = priv->listeners; l != NULL; l = l->next) {
|
||
GNOME_Evolution_Calendar_QueryListener_notifyEvalError (
|
||
l->data,
|
||
_("Evaluation of the search expression did not yield a boolean value"),
|
||
&ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("match_component(): 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);
|
||
}
|
||
|
||
e_sexp_result_free (priv->esexp, result);
|
||
}
|
||
|
||
/* Processes all components that are queued in the list */
|
||
static gboolean
|
||
process_components_cb (gpointer data)
|
||
{
|
||
Query *query;
|
||
QueryPrivate *priv;
|
||
char *uid;
|
||
GList *l;
|
||
CORBA_Environment ev;
|
||
|
||
query = QUERY (data);
|
||
priv = query->priv;
|
||
|
||
g_source_remove (priv->timeout_id);
|
||
priv->timeout_id = 0;
|
||
|
||
bonobo_object_ref (BONOBO_OBJECT (query));
|
||
|
||
while (priv->pending_uids) {
|
||
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);
|
||
|
||
/* run the main loop, for not blocking */
|
||
if (gtk_events_pending ())
|
||
gtk_main_iteration ();
|
||
}
|
||
|
||
bonobo_object_unref (BONOBO_OBJECT (query));
|
||
if (!priv || !priv->listeners)
|
||
return FALSE;
|
||
|
||
/* notify listeners that the query ended */
|
||
priv->state = QUERY_DONE;
|
||
|
||
CORBA_exception_init (&ev);
|
||
for (l = priv->listeners; l != NULL; l = l->next) {
|
||
GNOME_Evolution_Calendar_QueryListener_notifyQueryDone (
|
||
l->data,
|
||
GNOME_Evolution_Calendar_QueryListener_SUCCESS,
|
||
"",
|
||
&ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("start_query(): Could not notify the listener of "
|
||
"a finished query");
|
||
}
|
||
|
||
CORBA_exception_free (&ev);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Callback used when a component changes in the backend */
|
||
static void
|
||
backend_obj_updated_cb (CalBackend *backend, const char *uid, gpointer data)
|
||
{
|
||
Query *query;
|
||
QueryPrivate *priv;
|
||
|
||
query = QUERY (data);
|
||
priv = query->priv;
|
||
|
||
g_assert (priv->state == QUERY_IN_PROGRESS || priv->state == QUERY_DONE);
|
||
|
||
bonobo_object_ref (BONOBO_OBJECT (query));
|
||
|
||
match_component (query, uid, FALSE, 0, 0);
|
||
remove_from_pending (query, uid);
|
||
|
||
bonobo_object_unref (BONOBO_OBJECT (query));
|
||
}
|
||
|
||
/* 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;
|
||
|
||
g_assert (priv->state == QUERY_IN_PROGRESS || priv->state == QUERY_DONE);
|
||
|
||
bonobo_object_ref (BONOBO_OBJECT (query));
|
||
|
||
remove_component (query, uid);
|
||
remove_from_pending (query, uid);
|
||
|
||
bonobo_object_unref (BONOBO_OBJECT (query));
|
||
}
|
||
|
||
/* Actually starts the query */
|
||
static void
|
||
start_query (Query *query)
|
||
{
|
||
QueryPrivate *priv;
|
||
|
||
priv = query->priv;
|
||
|
||
if (!parse_sexp (query))
|
||
return;
|
||
|
||
/* Populate the query with UIDs so that we can process them asynchronously */
|
||
|
||
priv->state = QUERY_IN_PROGRESS;
|
||
priv->pending_uids = query_backend_get_uids (priv->qb, CALOBJ_TYPE_ANY);
|
||
priv->pending_total = g_list_length (priv->pending_uids);
|
||
priv->n_pending = priv->pending_total;
|
||
|
||
g_signal_connect (G_OBJECT (priv->backend), "obj_updated",
|
||
G_CALLBACK (backend_obj_updated_cb),
|
||
query);
|
||
g_signal_connect (G_OBJECT (priv->backend), "obj_removed",
|
||
G_CALLBACK (backend_obj_removed_cb),
|
||
query);
|
||
|
||
priv->timeout_id = g_timeout_add (100, (GSourceFunc) process_components_cb, query);
|
||
}
|
||
|
||
/* Idle handler for starting a query */
|
||
static gboolean
|
||
start_query_cb (gpointer data)
|
||
{
|
||
Query *query;
|
||
QueryPrivate *priv;
|
||
|
||
query = QUERY (data);
|
||
priv = query->priv;
|
||
|
||
g_source_remove (priv->timeout_id);
|
||
priv->timeout_id = 0;
|
||
|
||
if (priv->state == QUERY_START_PENDING) {
|
||
priv->state = QUERY_IN_PROGRESS;
|
||
start_query (query);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
listener_died_cb (EComponentListener *cl, gpointer data)
|
||
{
|
||
QueryPrivate *priv;
|
||
Query *query = QUERY (data);
|
||
GNOME_Evolution_Calendar_QueryListener ql;
|
||
CORBA_Environment ev;
|
||
|
||
priv = query->priv;
|
||
|
||
ql = e_component_listener_get_component (cl);
|
||
priv->listeners = g_list_remove (priv->listeners, ql);
|
||
|
||
priv->component_listeners = g_list_remove (priv->component_listeners, cl);
|
||
g_object_unref (cl);
|
||
|
||
CORBA_exception_init (&ev);
|
||
bonobo_object_release_unref (ql, &ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("query_destroy(): Could not unref the listener\n");
|
||
|
||
CORBA_exception_free (&ev);
|
||
}
|
||
|
||
static void
|
||
add_uid_cb (gpointer key, gpointer value, gpointer data)
|
||
{
|
||
char *uid = (char *) key;
|
||
GList **uidlist = (GList **) data;
|
||
|
||
*uidlist = g_list_append (*uidlist, uid);
|
||
}
|
||
|
||
/* Idle handler for starting a cached query */
|
||
static gboolean
|
||
start_cached_query_cb (gpointer data)
|
||
{
|
||
CORBA_Environment ev;
|
||
QueryPrivate *priv;
|
||
EComponentListener *cl;
|
||
StartCachedQueryInfo *info = (StartCachedQueryInfo *) data;
|
||
|
||
priv = info->query->priv;
|
||
|
||
g_source_remove (info->tid);
|
||
priv->cached_timeouts = g_list_remove (priv->cached_timeouts,
|
||
GINT_TO_POINTER (info->tid));
|
||
|
||
/* if the query hasn't started yet, we add the listener */
|
||
if (priv->state == QUERY_START_PENDING ||
|
||
priv->state == QUERY_WAIT_FOR_BACKEND) {
|
||
priv->listeners = g_list_append (priv->listeners, info->ql);
|
||
|
||
cl = e_component_listener_new (info->ql, 0);
|
||
priv->component_listeners = g_list_append (priv->component_listeners, cl);
|
||
g_signal_connect (G_OBJECT (cl), "component_died",
|
||
G_CALLBACK (listener_died_cb), info->query);
|
||
} else if (priv->state == QUERY_IN_PROGRESS) {
|
||
/* if it's in progress, we re-add the timeout */
|
||
info->tid = g_timeout_add (100, (GSourceFunc) start_cached_query_cb, info);
|
||
priv->cached_timeouts = g_list_append (priv->cached_timeouts,
|
||
GINT_TO_POINTER (info->tid));
|
||
|
||
return FALSE;
|
||
} else if (priv->state == QUERY_PARSE_ERROR) {
|
||
/* notify listener of error */
|
||
CORBA_exception_init (&ev);
|
||
GNOME_Evolution_Calendar_QueryListener_notifyQueryDone (
|
||
info->ql,
|
||
GNOME_Evolution_Calendar_QueryListener_PARSE_ERROR,
|
||
_("Parse error"),
|
||
&ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("start_cached_query_cb(): Could not notify the listener of "
|
||
"a parse error");
|
||
|
||
CORBA_exception_free (&ev);
|
||
|
||
/* remove all traces of this query */
|
||
cached_queries = g_list_remove (cached_queries, info->query);
|
||
bonobo_object_unref (BONOBO_OBJECT (info->query));
|
||
} else if (priv->state == QUERY_DONE) {
|
||
int len;
|
||
GList *uid_list = NULL, *l;
|
||
|
||
CORBA_exception_init (&ev);
|
||
|
||
/* if the query is done, then we just notify the listener of all the
|
||
* UIDS we've got so far, all at once */
|
||
g_hash_table_foreach (priv->uids, (GHFunc) add_uid_cb, &uid_list);
|
||
|
||
len = g_list_length (uid_list);
|
||
if (len > 0) {
|
||
int n;
|
||
GNOME_Evolution_Calendar_CalObjUIDSeq *corba_uids;
|
||
|
||
corba_uids = GNOME_Evolution_Calendar_CalObjUIDSeq__alloc ();
|
||
corba_uids->_length = len;
|
||
corba_uids->_maximum = len;
|
||
corba_uids->_buffer = CORBA_sequence_GNOME_Evolution_Calendar_CalObjUID_allocbuf (len);
|
||
CORBA_sequence_set_release (corba_uids, TRUE);
|
||
|
||
for (l = uid_list, n = 0; l != NULL; l = l->next, n++)
|
||
corba_uids->_buffer[n] = CORBA_string_dup ((CORBA_char *) l->data);
|
||
|
||
GNOME_Evolution_Calendar_QueryListener_notifyObjUpdated (
|
||
info->ql,
|
||
corba_uids,
|
||
TRUE,
|
||
len,
|
||
len, &ev);
|
||
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("start_cached_query_cb(): Could not notify the listener of all "
|
||
"cached components");
|
||
|
||
CORBA_free (corba_uids);
|
||
g_list_free (uid_list);
|
||
}
|
||
|
||
/* setup private data and notify listener that the query ended */
|
||
priv->listeners = g_list_append (priv->listeners, info->ql);
|
||
|
||
cl = e_component_listener_new (info->ql, 0);
|
||
priv->component_listeners = g_list_append (priv->component_listeners, cl);
|
||
g_signal_connect (G_OBJECT (cl), "component_died",
|
||
G_CALLBACK (listener_died_cb), info->query);
|
||
|
||
GNOME_Evolution_Calendar_QueryListener_notifyQueryDone (
|
||
info->ql,
|
||
GNOME_Evolution_Calendar_QueryListener_SUCCESS,
|
||
"",
|
||
&ev);
|
||
if (BONOBO_EX (&ev))
|
||
g_message ("start_cached_query_cb(): Could not notify the listener of "
|
||
"a finished query");
|
||
|
||
CORBA_exception_free (&ev);
|
||
}
|
||
|
||
g_free (info);
|
||
|
||
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;
|
||
|
||
g_assert (priv->state == QUERY_WAIT_FOR_BACKEND);
|
||
|
||
g_signal_handlers_disconnect_matched (G_OBJECT (priv->backend),
|
||
G_SIGNAL_MATCH_DATA,
|
||
0, 0, NULL, NULL, query);
|
||
priv->state = QUERY_START_PENDING;
|
||
|
||
if (status == CAL_BACKEND_OPEN_SUCCESS) {
|
||
g_assert (cal_backend_is_loaded (backend));
|
||
|
||
priv->timeout_id = g_timeout_add (100, (GSourceFunc) start_query_cb, query);
|
||
}
|
||
}
|
||
|
||
/* Callback used when the backend for a cached query is destroyed */
|
||
static void
|
||
backend_destroyed_cb (GObject *object, gpointer data)
|
||
{
|
||
Query *query;
|
||
|
||
query = QUERY (data);
|
||
|
||
cached_queries = g_list_remove (cached_queries, query);
|
||
bonobo_object_unref (BONOBO_OBJECT (query));
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
EComponentListener *cl;
|
||
|
||
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->listeners = g_list_append (NULL, CORBA_Object_duplicate (ql, &ev));
|
||
if (BONOBO_EX (&ev)) {
|
||
g_message ("query_construct(): Could not duplicate the listener");
|
||
priv->listeners = NULL;
|
||
CORBA_exception_free (&ev);
|
||
return NULL;
|
||
}
|
||
CORBA_exception_free (&ev);
|
||
|
||
cl = e_component_listener_new (ql, 0);
|
||
priv->component_listeners = g_list_append (priv->component_listeners, cl);
|
||
g_signal_connect (G_OBJECT (cl), "component_died",
|
||
G_CALLBACK (listener_died_cb), query);
|
||
|
||
priv->backend = backend;
|
||
g_object_ref (priv->backend);
|
||
|
||
priv->qb = query_backend_new (query, backend);
|
||
priv->default_zone = cal_backend_get_default_timezone (backend);
|
||
|
||
priv->sexp = g_strdup (sexp);
|
||
|
||
/* Queue the query to be started asynchronously */
|
||
|
||
if (cal_backend_is_loaded (priv->backend)) {
|
||
priv->state = QUERY_START_PENDING;
|
||
|
||
priv->timeout_id = g_timeout_add (100, (GSourceFunc) start_query_cb, query);
|
||
} else
|
||
g_signal_connect (G_OBJECT (priv->backend), "opened",
|
||
G_CALLBACK (backend_opened_cb),
|
||
query);
|
||
|
||
return query;
|
||
}
|
||
|
||
/**
|
||
* query_new:
|
||
* @backend: Calendar backend that the query object will monitor.
|
||
* @ql: Listener for query results.
|
||
* @sexp: Sexp that defines the query.
|
||
*
|
||
* Creates a new query engine object that monitors a calendar backend.
|
||
*
|
||
* Return value: A newly-created query object, or NULL on failure.
|
||
**/
|
||
Query *
|
||
query_new (CalBackend *backend,
|
||
GNOME_Evolution_Calendar_QueryListener ql,
|
||
const char *sexp)
|
||
{
|
||
Query *query;
|
||
GList *l;
|
||
|
||
/* first, see if we've got this query in our cache */
|
||
for (l = cached_queries; l != NULL; l = l->next) {
|
||
query = QUERY (l->data);
|
||
|
||
g_assert (query != NULL);
|
||
|
||
if (query->priv->backend == backend &&
|
||
!strcmp (query->priv->sexp, sexp)) {
|
||
StartCachedQueryInfo *info;
|
||
CORBA_Environment ev;
|
||
|
||
info = g_new0 (StartCachedQueryInfo, 1);
|
||
info->query = query;
|
||
|
||
CORBA_exception_init (&ev);
|
||
info->ql = CORBA_Object_duplicate (ql, &ev);
|
||
if (BONOBO_EX (&ev)) {
|
||
g_message ("query_new(): Could not duplicate listener object");
|
||
g_free (info);
|
||
|
||
return NULL;
|
||
}
|
||
CORBA_exception_free (&ev);
|
||
|
||
info->tid = g_timeout_add (100, (GSourceFunc) start_cached_query_cb, info);
|
||
query->priv->cached_timeouts = g_list_append (query->priv->cached_timeouts,
|
||
GINT_TO_POINTER (info->tid));
|
||
|
||
bonobo_object_ref (BONOBO_OBJECT (query));
|
||
return query;
|
||
}
|
||
}
|
||
|
||
/* not found, so create a new one */
|
||
query = QUERY (g_object_new (QUERY_TYPE, NULL));
|
||
if (!query_construct (query, backend, ql, sexp)) {
|
||
bonobo_object_unref (BONOBO_OBJECT (query));
|
||
return NULL;
|
||
}
|
||
|
||
/* add the new query to our cache */
|
||
g_signal_connect (G_OBJECT (query->priv->backend), "destroy",
|
||
G_CALLBACK (backend_destroyed_cb), query);
|
||
|
||
bonobo_object_ref (BONOBO_OBJECT (query));
|
||
cached_queries = g_list_append (cached_queries, query);
|
||
|
||
return query;
|
||
}
|