Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / calendar / libedata-cal / e-cal-backend-sexp.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  * cal-backend-card-sexp.c
4  * Copyright 1999, 2000, 2001, Ximian, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License, version 2, as published by the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <string.h>
26 #include <glib/gi18n-lib.h>
27 #include "libedataserver/e-data-server-util.h"
28 #include <libecal/e-cal-time-util.h>
29
30 #include "e-cal-backend-sexp.h"
31
32 static GObjectClass *parent_class;
33
34 typedef struct _SearchContext SearchContext;
35
36 struct _ECalBackendSExpPrivate {
37         ESExp *search_sexp;
38         char *text;
39         SearchContext *search_context;
40 };
41
42 struct _SearchContext {
43         ECalComponent *comp;
44         ECalBackend *backend;
45         gboolean occurs;
46 };
47
48 static ESExpResult *func_is_completed (ESExp *esexp, int argc, ESExpResult **argv, void *data);
49
50 /**
51  * e_cal_backend_sexp_func_time_now:
52  * @esexp: An #ESExp object.
53  * @argc: Number of arguments.
54  * @argv: The arguments.
55  * @data: Closure data.
56  *
57  * Processes the (time-now) sexp expression.
58  *
59  * Return value: The result of the function.
60  */
61 ESExpResult *
62 e_cal_backend_sexp_func_time_now (ESExp *esexp, int argc, ESExpResult **argv, void *data)
63 {
64         ESExpResult *result;
65
66         if (argc != 0) {
67                 e_sexp_fatal_error (esexp, _("\"%s\" expects no arguments"),
68                                     "time-now");
69                 return NULL;
70         }
71
72         result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
73         result->value.time = time (NULL);
74
75         return result;
76 }
77
78 /**
79  * e_cal_backend_sexp_func_make_time:
80  * @esexp: An #ESExp object.
81  * @argc: Number of arguments.
82  * @argv: The arguments.
83  * @data: Closure data.
84  *
85  * (make-time ISODATE)
86  * ISODATE - string, ISO 8601 date/time representation
87  *
88  * Constructs a time_t value for the specified date.
89  *
90  * Return value: The result of the function.
91  */
92 ESExpResult *
93 e_cal_backend_sexp_func_make_time (ESExp *esexp, int argc, ESExpResult **argv, void *data)
94 {
95         const char *str;
96         time_t t;
97         ESExpResult *result;
98
99         if (argc != 1) {
100                 e_sexp_fatal_error (esexp, _("\"%s\" expects one argument"),
101                                     "make-time");
102                 return NULL;
103         }
104
105         if (argv[0]->type != ESEXP_RES_STRING) {
106                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
107                                              "argument to be a string"),
108                                     "make-time");
109                 return NULL;
110         }
111         str = argv[0]->value.string;
112         if (!str || !*str) {
113                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
114                                              "argument to be a string"),
115                                     "make-time");
116                 return NULL;
117         }
118
119         t = time_from_isodate (str);
120         if (t == -1) {
121                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
122                                              "argument to be an ISO 8601 "
123                                              "date/time string"),
124                                     "make-time");
125                 return NULL;
126         }
127
128         result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
129         result->value.time = t;
130
131         return result;
132 }
133
134 /**
135  * e_cal_backend_sexp_func_time_add_day:
136  * @esexp: An #ESExp object.
137  * @argc: Number of arguments.
138  * @argv: The arguments.
139  * @data: Closure data.
140  *
141  * (time-add-day TIME N)
142  * TIME - time_t, base time
143  * N - int, number of days to add
144  *
145  * Adds the specified number of days to a time value.
146  *
147  * FIXME: TIMEZONES - need to use a timezone or daylight saving changes will
148  * make the result incorrect.
149  *
150  * Return value: The result of the function.
151  */
152 ESExpResult *
153 e_cal_backend_sexp_func_time_add_day (ESExp *esexp, int argc, ESExpResult **argv, void *data)
154 {
155         ESExpResult *result;
156         time_t t;
157         int n;
158
159         if (argc != 2) {
160                 e_sexp_fatal_error (esexp, _("\"%s\" expects two arguments"),
161                                     "time-add-day");
162                 return NULL;
163         }
164
165         if (argv[0]->type != ESEXP_RES_TIME) {
166                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
167                                              "argument to be a time_t"),
168                                     "time-add-day");
169                 return NULL;
170         }
171         t = argv[0]->value.time;
172
173         if (argv[1]->type != ESEXP_RES_INT) {
174                 e_sexp_fatal_error (esexp, _("\"%s\" expects the second "
175                                              "argument to be an integer"),
176                                     "time-add-day");
177                 return NULL;
178         }
179         n = argv[1]->value.number;
180         
181         result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
182         result->value.time = time_add_day (t, n);
183
184         return result;
185 }
186
187 /**
188  * e_cal_backend_sexp_func_time_day_begin:
189  * @esexp: An #ESExp object.
190  * @argc: Number of arguments.
191  * @argv: The arguments.
192  * @data: Closure data.
193  *
194  * (time-day-begin TIME)
195  * TIME - time_t, base time
196  *
197  * Returns the start of the day, according to the local time.
198  *
199  * FIXME: TIMEZONES - this uses the current Unix timezone.
200  *
201  * Return value: The result of the function.
202  */
203 ESExpResult *
204 e_cal_backend_sexp_func_time_day_begin (ESExp *esexp, int argc, ESExpResult **argv, void *data)
205 {
206         time_t t;
207         ESExpResult *result;
208
209         if (argc != 1) {
210                 e_sexp_fatal_error (esexp, _("\"%s\" expects one argument"),
211                                     "time-day-begin");
212                 return NULL;
213         }
214
215         if (argv[0]->type != ESEXP_RES_TIME) {
216                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
217                                              "argument to be a time_t"),
218                                     "time-day-begin");
219                 return NULL;
220         }
221         t = argv[0]->value.time;
222
223         result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
224         result->value.time = time_day_begin (t);
225
226         return result;
227 }
228
229 /**
230  * e_cal_backend_sexp_func_time_day_end:
231  * @esexp: An #ESExp object.
232  * @argc: Number of arguments.
233  * @argv: The arguments.
234  * @data: Closure data.
235  *
236  * (time-day-end TIME)
237  * TIME - time_t, base time
238  *
239  * Returns the end of the day, according to the local time.
240  *
241  * FIXME: TIMEZONES - this uses the current Unix timezone.
242  *
243  * Return value: The result of the function.
244  */
245 ESExpResult *
246 e_cal_backend_sexp_func_time_day_end (ESExp *esexp, int argc, ESExpResult **argv, void *data)
247 {
248         time_t t;
249         ESExpResult *result;
250
251         if (argc != 1) {
252                 e_sexp_fatal_error (esexp, _("\"%s\" expects one argument"),
253                                     "time-day-end");
254                 return NULL;
255         }
256
257         if (argv[0]->type != ESEXP_RES_TIME) {
258                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
259                                              "argument to be a time_t"),
260                                     "time-day-end");
261                 return NULL;
262         }
263         t = argv[0]->value.time;
264
265         result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
266         result->value.time = time_day_end (t);
267
268         return result;
269 }
270
271 /* (uid? UID)
272  *
273  * UID - the uid of the component
274  *
275  * Returns a boolean indicating whether the component has the given UID
276  */
277 static ESExpResult *
278 func_uid (ESExp *esexp, int argc, ESExpResult **argv, void *data)
279 {
280         SearchContext *ctx = data;
281         const char *uid = NULL, *arg_uid;
282         gboolean equal;
283         ESExpResult *result;
284
285         /* Check argument types */
286
287         if (argc != 1) {
288                 e_sexp_fatal_error (esexp, _("\"%s\" expects one argument"),
289                                     "uid");
290                 return NULL;
291         }
292
293         if (argv[0]->type != ESEXP_RES_STRING) {
294                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
295                                              "argument to be a string"),
296                                     "uid");
297                 return NULL;
298         }
299
300         arg_uid = argv[0]->value.string;        
301         e_cal_component_get_uid (ctx->comp, &uid);
302
303         if (!arg_uid && !uid)
304                 equal = TRUE;
305         else if ((!arg_uid || !uid) && arg_uid != uid)
306                 equal = FALSE;
307         else if (e_util_utf8_strstrcase (arg_uid, uid) != NULL && strlen (arg_uid) == strlen (uid))
308                 equal = TRUE;
309         else
310                 equal = FALSE;
311         
312         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
313         result->value.bool = equal;
314
315         return result;
316 }
317
318 static gboolean
319 check_instance_time_range_cb (ECalComponent *comp, time_t instance_start, time_t instance_end, gpointer data)
320 {
321         SearchContext *ctx = data;
322
323         /* if we get called, the event has an occurrence in the given time range */
324         ctx->occurs = TRUE;
325
326         return FALSE;
327 }
328
329 static icaltimezone *
330 resolve_tzid (const char *tzid, gpointer user_data)
331 {
332         SearchContext *ctx = user_data;
333                                                                                 
334         if (!tzid || !tzid[0])
335                 return NULL;
336         else if (!strcmp (tzid, "UTC"))
337                 return icaltimezone_get_utc_timezone ();
338                                                                                 
339         return e_cal_backend_internal_get_timezone (ctx->backend, tzid);
340 }
341
342 /* (occur-in-time-range? START END)
343  *
344  * START - time_t, start of the time range
345  * END - time_t, end of the time range
346  *
347  * Returns a boolean indicating whether the component has any occurrences in the
348  * specified time range.
349  */
350 static ESExpResult *
351 func_occur_in_time_range (ESExp *esexp, int argc, ESExpResult **argv, void *data)
352 {
353         SearchContext *ctx = data;
354         time_t start, end;
355         ESExpResult *result;
356         icaltimezone *default_zone;
357
358         /* Check argument types */
359
360         if (argc != 2) {
361                 e_sexp_fatal_error (esexp, _("\"%s\" expects two arguments"),
362                                     "occur-in-time-range");
363                 return NULL;
364         }
365
366         if (argv[0]->type != ESEXP_RES_TIME) {
367                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
368                                              "argument to be a time_t"),
369                                     "occur-in-time-range");
370                 return NULL;
371         }
372         start = argv[0]->value.time;
373
374         if (argv[1]->type != ESEXP_RES_TIME) {
375                 e_sexp_fatal_error (esexp, _("\"%s\" expects the second "
376                                              "argument to be a time_t"),
377                                     "occur-in-time-range");
378                 return NULL;
379         }
380         end = argv[1]->value.time;
381
382         /* See if the object occurs in the specified time range */
383         default_zone = e_cal_backend_internal_get_default_timezone (ctx->backend);
384         if (!default_zone)
385                 default_zone = icaltimezone_get_utc_timezone ();
386
387         ctx->occurs = FALSE;
388         e_cal_recur_generate_instances (ctx->comp, start, end,
389                                         (ECalRecurInstanceFn) check_instance_time_range_cb,
390                                         ctx, resolve_tzid, ctx,
391                                         default_zone);
392
393         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
394         result->value.bool = ctx->occurs;
395
396         return result;
397 }
398
399 static ESExpResult *
400 func_due_in_time_range (ESExp *esexp, int argc, ESExpResult **argv, void *data)
401 {
402         SearchContext *ctx = data;
403         time_t start, end;
404         ESExpResult *result;
405         icaltimezone *zone;
406         ECalComponentDateTime dt;
407         time_t due_t ;
408         gboolean retval;
409
410         /* Check argument types */
411
412         if (argc != 2) {
413                 e_sexp_fatal_error (esexp, _("\"%s\" expects two arguments"),
414                                 "due-in-time-range");
415                 return NULL;
416         }
417
418         if (argv[0]->type != ESEXP_RES_TIME) {
419                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
420                                         "argument to be a time_t"),
421                                 "due-in-time-range");
422                 return NULL;
423         }
424
425         start = argv[0]->value.time;
426
427         if (argv[1]->type != ESEXP_RES_TIME) {
428                 e_sexp_fatal_error (esexp, _("\"%s\" expects the second "
429                                         "argument to be a time_t"),
430                                 "due-in-time-range");
431                 return NULL;
432         }
433
434         end = argv[1]->value.time;
435         e_cal_component_get_due (ctx->comp, &dt);
436
437         if(dt.value != NULL) {
438                 zone = resolve_tzid (dt.tzid, ctx);
439                 result = e_sexp_result_new (esexp, ESEXP_RES_INT);
440                 if (zone)
441                         due_t = icaltime_as_timet_with_zone(*dt.value,zone);
442                 else
443                         due_t = icaltime_as_timet(*dt.value);
444         }       
445
446         if(dt.value != NULL && (due_t <= end && due_t >= start))
447                 retval = TRUE;
448         else 
449                 retval = FALSE; 
450
451         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
452         result->value.bool = retval;
453
454         e_cal_component_free_datetime (&dt);
455         
456         return result;
457 }
458
459 /* Returns whether a list of ECalComponentText items matches the specified string */
460 static gboolean
461 matches_text_list (GSList *text_list, const char *str)
462 {
463         GSList *l;
464         gboolean matches;
465
466         matches = FALSE;
467
468         for (l = text_list; l; l = l->next) {
469                 ECalComponentText *text;
470
471                 text = l->data;
472                 g_assert (text->value != NULL);
473
474                 if (e_util_utf8_strstrcasedecomp (text->value, str) != NULL) {
475                         matches = TRUE;
476                         break;
477                 }
478         }
479
480         return matches;
481 }
482
483 /* Returns whether the comments in a component matches the specified string */
484 static gboolean
485 matches_comment (ECalComponent *comp, const char *str)
486 {
487         GSList *list;
488         gboolean matches;
489
490         e_cal_component_get_comment_list (comp, &list);
491         matches = matches_text_list (list, str);
492         e_cal_component_free_text_list (list);
493
494         return matches;
495 }
496
497 /* Returns whether the description in a component matches the specified string */
498 static gboolean
499 matches_description (ECalComponent *comp, const char *str)
500 {
501         GSList *list;
502         gboolean matches;
503
504         e_cal_component_get_description_list (comp, &list);
505         matches = matches_text_list (list, str);
506         e_cal_component_free_text_list (list);
507
508         return matches;
509 }
510
511 static gboolean
512 matches_attendee (ECalComponent *comp, const char *str)
513 {
514         GSList *a_list = NULL, *l;
515         gboolean matches = FALSE;
516
517         e_cal_component_get_attendee_list (comp, &a_list);
518
519         for (l = a_list; l; l = l->next) {
520                 ECalComponentAttendee *att = l->data;
521
522                 if ((att->value && e_util_strstrcase (att->value, str)) || (att->cn != NULL &&
523                                         e_util_strstrcase (att->cn, str))) {
524                         matches = TRUE;
525                         break;
526                 }
527         }
528
529         e_cal_component_free_attendee_list (a_list);
530
531         return matches;
532
533 }
534
535 static gboolean
536 matches_organizer (ECalComponent *comp, const char *str)
537 {
538
539         ECalComponentOrganizer org;
540
541         e_cal_component_get_organizer (comp, &org);
542         if (str && !*str)
543                 return TRUE;
544
545         if ((org.value && e_util_strstrcase (org.value, str)) || 
546                         (org.cn && e_util_strstrcase (org.cn, str)))
547                 return TRUE;
548
549         return FALSE;
550 }
551
552 static gboolean
553 matches_classification (ECalComponent *comp, const char *str)
554 {
555         ECalComponentClassification classification;
556         ECalComponentClassification classification1;
557
558         if (!*str)
559                 return FALSE;
560         
561         if(g_str_equal (str, "Public"))
562                 classification1 = E_CAL_COMPONENT_CLASS_PUBLIC;
563         else if(g_str_equal (str, "Private"))
564                 classification1 = E_CAL_COMPONENT_CLASS_PRIVATE;
565         else if(g_str_equal (str, "Confidential"))
566                 classification1 = E_CAL_COMPONENT_CLASS_CONFIDENTIAL;
567         else    
568                 classification1 = E_CAL_COMPONENT_CLASS_UNKNOWN;
569
570         e_cal_component_get_classification(comp, &classification);
571
572         return (classification == classification1 ? TRUE : FALSE);
573 }
574
575 /* Returns whether the summary in a component matches the specified string */
576 static gboolean
577 matches_summary (ECalComponent *comp, const char *str)
578 {
579         ECalComponentText text;
580
581         e_cal_component_get_summary (comp, &text);
582
583         if (!*str)
584                 return TRUE;
585
586         if (!text.value)
587                 return FALSE;
588
589         return e_util_utf8_strstrcasedecomp (text.value, str) != NULL;
590 }
591
592 /* Returns whether the location in a component matches the specified string */
593 static gboolean
594 matches_location (ECalComponent *comp, const char *str)
595 {
596         const char *location = NULL;
597
598         e_cal_component_get_location (comp, &location);
599
600         if (!location)
601                 return FALSE;
602
603         return e_util_utf8_strstrcasedecomp (location, str) != NULL;
604 }
605
606 /* Returns whether any text field in a component matches the specified string */
607 static gboolean
608 matches_any (ECalComponent *comp, const char *str)
609 {
610         /* As an optimization, and to make life easier for the individual
611          * predicate functions, see if we are looking for the empty string right
612          * away.
613          */
614         if (strlen (str) == 0)
615                 return TRUE;
616
617         return (matches_comment (comp, str)
618                 || matches_description (comp, str)
619                 || matches_summary (comp, str)
620                 || matches_location (comp, str));
621 }
622
623 static gboolean
624 matches_priority (ECalComponent *comp ,const char *pr)
625 {
626         int *priority = NULL;
627
628         e_cal_component_get_priority (comp, &priority);
629
630         if (!priority || !*priority)
631                 return FALSE;
632
633         if (g_str_equal (pr, "HIGH") && *priority <= 4) 
634                 return TRUE;
635         else if (g_str_equal (pr, "NORMAL") && *priority == 5)
636                 return TRUE;
637         else if (g_str_equal (pr, "LOW") && *priority > 5)
638                 return TRUE;
639         else if (g_str_equal (pr, "UNDEFINED") && (!priority || !*priority))
640                 return TRUE ;
641
642         return FALSE;
643 }
644
645 static gboolean
646 matches_status (ECalComponent *comp ,const char *str)
647 {
648         icalproperty_status status ;
649
650         if (!*str)
651                 return FALSE;
652
653         e_cal_component_get_status (comp, &status);
654
655         if (g_str_equal (str, "NOT STARTED") && status == ICAL_STATUS_NONE)
656                         return TRUE;
657         else if (g_str_equal (str, "COMPLETED") && status == ICAL_STATUS_COMPLETED) 
658                         return TRUE;
659         else if(g_str_equal (str, "CANCELLED") && status == ICAL_STATUS_CANCELLED)  
660                         return TRUE;
661         else if(g_str_equal (str, "IN PROGRESS")  && status == ICAL_STATUS_INPROCESS)
662                         return TRUE;
663
664         return FALSE;
665 }
666
667 static ESExpResult *
668 func_has_attachment (ESExp *esexp, int argc, ESExpResult **argv, void *data)
669 {
670         SearchContext *ctx = data;
671         ESExpResult *result;
672
673         if (argc != 0) {
674                 e_sexp_fatal_error (esexp, _("\"%s\" expects no arguments"),
675                                 "has-attachments?");
676                 return NULL;
677         }
678
679         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
680         result->value.bool = e_cal_component_has_attachments (ctx->comp);
681
682         return result;
683 }
684
685 static ESExpResult *
686 func_percent_complete (ESExp *esexp, int argc, ESExpResult **argv, void *data)
687 {
688         SearchContext *ctx = data;
689         ESExpResult *result = NULL;
690         int *percent;
691
692         if (argc != 0) {
693                 e_sexp_fatal_error (esexp, _("\"%s\" expects no arguments"),
694                                 "percent-completed");
695                 return NULL;
696         }
697
698         e_cal_component_get_percent (ctx->comp, &percent);
699
700         if (percent && *percent) {      
701                 result = e_sexp_result_new (esexp, ESEXP_RES_INT);
702                 result->value.number = *percent;
703
704         }  
705
706         return result;
707 }
708
709 /* (contains? FIELD STR)
710  *
711  * FIELD - string, name of field to match (any, comment, description, summary, location)
712  * STR - string, match string
713  *
714  * Returns a boolean indicating whether the specified field contains the
715  * specified string.
716  */
717 static ESExpResult *
718 func_contains (ESExp *esexp, int argc, ESExpResult **argv, void *data)
719 {
720         SearchContext *ctx = data;
721         const char *field;
722         const char *str;
723         gboolean matches;
724         ESExpResult *result;
725
726         /* Check argument types */
727
728         if (argc != 2) {
729                 e_sexp_fatal_error (esexp, _("\"%s\" expects two arguments"),
730                                     "contains");
731                 return NULL;
732         }
733
734         if (argv[0]->type != ESEXP_RES_STRING) {
735                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
736                                              "argument to be a string"),
737                                     "contains");
738                 return NULL;
739         }
740         field = argv[0]->value.string;
741
742         if (argv[1]->type != ESEXP_RES_STRING) {
743                 e_sexp_fatal_error (esexp, _("\"%s\" expects the second "
744                                              "argument to be a string"),
745                                     "contains");
746                 return NULL;
747         }
748         str = argv[1]->value.string;
749
750         /* See if it matches */
751
752         if (strcmp (field, "any") == 0)
753                 matches = matches_any (ctx->comp, str);
754         else if (strcmp (field, "comment") == 0)
755                 matches = matches_comment (ctx->comp, str);
756         else if (strcmp (field, "description") == 0)
757                 matches = matches_description (ctx->comp, str);
758         else if (strcmp (field, "summary") == 0)
759                 matches = matches_summary (ctx->comp, str);
760         else if (strcmp (field, "location") == 0)
761                 matches = matches_location (ctx->comp, str);
762         else if (strcmp (field, "attendee") == 0)
763                 matches = matches_attendee (ctx->comp, str);
764         else if (strcmp (field, "organizer") == 0)
765                 matches = matches_organizer (ctx->comp, str);
766         else if(strcmp (field, "classification") == 0)
767                 matches = matches_classification (ctx->comp, str);
768         else if(strcmp (field, "status") == 0)
769                 matches = matches_status (ctx->comp, str);
770         else if(strcmp (field, "priority") == 0)
771                 matches = matches_priority (ctx->comp, str);
772         else {
773                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
774                                              "argument to be either \"any\", "
775                                         "\"summary\", or \"description\", or \"location\", or \"attendee\", or \"organizer\", or \"classification\""),
776                                     "contains");
777                 return NULL;
778         }
779
780         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
781         result->value.bool = matches;
782
783         return result;
784 }
785
786 /* (has-alarms?)
787  *
788  * A boolean value for components that have/dont have alarms.
789  *
790  * Returns: a boolean indicating whether the component has alarms or not.
791  */
792 static ESExpResult *
793 func_has_alarms (ESExp *esexp, int argc, ESExpResult **argv, void *data)
794 {
795         SearchContext *ctx = data;
796         ESExpResult *result;
797
798         /* Check argument types */
799
800         if (argc != 0) {
801                 e_sexp_fatal_error (esexp, _("\"%s\" expects no arguments"),
802                                     "has-alarms");
803                 return NULL;
804         }
805
806         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
807         result->value.bool = e_cal_component_has_alarms (ctx->comp);
808
809         return result;
810 }
811
812 /* (has-alarms-in-range? START END)
813  *
814  * START - time_t, start of the time range
815  * END - time_t, end of the time range
816  *
817  * Returns: a boolean indicating whether the component has alarms in the given
818  * time range or not.
819  */
820 static ESExpResult *
821 func_has_alarms_in_range (ESExp *esexp, int argc, ESExpResult **argv, void *data)
822 {
823         time_t start, end;
824         ESExpResult *result;
825         icaltimezone *default_zone;
826         ECalComponentAlarms *alarms;
827         ECalComponentAlarmAction omit[] = {-1};
828         SearchContext *ctx = data;
829
830         /* Check argument types */
831
832         if (argc != 2) {
833                 e_sexp_fatal_error (esexp, _("\"%s\" expects two arguments"),
834                                     "has-alarms-in-range");
835                 return NULL;
836         }
837
838         if (argv[0]->type != ESEXP_RES_TIME) {
839                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
840                                              "argument to be a time_t"),
841                                     "has-alarms-in-range");
842                 return NULL;
843         }
844         start = argv[0]->value.time;
845
846         if (argv[1]->type != ESEXP_RES_TIME) {
847                 e_sexp_fatal_error (esexp, _("\"%s\" expects the second "
848                                              "argument to be a time_t"),
849                                     "has-alarms-in-range");
850                 return NULL;
851         }
852         end = argv[1]->value.time;
853
854         /* See if the object has alarms in the given time range */
855         default_zone = e_cal_backend_internal_get_default_timezone (ctx->backend);
856         if (!default_zone)
857                 default_zone = icaltimezone_get_utc_timezone ();
858
859         alarms = e_cal_util_generate_alarms_for_comp (ctx->comp, start, end,
860                                                       omit, resolve_tzid,
861                                                       ctx, default_zone);
862
863         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
864         if (alarms) {
865                 result->value.bool = TRUE;
866                 e_cal_component_alarms_free (alarms);
867         } else
868                 result->value.bool = FALSE;
869
870         return result;
871 }
872
873 /* (has-categories? STR+)
874  * (has-categories? #f)
875  *
876  * STR - At least one string specifying a category
877  * Or you can specify a single #f (boolean false) value for components
878  * that have no categories assigned to them ("unfiled").
879  *
880  * Returns a boolean indicating whether the component has all the specified
881  * categories.
882  */
883 static ESExpResult *
884 func_has_categories (ESExp *esexp, int argc, ESExpResult **argv, void *data)
885 {
886         SearchContext *ctx = data;
887         gboolean unfiled;
888         int i;
889         GSList *categories;
890         gboolean matches;
891         ESExpResult *result;
892
893         /* Check argument types */
894
895         if (argc < 1) {
896                 e_sexp_fatal_error (esexp, _("\"%s\" expects at least one "
897                                              "argument"),
898                                     "has-categories");
899                 return NULL;
900         }
901
902         if (argc == 1 && argv[0]->type == ESEXP_RES_BOOL)
903                 unfiled = TRUE;
904         else
905                 unfiled = FALSE;
906
907         if (!unfiled)
908                 for (i = 0; i < argc; i++)
909                         if (argv[i]->type != ESEXP_RES_STRING) {
910                                 e_sexp_fatal_error (esexp, _("\"%s\" expects "
911                                                              "all arguments to "
912                                                              "be strings or "
913                                                              "one and only one "
914                                                              "argument to be a "
915                                                              "boolean false "
916                                                              "(#f)"),
917                                                     "has-categories");
918                                 return NULL;
919                         }
920
921         /* Search categories.  First, if there are no categories we return
922          * whether unfiled components are supposed to match.
923          */
924
925         e_cal_component_get_categories_list (ctx->comp, &categories);
926         if (!categories) {
927                 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
928                 result->value.bool = unfiled;
929
930                 return result;
931         }
932
933         /* Otherwise, we *do* have categories but unfiled components were
934          * requested, so this component does not match.
935          */
936         if (unfiled) {
937                 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
938                 result->value.bool = FALSE;
939
940                 return result;
941         }
942
943         matches = TRUE;
944
945         for (i = 0; i < argc; i++) {
946                 const char *sought;
947                 GSList *l;
948                 gboolean has_category;
949
950                 sought = argv[i]->value.string;
951
952                 has_category = FALSE;
953
954                 for (l = categories; l; l = l->next) {
955                         const char *category;
956
957                         category = l->data;
958
959                         if (strcmp (category, sought) == 0) {
960                                 has_category = TRUE;
961                                 break;
962                         }
963                 }
964
965                 if (!has_category) {
966                         matches = FALSE;
967                         break;
968                 }
969         }
970
971         e_cal_component_free_categories_list (categories);
972
973         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
974         result->value.bool = matches;
975
976         return result;
977 }
978
979 /* (has-recurrences?)
980  *
981  * A boolean value for components that have/dont have recurrences.
982  *
983  * Returns: a boolean indicating whether the component has recurrences or not.
984  */
985 static ESExpResult *
986 func_has_recurrences (ESExp *esexp, int argc, ESExpResult **argv, void *data)
987 {
988         SearchContext *ctx = data;
989         ESExpResult *result;
990
991         /* Check argument types */
992
993         if (argc != 0) {
994                 e_sexp_fatal_error (esexp, _("\"%s\" expects no arguments"),
995                                     "has-recurrences");
996                 return NULL;
997         }
998
999         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1000         result->value.bool = (e_cal_component_has_recurrences (ctx->comp) || e_cal_component_is_instance (ctx->comp));
1001
1002         return result;
1003 }
1004
1005 /* (is-completed?)
1006  *
1007  * Returns a boolean indicating whether the component is completed (i.e. has
1008  * a COMPLETED property. This is really only useful for TODO components.
1009  */
1010 static ESExpResult *
1011 func_is_completed (ESExp *esexp, int argc, ESExpResult **argv, void *data)
1012 {
1013         SearchContext *ctx = data;
1014         ESExpResult *result;
1015         struct icaltimetype *t;
1016         gboolean complete = FALSE;
1017
1018         /* Check argument types */
1019
1020         if (argc != 0) {
1021                 e_sexp_fatal_error (esexp, _("\"%s\" expects no arguments"),
1022                                     "is-completed");
1023                 return NULL;
1024         }
1025
1026         e_cal_component_get_completed (ctx->comp, &t);
1027         if (t) {
1028                 complete = TRUE;
1029                 e_cal_component_free_icaltimetype (t);
1030         }
1031
1032         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1033         result->value.bool = complete;
1034
1035         return result;
1036 }
1037
1038 /* (completed-before? TIME)
1039  *
1040  * TIME - time_t
1041  *
1042  * Returns a boolean indicating whether the component was completed on or
1043  * before the given time (i.e. it checks the COMPLETED property).
1044  * This is really only useful for TODO components.
1045  */
1046 static ESExpResult *
1047 func_completed_before (ESExp *esexp, int argc, ESExpResult **argv, void *data)
1048 {
1049         SearchContext *ctx = data;
1050         ESExpResult *result;
1051         struct icaltimetype *tt;
1052         icaltimezone *zone;
1053         gboolean retval = FALSE;
1054         time_t before_time, completed_time;
1055
1056         /* Check argument types */
1057
1058         if (argc != 1) {
1059                 e_sexp_fatal_error (esexp, _("\"%s\" expects one argument"),
1060                                     "completed-before");
1061                 return NULL;
1062         }
1063
1064         if (argv[0]->type != ESEXP_RES_TIME) {
1065                 e_sexp_fatal_error (esexp, _("\"%s\" expects the first "
1066                                              "argument to be a time_t"),
1067                                     "completed-before");
1068                 return NULL;
1069         }
1070         before_time = argv[0]->value.time;
1071
1072         e_cal_component_get_completed (ctx->comp, &tt);
1073         if (tt) {
1074                 /* COMPLETED must be in UTC. */
1075                 zone = icaltimezone_get_utc_timezone ();
1076                 completed_time = icaltime_as_timet_with_zone (*tt, zone);
1077
1078 #if 0
1079                 g_print ("Query Time    : %s", ctime (&before_time));
1080                 g_print ("Completed Time: %s", ctime (&completed_time));
1081 #endif
1082
1083                 /* We want to return TRUE if before_time is after
1084                    completed_time. */
1085                 if (difftime (before_time, completed_time) > 0) {
1086 #if 0
1087                         g_print ("  Returning TRUE\n");
1088 #endif
1089                         retval = TRUE;
1090                 }
1091
1092                 e_cal_component_free_icaltimetype (tt);
1093         }
1094
1095         result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1096         result->value.bool = retval;
1097
1098         return result;
1099 }
1100
1101 #if 0
1102 static struct prop_info {
1103         ECardSimpleField field_id;
1104         const char *query_prop;
1105         const char *ecard_prop;
1106 #define PROP_TYPE_NORMAL   0x01
1107 #define PROP_TYPE_LIST     0x02
1108 #define PROP_TYPE_LISTITEM 0x03
1109 #define PROP_TYPE_ID 0x04
1110         int prop_type;
1111         gboolean (*list_compare)(ECardSimple *ecard, const char *str,
1112                                  char *(*compare)(const char*, const char*));
1113
1114 } prop_info_table[] = {
1115 #define NORMAL_PROP(f,q,e) {f, q, e, PROP_TYPE_NORMAL, NULL}
1116 #define ID_PROP {0, "id", NULL, PROP_TYPE_ID, NULL}
1117 #define LIST_PROP(q,e,c) {0, q, e, PROP_TYPE_LIST, c}
1118
1119         /* query prop,  ecard prop,   type,              list compare function */
1120         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_FILE_AS, "file_as", "file_as" ),
1121         LIST_PROP ( "full_name", "full_name", compare_name), /* not really a list, but we need to compare both full and surname */
1122         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_URL, "url", "url" ),
1123         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_MAILER, "mailer", "mailer"),
1124         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_ORG, "org", "org"),
1125         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_ORG_UNIT, "org_unit", "org_unit"),
1126         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_OFFICE, "office", "office"),
1127         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_TITLE, "title", "title"),
1128         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_ROLE, "role", "role"),
1129         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_MANAGER, "manager", "manager"),
1130         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_ASSISTANT, "assistant", "assistant"),
1131         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_NICKNAME, "nickname", "nickname"),
1132         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_SPOUSE, "spouse", "spouse" ),
1133         NORMAL_PROP ( E_CARD_SIMPLE_FIELD_NOTE, "note", "note"),
1134         ID_PROP,
1135         LIST_PROP ( "email", "email", compare_email ),
1136         LIST_PROP ( "phone", "phone", compare_phone ),
1137         LIST_PROP ( "address", "address", compare_address ),
1138         LIST_PROP ( "category", "category", compare_category ),
1139         LIST_PROP ( "arbitrary", "arbitrary", compare_arbitrary )
1140 };
1141 static int num_prop_infos = sizeof(prop_info_table) / sizeof(prop_info_table[0]);
1142
1143 static ESExpResult *
1144 entry_compare(SearchContext *ctx, struct _ESExp *f,
1145               int argc, struct _ESExpResult **argv,
1146               char *(*compare)(const char*, const char*))
1147 {
1148         ESExpResult *r;
1149         int truth = FALSE;
1150
1151         if (argc == 2
1152             && argv[0]->type == ESEXP_RES_STRING
1153             && argv[1]->type == ESEXP_RES_STRING) {
1154                 char *propname;
1155                 struct prop_info *info = NULL;
1156                 int i;
1157                 gboolean any_field;
1158
1159                 propname = argv[0]->value.string;
1160
1161                 any_field = !strcmp(propname, "x-evolution-any-field");
1162                 for (i = 0; i < num_prop_infos; i ++) {
1163                         if (any_field
1164                             || !strcmp (prop_info_table[i].query_prop, propname)) {
1165                                 info = &prop_info_table[i];
1166                                 
1167                                 if (info->prop_type == PROP_TYPE_NORMAL) {
1168                                         char *prop = NULL;
1169                                         /* searches where the query's property
1170                                            maps directly to an ecard property */
1171                                         
1172                                         prop = e_card_simple_get (ctx->card, info->field_id);
1173
1174                                         if (prop && compare(prop, argv[1]->value.string)) {
1175                                                 truth = TRUE;
1176                                         }
1177                                         if ((!prop) && compare("", argv[1]->value.string)) {
1178                                                 truth = TRUE;
1179                                         }
1180                                         g_free (prop);
1181                                 } else if (info->prop_type == PROP_TYPE_LIST) {
1182                                 /* the special searches that match any of the list elements */
1183                                         truth = info->list_compare (ctx->card, argv[1]->value.string, compare);
1184                                 } else if (info->prop_type == PROP_TYPE_ID) {
1185                                         const char *prop = NULL;
1186                                         /* searches where the query's property
1187                                            maps directly to an ecard property */
1188                                         
1189                                         prop = e_card_get_id (ctx->card->card);
1190
1191                                         if (prop && compare(prop, argv[1]->value.string)) {
1192                                                 truth = TRUE;
1193                                         }
1194                                         if ((!prop) && compare("", argv[1]->value.string)) {
1195                                                 truth = TRUE;
1196                                         }
1197                                 }
1198
1199                                 /* if we're looking at all fields and find a match,
1200                                    or if we're just looking at this one field,
1201                                    break. */
1202                                 if ((any_field && truth)
1203                                     || !any_field)
1204                                         break;
1205                         }
1206                 }
1207                 
1208         }
1209         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1210         r->value.bool = truth;
1211
1212         return r;
1213 }
1214 #endif
1215
1216 /* 'builtin' functions */
1217 static struct {
1218         char *name;
1219         ESExpFunc *func;
1220         int type;               /* set to 1 if a function can perform shortcut evaluation, or
1221                                    doesn't execute everything, 0 otherwise */
1222 } symbols[] = {
1223         /* Time-related functions */
1224         { "time-now", e_cal_backend_sexp_func_time_now, 0 },
1225         { "make-time", e_cal_backend_sexp_func_make_time, 0 },
1226         { "time-add-day", e_cal_backend_sexp_func_time_add_day, 0 },
1227         { "time-day-begin", e_cal_backend_sexp_func_time_day_begin, 0 },
1228         { "time-day-end", e_cal_backend_sexp_func_time_day_end, 0 },
1229         /* Component-related functions */
1230         { "uid?", func_uid, 0 },
1231         { "occur-in-time-range?", func_occur_in_time_range, 0 },
1232         { "due-in-time-range?", func_due_in_time_range, 0 },
1233         { "contains?", func_contains, 0 },
1234         { "has-alarms?", func_has_alarms, 0 },
1235         { "has-alarms-in-range?", func_has_alarms_in_range, 0 },
1236         { "has-recurrences?", func_has_recurrences, 0 },
1237         { "has-categories?", func_has_categories, 0 },
1238         { "is-completed?", func_is_completed, 0 },
1239         { "completed-before?", func_completed_before, 0 },
1240         { "has-attachments?", func_has_attachment, 0 },
1241         { "percent-complete?", func_percent_complete, 0 }
1242 };
1243
1244 /**
1245  * e_cal_backend_sexp_match_comp:
1246  * @sexp: An #ESExp object.
1247  * @comp: Component to match against the expression.
1248  * @backend: Backend.
1249  *
1250  * Matches the given ECalComponent against the expression.
1251  *
1252  * Return value: TRUE if the component matched the expression, FALSE if not.
1253  */
1254 gboolean
1255 e_cal_backend_sexp_match_comp (ECalBackendSExp *sexp, ECalComponent *comp, ECalBackend *backend)
1256 {
1257         ESExpResult *r;
1258         gboolean retval;
1259
1260         g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), FALSE);
1261         g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
1262         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), FALSE);
1263
1264         sexp->priv->search_context->comp = g_object_ref (comp);
1265         sexp->priv->search_context->backend = g_object_ref (backend);
1266
1267         /* if it's not a valid vcard why is it in our db? :) */
1268         if (!sexp->priv->search_context->comp)  {
1269                 g_object_unref (sexp->priv->search_context->backend);
1270                 return FALSE;
1271         }
1272         r = e_sexp_eval(sexp->priv->search_sexp);
1273
1274         retval = (r && r->type == ESEXP_RES_BOOL && r->value.bool);
1275
1276         g_object_unref (sexp->priv->search_context->comp);
1277         g_object_unref (sexp->priv->search_context->backend);
1278
1279         e_sexp_result_free(sexp->priv->search_sexp, r);
1280
1281         return retval;
1282 }
1283
1284 /**
1285  * e_cal_backend_sexp_match_object:
1286  * @sexp: An #ESExp object.
1287  * @object: An iCalendar string.
1288  * @backend: A backend.
1289  *
1290  * Match an iCalendar expression against the expression.
1291  *
1292  * Return value: TRUE if the object matches the expression, FALSE if not.
1293  */
1294 gboolean
1295 e_cal_backend_sexp_match_object (ECalBackendSExp *sexp, const char *object, ECalBackend *backend)
1296 {
1297         ECalComponent *comp;
1298         icalcomponent *icalcomp;
1299         gboolean retval;
1300
1301         icalcomp = icalcomponent_new_from_string ((char *) object);
1302         if (!icalcomp)
1303                 return FALSE;
1304
1305         comp = e_cal_component_new ();
1306         e_cal_component_set_icalcomponent (comp, icalcomp);
1307         
1308         retval = e_cal_backend_sexp_match_comp (sexp, comp, backend);
1309
1310         g_object_unref (comp);
1311
1312         return retval;
1313 }
1314
1315 \f
1316
1317 /**
1318  * e_cal_backend_card_sexp_new:
1319  * @text: The expression to use.
1320  *
1321  * Creates a new #EXCalBackendSExp object.
1322  *
1323  * Return value: The newly created ECalBackendSExp object.
1324  */
1325 ECalBackendSExp *
1326 e_cal_backend_sexp_new (const char *text)
1327 {
1328         ECalBackendSExp *sexp = g_object_new (E_TYPE_CAL_BACKEND_SEXP, NULL);
1329         int esexp_error;
1330         int i;
1331
1332         sexp->priv->search_sexp = e_sexp_new();
1333         sexp->priv->text = g_strdup (text);
1334
1335         for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
1336                 if (symbols[i].type == 1) {
1337                         e_sexp_add_ifunction(sexp->priv->search_sexp, 0, symbols[i].name,
1338                                              (ESExpIFunc *)symbols[i].func, sexp->priv->search_context);
1339                 } else {
1340                         e_sexp_add_function(sexp->priv->search_sexp, 0, symbols[i].name,
1341                                             symbols[i].func, sexp->priv->search_context);
1342                 }
1343         }
1344
1345         e_sexp_input_text(sexp->priv->search_sexp, text, strlen(text));
1346         esexp_error = e_sexp_parse(sexp->priv->search_sexp);
1347
1348         if (esexp_error == -1) {
1349                 g_object_unref (sexp);
1350                 sexp = NULL;
1351         }
1352
1353         return sexp;
1354 }
1355
1356 /**
1357  * e_cal_backend_sexp_text:
1358  * @sexp: An #ECalBackendSExp object.
1359  *
1360  * Retrieve the text expression for the given ECalBackendSExp object.
1361  *
1362  * Return value: The text expression.
1363  */
1364 const char *
1365 e_cal_backend_sexp_text (ECalBackendSExp *sexp)
1366 {
1367         ECalBackendSExpPrivate *priv;
1368         
1369         g_return_val_if_fail (sexp != NULL, NULL);
1370         g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), NULL);
1371
1372         priv = sexp->priv;
1373
1374         return priv->text;
1375 }
1376
1377 static void
1378 e_cal_backend_sexp_dispose (GObject *object)
1379 {
1380         ECalBackendSExp *sexp = E_CAL_BACKEND_SEXP (object);
1381
1382         if (sexp->priv) {
1383                 e_sexp_unref(sexp->priv->search_sexp);
1384
1385                 g_free (sexp->priv->text);
1386
1387                 g_free (sexp->priv->search_context);
1388                 g_free (sexp->priv);
1389                 sexp->priv = NULL;
1390         }
1391
1392         if (G_OBJECT_CLASS (parent_class)->dispose)
1393                 G_OBJECT_CLASS (parent_class)->dispose (object);
1394 }
1395
1396 static void
1397 e_cal_backend_sexp_class_init (ECalBackendSExpClass *klass)
1398 {
1399         GObjectClass  *object_class = G_OBJECT_CLASS (klass);
1400
1401         parent_class = g_type_class_peek_parent (klass);
1402
1403         /* Set the virtual methods. */
1404
1405         object_class->dispose = e_cal_backend_sexp_dispose;
1406 }
1407
1408 static void
1409 e_cal_backend_sexp_init (ECalBackendSExp *sexp)
1410 {
1411         ECalBackendSExpPrivate *priv;
1412
1413         priv = g_new0 (ECalBackendSExpPrivate, 1);
1414
1415         sexp->priv = priv;
1416         priv->search_context = g_new (SearchContext, 1);
1417 }
1418
1419 /**
1420  * e_cal_backend_sexp_get_type:
1421  *
1422  * Registers the #ECalBackendSExp class if needed.
1423  *
1424  * Return value: The unique identifier of the class.
1425  */
1426 GType
1427 e_cal_backend_sexp_get_type (void)
1428 {
1429         static GType type = 0;
1430
1431         if (! type) {
1432                 GTypeInfo info = {
1433                         sizeof (ECalBackendSExpClass),
1434                         NULL, /* base_class_init */
1435                         NULL, /* base_class_finalize */
1436                         (GClassInitFunc)  e_cal_backend_sexp_class_init,
1437                         NULL, /* class_finalize */
1438                         NULL, /* class_data */
1439                         sizeof (ECalBackendSExp),
1440                         0,    /* n_preallocs */
1441                         (GInstanceInitFunc) e_cal_backend_sexp_init
1442                 };
1443
1444                 type = g_type_register_static (G_TYPE_OBJECT, "ECalBackendSExp", &info, 0);
1445         }
1446
1447         return type;
1448 }