1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 /**
19 * SECTION: e-cal-backend-sexp
20 * @include: libedata-cal/libedata-cal.h
21 * @short_description: A utility for comparing #ECalComponent(s) with search expressions.
22 *
23 * This API is an all purpose utility for comparing #ECalComponent(s) with search expressions
24 * and is used by various backends to implement component filtering and searching.
25 */
26
27 #include "evolution-data-server-config.h"
28
29 #include <string.h>
30 #include <glib/gi18n-lib.h>
31
32 #include "e-cal-backend-sexp.h"
33
34 typedef struct _SearchContext {
35 ECalComponent *comp;
36 ETimezoneCache *cache;
37 gboolean occurs;
38 gint occurrences_count;
39
40 gboolean expr_range_set;
41 time_t expr_range_start;
42 time_t expr_range_end;
43 } SearchContext;
44
45 struct _ECalBackendSExpPrivate {
46 ESExp *search_sexp;
47 gchar *text;
48 SearchContext search_context;
49 GRecMutex search_context_lock;
50 };
51
52 G_DEFINE_TYPE_WITH_PRIVATE (ECalBackendSExp, e_cal_backend_sexp, G_TYPE_OBJECT)
53
54 static ESExpResult *func_is_completed (ESExp *esexp, gint argc, ESExpResult **argv, gpointer data);
55
56 /* (uid? UID)
57 *
58 * UID - the uid of the component
59 *
60 * Returns a boolean indicating whether the component has the given UID
61 */
62 static ESExpResult *
func_uid(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)63 func_uid (ESExp *esexp,
64 gint argc,
65 ESExpResult **argv,
66 gpointer data)
67 {
68 SearchContext *ctx = data;
69 const gchar *uid, *arg_uid;
70 gboolean equal;
71 ESExpResult *result;
72
73 /* Check argument types */
74
75 if (argc != 1) {
76 e_sexp_fatal_error (
77 esexp, _("“%s” expects one argument"),
78 "uid");
79 return NULL;
80 }
81
82 if (argv[0]->type != ESEXP_RES_STRING) {
83 e_sexp_fatal_error (
84 esexp, _("“%s” expects the first "
85 "argument to be a string"),
86 "uid");
87 return NULL;
88 }
89
90 arg_uid = argv[0]->value.string;
91 uid = e_cal_component_get_uid (ctx->comp);
92
93 if (!arg_uid && !uid)
94 equal = TRUE;
95 else if ((!arg_uid || !uid) && arg_uid != uid)
96 equal = FALSE;
97 else if (e_util_utf8_strstrcase (arg_uid, uid) != NULL && strlen (arg_uid) == strlen (uid))
98 equal = TRUE;
99 else
100 equal = FALSE;
101
102 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
103 result->value.boolean = equal;
104
105 return result;
106 }
107
108 static gboolean
check_instance_time_range_cb(ICalComponent * comp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)109 check_instance_time_range_cb (ICalComponent *comp,
110 ICalTime *instance_start,
111 ICalTime *instance_end,
112 gpointer user_data,
113 GCancellable *cancellable,
114 GError **error)
115 {
116 SearchContext *ctx = user_data;
117
118 /* if we get called, the event has an occurrence in the given time range */
119 ctx->occurs = TRUE;
120
121 return FALSE;
122 }
123
124 static ICalTimezone *
resolve_tzid(const gchar * tzid,gpointer user_data,GCancellable * cancellable,GError ** error)125 resolve_tzid (const gchar *tzid,
126 gpointer user_data,
127 GCancellable *cancellable,
128 GError **error)
129 {
130 SearchContext *ctx = user_data;
131
132 if (tzid == NULL || *tzid == '\0')
133 return NULL;
134
135 return e_timezone_cache_get_timezone (ctx->cache, tzid);
136 }
137
138 /* (occur-in-time-range? START END TZLOC)
139 *
140 * START - time_t, start of the time range, in UTC
141 * END - time_t, end of the time range, in UTC
142 * TZLOC - optional string with timezone location to use
143 * as default timezone; if not set, UTC is used
144 *
145 * Returns a boolean indicating whether the component has any occurrences in the
146 * specified time range.
147 */
148 static ESExpResult *
func_occur_in_time_range(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)149 func_occur_in_time_range (ESExp *esexp,
150 gint argc,
151 ESExpResult **argv,
152 gpointer data)
153 {
154 SearchContext *ctx = data;
155 time_t start, end;
156 ESExpResult *result;
157 ICalTimezone *default_zone = NULL, *utc_zone;
158 ICalTime *starttt, *endtt;
159
160 /* Check argument types */
161
162 if (argc != 2 && argc != 3) {
163 e_sexp_fatal_error (
164 esexp, _("“%s” expects two or three arguments"),
165 "occur-in-time-range");
166 return NULL;
167 }
168
169 if (argv[0]->type != ESEXP_RES_TIME) {
170 e_sexp_fatal_error (
171 esexp, _("“%s” expects the first "
172 "argument to be a time_t"),
173 "occur-in-time-range");
174 return NULL;
175 }
176 start = argv[0]->value.time;
177
178 if (argv[1]->type != ESEXP_RES_TIME) {
179 e_sexp_fatal_error (
180 esexp, _("“%s” expects the second "
181 "argument to be a time_t"),
182 "occur-in-time-range");
183 return NULL;
184 }
185 end = argv[1]->value.time;
186
187 if (argc == 3) {
188 if (argv[2]->type != ESEXP_RES_STRING) {
189 e_sexp_fatal_error (
190 esexp, _("“%s” expects the third "
191 "argument to be a string"),
192 "occur-in-time-range");
193 return NULL;
194 }
195
196 default_zone = resolve_tzid (argv[2]->value.string, ctx, NULL, NULL);
197 }
198
199 utc_zone = i_cal_timezone_get_utc_timezone ();
200
201 if (!default_zone)
202 default_zone = utc_zone;
203
204 /* See if the object occurs in the specified time range */
205 ctx->occurs = FALSE;
206
207 starttt = i_cal_time_new_from_timet_with_zone (start, FALSE, utc_zone);
208 endtt = i_cal_time_new_from_timet_with_zone (end, FALSE, utc_zone);
209
210 e_cal_recur_generate_instances_sync (
211 e_cal_component_get_icalcomponent (ctx->comp), starttt, endtt,
212 check_instance_time_range_cb,
213 ctx, resolve_tzid, ctx,
214 default_zone, NULL, NULL);
215
216 g_clear_object (&starttt);
217 g_clear_object (&endtt);
218
219 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
220 result->value.boolean = ctx->occurs;
221
222 return result;
223 }
224
225 static gboolean
count_instances_time_range_cb(ICalComponent * comp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)226 count_instances_time_range_cb (ICalComponent *comp,
227 ICalTime *instance_start,
228 ICalTime *instance_end,
229 gpointer user_data,
230 GCancellable *cancellable,
231 GError **error)
232 {
233 SearchContext *ctx = user_data;
234
235 ctx->occurrences_count++;
236
237 return TRUE;
238 }
239
240 /*
241 * (occurrences-count? START END)
242 * (occurrences-count?)
243 *
244 * Counts occurrences either in the given time range (the first variant)
245 * or in the time range defined by the expression itself (the second variant).
246 * If the time range cannot be determined, then -1 is returned.
247 */
248 static ESExpResult *
func_occurrences_count(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)249 func_occurrences_count (ESExp *esexp,
250 gint argc,
251 ESExpResult **argv,
252 gpointer data)
253 {
254 SearchContext *ctx = data;
255 time_t start, end;
256 ESExpResult *result;
257 ICalTimezone *default_zone;
258 ICalTime *starttt, *endtt;
259
260 /* Check argument types */
261
262 if (argc != 2 && argc != 0) {
263 e_sexp_fatal_error (
264 esexp, _("“%s” expects none or two arguments"),
265 "occurrences-count");
266 return NULL;
267 }
268
269 if (argc == 2) {
270 if (argv[0]->type != ESEXP_RES_TIME) {
271 e_sexp_fatal_error (
272 esexp, _("“%s” expects the first argument to be a time_t"),
273 "occurrences-count");
274 return NULL;
275 }
276 start = argv[0]->value.time;
277
278 if (argv[1]->type != ESEXP_RES_TIME) {
279 e_sexp_fatal_error (
280 esexp, _("“%s” expects the second argument to be a time_t"),
281 "occurrences-count");
282 return NULL;
283 }
284 end = argv[1]->value.time;
285 } else if (ctx->expr_range_set) {
286 start = ctx->expr_range_start;
287 end = ctx->expr_range_end;
288 } else {
289 result = e_sexp_result_new (esexp, ESEXP_RES_INT);
290 result->value.number = -1;
291
292 return result;
293 }
294
295 default_zone = i_cal_timezone_get_utc_timezone ();
296
297 starttt = i_cal_time_new_from_timet_with_zone (start, FALSE, default_zone);
298 endtt = i_cal_time_new_from_timet_with_zone (end, FALSE, default_zone);
299
300 ctx->occurrences_count = 0;
301 e_cal_recur_generate_instances_sync (
302 e_cal_component_get_icalcomponent (ctx->comp), starttt, endtt,
303 count_instances_time_range_cb, ctx,
304 resolve_tzid, ctx, default_zone, NULL, NULL);
305
306 g_clear_object (&starttt);
307 g_clear_object (&endtt);
308
309 result = e_sexp_result_new (esexp, ESEXP_RES_INT);
310 result->value.number = ctx->occurrences_count;
311
312 return result;
313 }
314
315 static ESExpResult *
func_due_in_time_range(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)316 func_due_in_time_range (ESExp *esexp,
317 gint argc,
318 ESExpResult **argv,
319 gpointer data)
320 {
321 SearchContext *ctx = data;
322 time_t start, end;
323 ESExpResult *result;
324 ICalTimezone *zone;
325 ECalComponentDateTime *dt;
326 time_t due_t;
327 gboolean retval;
328
329 /* Check argument types */
330
331 if (argc != 2) {
332 e_sexp_fatal_error (
333 esexp, _("“%s” expects two arguments"),
334 "due-in-time-range");
335 return NULL;
336 }
337
338 if (argv[0]->type != ESEXP_RES_TIME) {
339 e_sexp_fatal_error (
340 esexp, _("“%s” expects the first "
341 "argument to be a time_t"),
342 "due-in-time-range");
343 return NULL;
344 }
345
346 start = argv[0]->value.time;
347
348 if (argv[1]->type != ESEXP_RES_TIME) {
349 e_sexp_fatal_error (
350 esexp, _("“%s” expects the second "
351 "argument to be a time_t"),
352 "due-in-time-range");
353 return NULL;
354 }
355
356 end = argv[1]->value.time;
357 dt = e_cal_component_get_due (ctx->comp);
358
359 if (dt && e_cal_component_datetime_get_value (dt)) {
360 zone = resolve_tzid (e_cal_component_datetime_get_tzid (dt), ctx, NULL, NULL);
361 if (zone)
362 due_t = i_cal_time_as_timet_with_zone (e_cal_component_datetime_get_value (dt), zone);
363 else
364 due_t = i_cal_time_as_timet (e_cal_component_datetime_get_value (dt));
365 } else {
366 due_t = (time_t) 0;
367 }
368
369 if (dt && e_cal_component_datetime_get_value (dt) && (due_t <= end && due_t >= start))
370 retval = TRUE;
371 else
372 retval = FALSE;
373
374 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
375 result->value.boolean = retval;
376
377 e_cal_component_datetime_free (dt);
378
379 return result;
380 }
381
382 /* Returns whether a list of ECalComponentText items matches the specified string */
383 static gboolean
matches_text_list(GSList * text_list,const gchar * str)384 matches_text_list (GSList *text_list,
385 const gchar *str)
386 {
387 GSList *l;
388 gboolean matches = FALSE;
389
390 for (l = text_list; l; l = l->next) {
391 ECalComponentText *text = l->data;
392
393 if (text && e_cal_component_text_get_value (text) &&
394 e_util_utf8_strstrcasedecomp (e_cal_component_text_get_value (text), str) != NULL) {
395 matches = TRUE;
396 break;
397 }
398 }
399
400 return matches;
401 }
402
403 /* Returns whether the comments in a component matches the specified string */
404 static gboolean
matches_comment(ECalComponent * comp,const gchar * str)405 matches_comment (ECalComponent *comp,
406 const gchar *str)
407 {
408 GSList *list;
409 gboolean matches;
410
411 list = e_cal_component_get_comments (comp);
412 matches = matches_text_list (list, str);
413 g_slist_free_full (list, e_cal_component_text_free);
414
415 return matches;
416 }
417
418 /* Returns whether the description in a component matches the specified string */
419 static gboolean
matches_description(ECalComponent * comp,const gchar * str)420 matches_description (ECalComponent *comp,
421 const gchar *str)
422 {
423 GSList *list;
424 gboolean matches;
425
426 list = e_cal_component_get_descriptions (comp);
427 matches = matches_text_list (list, str);
428 g_slist_free_full (list, e_cal_component_text_free);
429
430 return matches;
431 }
432
433 static gboolean
matches_attendee(ECalComponent * comp,const gchar * str)434 matches_attendee (ECalComponent *comp,
435 const gchar *str)
436 {
437 GSList *a_list = NULL, *l;
438 gboolean matches = FALSE;
439
440 a_list = e_cal_component_get_attendees (comp);
441
442 for (l = a_list; l; l = l->next) {
443 ECalComponentAttendee *att = l->data;
444
445 if (!att)
446 continue;
447
448 if ((e_cal_component_attendee_get_value (att) && e_util_utf8_strstrcasedecomp (e_cal_component_attendee_get_value (att), str)) ||
449 (e_cal_component_attendee_get_cn (att) && e_util_utf8_strstrcasedecomp (e_cal_component_attendee_get_cn (att), str))) {
450 matches = TRUE;
451 break;
452 }
453 }
454
455 g_slist_free_full (a_list, e_cal_component_attendee_free);
456
457 return matches;
458 }
459
460 static gboolean
matches_organizer(ECalComponent * comp,const gchar * str)461 matches_organizer (ECalComponent *comp,
462 const gchar *str)
463 {
464
465 ECalComponentOrganizer *org;
466 gboolean matches;
467
468 if (str && !*str)
469 return TRUE;
470
471 org = e_cal_component_get_organizer (comp);
472 if (!org)
473 return FALSE;
474
475 matches = (e_cal_component_organizer_get_value (org) && e_util_utf8_strstrcasedecomp (e_cal_component_organizer_get_value (org), str)) ||
476 (e_cal_component_organizer_get_cn (org) && e_util_utf8_strstrcasedecomp (e_cal_component_organizer_get_cn (org), str));
477
478 e_cal_component_organizer_free (org);
479
480 return matches;
481 }
482
483 static gboolean
matches_classification(ECalComponent * comp,const gchar * str)484 matches_classification (ECalComponent *comp,
485 const gchar *str)
486 {
487 ECalComponentClassification classification;
488 ECalComponentClassification classification1;
489
490 if (!*str)
491 return FALSE;
492
493 if (g_str_equal (str, "Public"))
494 classification1 = E_CAL_COMPONENT_CLASS_PUBLIC;
495 else if (g_str_equal (str, "Private"))
496 classification1 = E_CAL_COMPONENT_CLASS_PRIVATE;
497 else if (g_str_equal (str, "Confidential"))
498 classification1 = E_CAL_COMPONENT_CLASS_CONFIDENTIAL;
499 else
500 classification1 = E_CAL_COMPONENT_CLASS_UNKNOWN;
501
502 classification = e_cal_component_get_classification (comp);
503
504 return (classification == classification1 ? TRUE : FALSE);
505 }
506
507 /* Returns whether the summary in a component matches the specified string */
508 static gboolean
matches_summary(ECalComponent * comp,const gchar * str)509 matches_summary (ECalComponent *comp,
510 const gchar *str)
511 {
512 ECalComponentText *text;
513 gboolean matches;
514
515 if (!*str)
516 return TRUE;
517
518 text = e_cal_component_get_summary (comp);
519
520 matches = text && e_cal_component_text_get_value (text) &&
521 e_util_utf8_strstrcasedecomp (e_cal_component_text_get_value (text), str) != NULL;
522
523 e_cal_component_text_free (text);
524
525 return matches;
526 }
527
528 /* Returns whether the location in a component matches the specified string */
529 static gboolean
matches_location(ECalComponent * comp,const gchar * str)530 matches_location (ECalComponent *comp,
531 const gchar *str)
532 {
533 gchar *location;
534 gboolean matches;
535
536 location = e_cal_component_get_location (comp);
537
538 matches = location && e_util_utf8_strstrcasedecomp (location, str) != NULL;
539
540 g_free (location);
541
542 return matches;
543 }
544
545 /* Returns whether any text field in a component matches the specified string */
546 static gboolean
matches_any(ECalComponent * comp,const gchar * str)547 matches_any (ECalComponent *comp,
548 const gchar *str)
549 {
550 /* As an optimization, and to make life easier for the individual
551 * predicate functions, see if we are looking for the empty string right
552 * away.
553 */
554 if (strlen (str) == 0)
555 return TRUE;
556
557 return (matches_comment (comp, str)
558 || matches_description (comp, str)
559 || matches_summary (comp, str)
560 || matches_location (comp, str));
561 }
562
563 static gboolean
matches_priority(ECalComponent * comp,const gchar * pr)564 matches_priority (ECalComponent *comp ,const gchar *pr)
565 {
566 gboolean res = FALSE;
567 gint priority;
568
569 priority = e_cal_component_get_priority (comp);
570
571 if (priority == -1)
572 return g_str_equal (pr, "UNDEFINED");
573
574 if (g_str_equal (pr, "HIGH") && priority <= 4)
575 res = TRUE;
576 else if (g_str_equal (pr, "NORMAL") && priority == 5)
577 res = TRUE;
578 else if (g_str_equal (pr, "LOW") && priority > 5)
579 res = TRUE;
580
581 return res;
582 }
583
584 static gboolean
matches_status(ECalComponent * comp,const gchar * str)585 matches_status (ECalComponent *comp ,const gchar *str)
586 {
587 ICalPropertyStatus status;
588
589 if (!*str)
590 return FALSE;
591
592 status = e_cal_component_get_status (comp);
593
594 switch (status) {
595 case I_CAL_STATUS_NONE:
596 return g_str_equal (str, "NOT STARTED");
597 case I_CAL_STATUS_COMPLETED:
598 return g_str_equal (str, "COMPLETED");
599 case I_CAL_STATUS_CANCELLED:
600 return g_str_equal (str, "CANCELLED");
601 case I_CAL_STATUS_INPROCESS:
602 return g_str_equal (str, "IN PROGRESS");
603 case I_CAL_STATUS_NEEDSACTION:
604 return g_str_equal (str, "NEEDS ACTION");
605 case I_CAL_STATUS_TENTATIVE:
606 return g_str_equal (str, "TENTATIVE");
607 case I_CAL_STATUS_CONFIRMED:
608 return g_str_equal (str, "CONFIRMED");
609 case I_CAL_STATUS_DRAFT:
610 return g_str_equal (str, "DRAFT");
611 case I_CAL_STATUS_FINAL:
612 return g_str_equal (str, "FINAL");
613 case I_CAL_STATUS_SUBMITTED:
614 return g_str_equal (str, "SUBMITTED");
615 case I_CAL_STATUS_PENDING:
616 return g_str_equal (str, "PENDING");
617 case I_CAL_STATUS_FAILED:
618 return g_str_equal (str, "FAILED");
619 case I_CAL_STATUS_DELETED:
620 return g_str_equal (str, "DELETED");
621 case I_CAL_STATUS_X:
622 break;
623 }
624
625 return FALSE;
626 }
627
628 static ESExpResult *
func_has_attachment(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)629 func_has_attachment (ESExp *esexp,
630 gint argc,
631 ESExpResult **argv,
632 gpointer data)
633 {
634 SearchContext *ctx = data;
635 ESExpResult *result;
636
637 if (argc != 0) {
638 e_sexp_fatal_error (
639 esexp, _("“%s” expects no arguments"),
640 "has-attachments?");
641 return NULL;
642 }
643
644 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
645 result->value.boolean = e_cal_component_has_attachments (ctx->comp);
646
647 return result;
648 }
649
650 static ESExpResult *
func_percent_complete(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)651 func_percent_complete (ESExp *esexp,
652 gint argc,
653 ESExpResult **argv,
654 gpointer data)
655 {
656 SearchContext *ctx = data;
657 ESExpResult *result = NULL;
658 gint percent;
659
660 if (argc != 0) {
661 e_sexp_fatal_error (
662 esexp, _("“%s” expects no arguments"),
663 "percent-completed");
664 return NULL;
665 }
666
667 percent = e_cal_component_get_percent_complete (ctx->comp);
668
669 result = e_sexp_result_new (esexp, ESEXP_RES_INT);
670 result->value.number = percent;
671
672 return result;
673 }
674
675 /* (contains? FIELD STR)
676 *
677 * FIELD - string, name of field to match
678 * (any, comment, description, summary, location)
679 * STR - string, match string
680 *
681 * Returns a boolean indicating whether the specified field contains the
682 * specified string.
683 */
684 static ESExpResult *
func_contains(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)685 func_contains (ESExp *esexp,
686 gint argc,
687 ESExpResult **argv,
688 gpointer data)
689 {
690 SearchContext *ctx = data;
691 const gchar *field;
692 const gchar *str;
693 gboolean matches;
694 ESExpResult *result;
695
696 /* Check argument types */
697
698 if (argc != 2) {
699 e_sexp_fatal_error (
700 esexp, _("“%s” expects two arguments"),
701 "contains");
702 return NULL;
703 }
704
705 if (argv[0]->type != ESEXP_RES_STRING) {
706 e_sexp_fatal_error (
707 esexp, _("“%s” expects the first "
708 "argument to be a string"),
709 "contains");
710 return NULL;
711 }
712 field = argv[0]->value.string;
713
714 if (argv[1]->type != ESEXP_RES_STRING) {
715 e_sexp_fatal_error (
716 esexp, _("“%s” expects the second "
717 "argument to be a string"),
718 "contains");
719 return NULL;
720 }
721 str = argv[1]->value.string;
722
723 /* See if it matches */
724
725 if (strcmp (field, "any") == 0)
726 matches = matches_any (ctx->comp, str);
727 else if (strcmp (field, "comment") == 0)
728 matches = matches_comment (ctx->comp, str);
729 else if (strcmp (field, "description") == 0)
730 matches = matches_description (ctx->comp, str);
731 else if (strcmp (field, "summary") == 0)
732 matches = matches_summary (ctx->comp, str);
733 else if (strcmp (field, "location") == 0)
734 matches = matches_location (ctx->comp, str);
735 else if (strcmp (field, "attendee") == 0)
736 matches = matches_attendee (ctx->comp, str);
737 else if (strcmp (field, "organizer") == 0)
738 matches = matches_organizer (ctx->comp, str);
739 else if (strcmp (field, "classification") == 0)
740 matches = matches_classification (ctx->comp, str);
741 else if (strcmp (field, "status") == 0)
742 matches = matches_status (ctx->comp, str);
743 else if (strcmp (field, "priority") == 0)
744 matches = matches_priority (ctx->comp, str);
745 else {
746 e_sexp_fatal_error (
747 esexp, _("“%s” expects the first "
748 "argument to be either “any”, "
749 "“summary”, or “description”, or "
750 "“location”, or “attendee”, or "
751 "“organizer”, or “classification”"),
752 "contains");
753 return NULL;
754 }
755
756 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
757 result->value.boolean = matches;
758
759 return result;
760 }
761
762 static ESExpResult *
check_has_property(ESExp * esexp,ECalComponent * comp,ICalPropertyKind kind)763 check_has_property (ESExp *esexp,
764 ECalComponent *comp,
765 ICalPropertyKind kind)
766 {
767 ESExpResult *result;
768
769 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
770 result->value.boolean = e_cal_util_component_has_property (e_cal_component_get_icalcomponent (comp), kind);
771
772 return result;
773 }
774
775 /* (has-start?)
776 *
777 * A boolean value for components that have/don't have filled start date/time.
778 *
779 * Returns: whether the component has start date/time filled
780 */
781 static ESExpResult *
func_has_start(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)782 func_has_start (ESExp *esexp,
783 gint argc,
784 ESExpResult **argv,
785 gpointer data)
786 {
787 SearchContext *ctx = data;
788
789 if (argc != 0) {
790 e_sexp_fatal_error (esexp, _("“%s” expects no arguments"), "has-start");
791 return NULL;
792 }
793
794 return check_has_property (esexp, ctx->comp, I_CAL_DTSTART_PROPERTY);
795 }
796
797 /* (has-end?)
798 *
799 * A boolean value for components that have/don't have filled DTEND property.
800 *
801 * Returns: whether the component has DTEND filled
802 */
803 static ESExpResult *
func_has_end(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)804 func_has_end (ESExp *esexp,
805 gint argc,
806 ESExpResult **argv,
807 gpointer data)
808 {
809 SearchContext *ctx = data;
810
811 if (argc != 0) {
812 e_sexp_fatal_error (esexp, _("“%s” expects no arguments"), "has-end");
813 return NULL;
814 }
815
816 return check_has_property (esexp, ctx->comp, I_CAL_DTEND_PROPERTY);
817 }
818
819 /* (has-due?)
820 *
821 * A boolean value for components that have/don't have filled DUE property.
822 *
823 * Returns: whether the component has DUE filled
824 */
825 static ESExpResult *
func_has_due(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)826 func_has_due (ESExp *esexp,
827 gint argc,
828 ESExpResult **argv,
829 gpointer data)
830 {
831 SearchContext *ctx = data;
832
833 if (argc != 0) {
834 e_sexp_fatal_error (esexp, _("“%s” expects no arguments"), "has-due");
835 return NULL;
836 }
837
838 return check_has_property (esexp, ctx->comp, I_CAL_DUE_PROPERTY);
839 }
840
841
842 /* (has-duration?)
843 *
844 * A boolean value for components that have/don't have filled DURATION property.
845 *
846 * Returns: whether the component has DURATION filled
847 */
848 static ESExpResult *
func_has_duration(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)849 func_has_duration (ESExp *esexp,
850 gint argc,
851 ESExpResult **argv,
852 gpointer data)
853 {
854 SearchContext *ctx = data;
855
856 if (argc != 0) {
857 e_sexp_fatal_error (esexp, _("“%s” expects no arguments"), "has-duration");
858 return NULL;
859 }
860
861 return check_has_property (esexp, ctx->comp, I_CAL_DURATION_PROPERTY);
862 }
863
864 /* (has-alarms?)
865 *
866 * A boolean value for components that have/dont have alarms.
867 *
868 * Returns: a boolean indicating whether the component has alarms or not.
869 */
870 static ESExpResult *
func_has_alarms(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)871 func_has_alarms (ESExp *esexp,
872 gint argc,
873 ESExpResult **argv,
874 gpointer data)
875 {
876 SearchContext *ctx = data;
877 ESExpResult *result;
878
879 /* Check argument types */
880
881 if (argc != 0) {
882 e_sexp_fatal_error (
883 esexp, _("“%s” expects no arguments"),
884 "has-alarms");
885 return NULL;
886 }
887
888 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
889 result->value.boolean = e_cal_component_has_alarms (ctx->comp);
890
891 return result;
892 }
893
894 /* (has-alarms-in-range? START END)
895 *
896 * START - time_t, start of the time range
897 * END - time_t, end of the time range
898 *
899 * Returns: a boolean indicating whether the component has alarms in the given
900 * time range or not.
901 */
902 static ESExpResult *
func_has_alarms_in_range(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)903 func_has_alarms_in_range (ESExp *esexp,
904 gint argc,
905 ESExpResult **argv,
906 gpointer data)
907 {
908 time_t start, end;
909 ESExpResult *result;
910 ICalTimezone *default_zone;
911 ECalComponentAlarms *alarms;
912 ECalComponentAlarmAction omit[] = {-1};
913 SearchContext *ctx = data;
914
915 /* Check argument types */
916
917 if (argc != 2) {
918 e_sexp_fatal_error (
919 esexp, _("“%s” expects two arguments"),
920 "has-alarms-in-range");
921 return NULL;
922 }
923
924 if (argv[0]->type != ESEXP_RES_TIME) {
925 e_sexp_fatal_error (
926 esexp, _("“%s” expects the first "
927 "argument to be a time_t"),
928 "has-alarms-in-range");
929 return NULL;
930 }
931 start = argv[0]->value.time;
932
933 if (argv[1]->type != ESEXP_RES_TIME) {
934 e_sexp_fatal_error (
935 esexp, _("“%s” expects the second "
936 "argument to be a time_t"),
937 "has-alarms-in-range");
938 return NULL;
939 }
940 end = argv[1]->value.time;
941
942 /* See if the object has alarms in the given time range */
943 default_zone = i_cal_timezone_get_utc_timezone ();
944
945 alarms = e_cal_util_generate_alarms_for_comp (
946 ctx->comp, start, end,
947 omit, resolve_tzid,
948 ctx, default_zone);
949
950 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
951 if (alarms) {
952 result->value.boolean = TRUE;
953 e_cal_component_alarms_free (alarms);
954 } else
955 result->value.boolean = FALSE;
956
957 return result;
958 }
959
960 /* (has-categories? STR+)
961 * (has-categories? #f)
962 *
963 * STR - At least one string specifying a category
964 * Or you can specify a single #f (boolean false) value for components
965 * that have no categories assigned to them ("unfiled").
966 *
967 * Returns a boolean indicating whether the component has all the specified
968 * categories.
969 */
970 static ESExpResult *
func_has_categories(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)971 func_has_categories (ESExp *esexp,
972 gint argc,
973 ESExpResult **argv,
974 gpointer data)
975 {
976 SearchContext *ctx = data;
977 gboolean unfiled;
978 gint i;
979 GSList *categories;
980 gboolean matches;
981 ESExpResult *result;
982
983 /* Check argument types */
984
985 if (argc < 1) {
986 e_sexp_fatal_error (
987 esexp, _("“%s” expects at least one "
988 "argument"),
989 "has-categories");
990 return NULL;
991 }
992
993 if (argc == 1 && argv[0]->type == ESEXP_RES_BOOL)
994 unfiled = TRUE;
995 else
996 unfiled = FALSE;
997
998 if (!unfiled)
999 for (i = 0; i < argc; i++)
1000 if (argv[i]->type != ESEXP_RES_STRING) {
1001 e_sexp_fatal_error (
1002 esexp, _("“%s” expects "
1003 "all arguments to "
1004 "be strings or "
1005 "one and only one "
1006 "argument to be a "
1007 "boolean false "
1008 "(#f)"),
1009 "has-categories");
1010 return NULL;
1011 }
1012
1013 /* Search categories. First, if there are no categories we return
1014 * whether unfiled components are supposed to match.
1015 */
1016
1017 categories = e_cal_component_get_categories_list (ctx->comp);
1018 if (!categories) {
1019 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1020 result->value.boolean = unfiled;
1021
1022 return result;
1023 }
1024
1025 /* Otherwise, we *do* have categories but unfiled components were
1026 * requested, so this component does not match.
1027 */
1028 if (unfiled) {
1029 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1030 result->value.boolean = FALSE;
1031
1032 g_slist_free_full (categories, g_free);
1033
1034 return result;
1035 }
1036
1037 matches = TRUE;
1038
1039 for (i = 0; i < argc; i++) {
1040 const gchar *sought;
1041 GSList *l;
1042 gboolean has_category;
1043
1044 sought = argv[i]->value.string;
1045
1046 has_category = FALSE;
1047
1048 for (l = categories; l; l = l->next) {
1049 const gchar *category;
1050
1051 category = l->data;
1052
1053 if (strcmp (category, sought) == 0) {
1054 has_category = TRUE;
1055 break;
1056 }
1057 }
1058
1059 if (!has_category) {
1060 matches = FALSE;
1061 break;
1062 }
1063 }
1064
1065 g_slist_free_full (categories, g_free);
1066
1067 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1068 result->value.boolean = matches;
1069
1070 return result;
1071 }
1072
1073 /* (has-recurrences?)
1074 *
1075 * A boolean value for components that have/dont have recurrences.
1076 *
1077 * Returns: a boolean indicating whether the component has recurrences or not.
1078 */
1079 static ESExpResult *
func_has_recurrences(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1080 func_has_recurrences (ESExp *esexp,
1081 gint argc,
1082 ESExpResult **argv,
1083 gpointer data)
1084 {
1085 SearchContext *ctx = data;
1086 ESExpResult *result;
1087
1088 /* Check argument types */
1089
1090 if (argc != 0) {
1091 e_sexp_fatal_error (
1092 esexp, _("“%s” expects no arguments"),
1093 "has-recurrences");
1094 return NULL;
1095 }
1096
1097 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1098 result->value.boolean =
1099 e_cal_component_has_recurrences (ctx->comp) ||
1100 e_cal_component_is_instance (ctx->comp);
1101
1102 return result;
1103 }
1104
1105 /* (is-completed?)
1106 *
1107 * Returns a boolean indicating whether the component is completed (i.e. has
1108 * a COMPLETED property. This is really only useful for TODO components.
1109 */
1110 static ESExpResult *
func_is_completed(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1111 func_is_completed (ESExp *esexp,
1112 gint argc,
1113 ESExpResult **argv,
1114 gpointer data)
1115 {
1116 SearchContext *ctx = data;
1117 ESExpResult *result;
1118 ICalTime *itt;
1119 gboolean complete = FALSE;
1120
1121 /* Check argument types */
1122
1123 if (argc != 0) {
1124 e_sexp_fatal_error (
1125 esexp, _("“%s” expects no arguments"),
1126 "is-completed");
1127 return NULL;
1128 }
1129
1130 itt = e_cal_component_get_completed (ctx->comp);
1131 if (itt) {
1132 complete = TRUE;
1133 g_object_unref (itt);
1134 } else {
1135 ICalPropertyStatus status;
1136
1137 status = e_cal_component_get_status (ctx->comp);
1138
1139 complete = status == I_CAL_STATUS_COMPLETED;
1140 }
1141
1142 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1143 result->value.boolean = complete;
1144
1145 return result;
1146 }
1147
1148 /* (completed-before? TIME)
1149 *
1150 * TIME - time_t
1151 *
1152 * Returns a boolean indicating whether the component was completed on or
1153 * before the given time (i.e. it checks the COMPLETED property).
1154 * This is really only useful for TODO components.
1155 */
1156 static ESExpResult *
func_completed_before(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1157 func_completed_before (ESExp *esexp,
1158 gint argc,
1159 ESExpResult **argv,
1160 gpointer data)
1161 {
1162 SearchContext *ctx = data;
1163 ESExpResult *result;
1164 ICalTime *itt;
1165 ICalTimezone *zone;
1166 gboolean retval = FALSE;
1167 time_t before_time, completed_time;
1168
1169 /* Check argument types */
1170
1171 if (argc != 1) {
1172 e_sexp_fatal_error (
1173 esexp, _("“%s” expects one argument"),
1174 "completed-before");
1175 return NULL;
1176 }
1177
1178 if (argv[0]->type != ESEXP_RES_TIME) {
1179 e_sexp_fatal_error (
1180 esexp, _("“%s” expects the first "
1181 "argument to be a time_t"),
1182 "completed-before");
1183 return NULL;
1184 }
1185
1186 before_time = argv[0]->value.time;
1187
1188 itt = e_cal_component_get_completed (ctx->comp);
1189 if (itt) {
1190 /* COMPLETED must be in UTC. */
1191 zone = i_cal_timezone_get_utc_timezone ();
1192 completed_time = i_cal_time_as_timet_with_zone (itt, zone);
1193
1194 /* We want to return TRUE if before_time is after
1195 * completed_time. */
1196 if (difftime (before_time, completed_time) > 0) {
1197 retval = TRUE;
1198 }
1199
1200 g_object_unref (itt);
1201 }
1202
1203 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1204 result->value.boolean = retval;
1205
1206 return result;
1207 }
1208
1209 /* (starts-before? TIME)
1210 *
1211 * TIME - time_t
1212 *
1213 * Returns a boolean indicating whether the component has a start on or
1214 * before the given time (i.e. it checks the DTSTART property).
1215 */
1216 static ESExpResult*
func_starts_before(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1217 func_starts_before (ESExp *esexp,
1218 gint argc,
1219 ESExpResult **argv,
1220 gpointer data)
1221 {
1222 SearchContext *ctx = data;
1223 ESExpResult *result;
1224 ECalComponentDateTime *dt;
1225 ICalTimezone *zone;
1226 ICalTime *itt;
1227 gboolean retval = FALSE;
1228 time_t reference_time, start_time;
1229
1230 /* Check argument types */
1231
1232 if (argc != 1) {
1233 e_sexp_fatal_error (
1234 esexp, _("“%s” expects one argument"),
1235 "starts-before");
1236 return NULL;
1237 }
1238
1239 if (argv[0]->type != ESEXP_RES_TIME) {
1240 e_sexp_fatal_error (
1241 esexp, _("“%s” expects the first "
1242 "argument to be a time_t"),
1243 "starts-before");
1244 return NULL;
1245 }
1246
1247 reference_time = argv[0]->value.time;
1248
1249 dt = e_cal_component_get_dtstart (ctx->comp);
1250 if (dt) {
1251 itt = e_cal_component_datetime_get_value (dt);
1252
1253 if (itt) {
1254 /* DTSTART must be in UTC. */
1255 zone = i_cal_timezone_get_utc_timezone ();
1256 start_time = i_cal_time_as_timet_with_zone (itt, zone);
1257
1258 /* We want to return TRUE if start_time is before reference_time. */
1259 if (difftime (start_time, reference_time) <= 0) {
1260 retval = TRUE;
1261 }
1262 }
1263 e_cal_component_datetime_free (dt);
1264 }
1265
1266 result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
1267 result->value.boolean = retval;
1268
1269 return result;
1270 }
1271
1272 static void
cal_backend_sexp_finalize(GObject * object)1273 cal_backend_sexp_finalize (GObject *object)
1274 {
1275 ECalBackendSExpPrivate *priv;
1276
1277 priv = E_CAL_BACKEND_SEXP (object)->priv;
1278
1279 g_object_unref (priv->search_sexp);
1280 g_free (priv->text);
1281 g_rec_mutex_clear (&priv->search_context_lock);
1282
1283 /* Chain up to parent's finalize() method. */
1284 G_OBJECT_CLASS (e_cal_backend_sexp_parent_class)->finalize (object);
1285 }
1286
1287 static void
e_cal_backend_sexp_class_init(ECalBackendSExpClass * class)1288 e_cal_backend_sexp_class_init (ECalBackendSExpClass *class)
1289 {
1290 GObjectClass *object_class;
1291
1292 object_class = G_OBJECT_CLASS (class);
1293 object_class->finalize = cal_backend_sexp_finalize;
1294 }
1295
1296 static void
e_cal_backend_sexp_init(ECalBackendSExp * sexp)1297 e_cal_backend_sexp_init (ECalBackendSExp *sexp)
1298 {
1299 sexp->priv = e_cal_backend_sexp_get_instance_private (sexp);
1300
1301 g_rec_mutex_init (&sexp->priv->search_context_lock);
1302 }
1303
1304 /* 'builtin' functions */
1305 static struct {
1306 const gchar *name;
1307 ESExpFunc *func;
1308 gint type; /* set to 1 if a function can perform shortcut
1309 * evaluation, or doesn't execute everything,
1310 * 0 otherwise */
1311 } symbols[] = {
1312 /* Time-related functions */
1313 { "time-now", e_cal_backend_sexp_func_time_now, 0 },
1314 { "make-time", e_cal_backend_sexp_func_make_time, 0 },
1315 { "time-add-day", e_cal_backend_sexp_func_time_add_day, 0 },
1316 { "time-day-begin", e_cal_backend_sexp_func_time_day_begin, 0 },
1317 { "time-day-end", e_cal_backend_sexp_func_time_day_end, 0 },
1318 /* Component-related functions */
1319 { "uid?", func_uid, 0 },
1320 { "occur-in-time-range?", func_occur_in_time_range, 0 },
1321 { "due-in-time-range?", func_due_in_time_range, 0 },
1322 { "contains?", func_contains, 0 },
1323 { "has-start?", func_has_start, 0 },
1324 { "has-end?", func_has_end, 0 },
1325 { "has-due?", func_has_due, 0 },
1326 { "has-duration?", func_has_duration, 0 },
1327 { "has-alarms?", func_has_alarms, 0 },
1328 { "has-alarms-in-range?", func_has_alarms_in_range, 0 },
1329 { "has-recurrences?", func_has_recurrences, 0 },
1330 { "has-categories?", func_has_categories, 0 },
1331 { "is-completed?", func_is_completed, 0 },
1332 { "completed-before?", func_completed_before, 0 },
1333 { "has-attachments?", func_has_attachment, 0 },
1334 { "percent-complete?", func_percent_complete, 0 },
1335 { "occurrences-count?", func_occurrences_count, 0 },
1336 { "starts-before?", func_starts_before, 0 }
1337 };
1338
1339 /**
1340 * e_cal_backend_card_sexp_new:
1341 * @text: The expression to use.
1342 *
1343 * Creates a new #ECalBackendSExp from @text.
1344 *
1345 * Returns: a new #ECalBackendSExp
1346 */
1347 ECalBackendSExp *
e_cal_backend_sexp_new(const gchar * text)1348 e_cal_backend_sexp_new (const gchar *text)
1349 {
1350 ECalBackendSExp *sexp;
1351 gint ii;
1352
1353 g_return_val_if_fail (text != NULL, NULL);
1354
1355 sexp = g_object_new (E_TYPE_CAL_BACKEND_SEXP, NULL);
1356 sexp->priv->search_sexp = e_sexp_new ();
1357 sexp->priv->text = g_strdup (text);
1358
1359 for (ii = 0; ii < G_N_ELEMENTS (symbols); ii++) {
1360 if (symbols[ii].type == 1) {
1361 e_sexp_add_ifunction (
1362 sexp->priv->search_sexp, 0,
1363 symbols[ii].name,
1364 (ESExpIFunc *) symbols[ii].func,
1365 &(sexp->priv->search_context));
1366 } else {
1367 e_sexp_add_function (
1368 sexp->priv->search_sexp, 0,
1369 symbols[ii].name,
1370 symbols[ii].func,
1371 &(sexp->priv->search_context));
1372 }
1373 }
1374
1375 e_sexp_input_text (sexp->priv->search_sexp, text, strlen (text));
1376
1377 if (e_sexp_parse (sexp->priv->search_sexp) == -1) {
1378 g_object_unref (sexp);
1379 sexp = NULL;
1380 }
1381
1382 if (sexp != NULL) {
1383 SearchContext *ctx = &(sexp->priv->search_context);
1384
1385 ctx->expr_range_set = e_sexp_evaluate_occur_times (
1386 sexp->priv->search_sexp,
1387 &ctx->expr_range_start,
1388 &ctx->expr_range_end);
1389 }
1390
1391 return sexp;
1392 }
1393
1394 /**
1395 * e_cal_backend_sexp_text:
1396 * @sexp: An #ECalBackendSExp object.
1397 *
1398 * Retrieve the text expression for the given #ECalBackendSExp object.
1399 *
1400 * Returns: the text expression
1401 */
1402 const gchar *
e_cal_backend_sexp_text(ECalBackendSExp * sexp)1403 e_cal_backend_sexp_text (ECalBackendSExp *sexp)
1404 {
1405 g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), NULL);
1406
1407 return sexp->priv->text;
1408 }
1409
1410 /**
1411 * e_cal_backend_sexp_match_comp:
1412 * @sexp: An #ESExp object.
1413 * @comp: Component to match against the expression.
1414 * @cache: an #ETimezoneCache
1415 *
1416 * Checks if @comp matches @sexp.
1417 *
1418 * Returns: %TRUE if the component matches, %FALSE otherwise
1419 */
1420 gboolean
e_cal_backend_sexp_match_comp(ECalBackendSExp * sexp,ECalComponent * comp,ETimezoneCache * cache)1421 e_cal_backend_sexp_match_comp (ECalBackendSExp *sexp,
1422 ECalComponent *comp,
1423 ETimezoneCache *cache)
1424 {
1425 ESExpResult *r;
1426 gboolean retval;
1427
1428 g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), FALSE);
1429 g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
1430 g_return_val_if_fail (E_IS_TIMEZONE_CACHE (cache), FALSE);
1431
1432 e_cal_backend_sexp_lock (sexp);
1433
1434 sexp->priv->search_context.comp = g_object_ref (comp);
1435 sexp->priv->search_context.cache = g_object_ref (cache);
1436
1437 r = e_sexp_eval (sexp->priv->search_sexp);
1438
1439 retval = (r && r->type == ESEXP_RES_BOOL && r->value.boolean);
1440
1441 g_object_unref (sexp->priv->search_context.comp);
1442 g_object_unref (sexp->priv->search_context.cache);
1443
1444 e_sexp_result_free (sexp->priv->search_sexp, r);
1445
1446 e_cal_backend_sexp_unlock (sexp);
1447
1448 return retval;
1449 }
1450
1451 /**
1452 * e_cal_backend_sexp_match_object:
1453 * @sexp: An #ESExp object.
1454 * @object: An iCalendar string.
1455 * @cache: an #ETimezoneCache
1456 *
1457 * Checks if @object matches @sexp.
1458 *
1459 * Returns: %TRUE if the object matches, %FALSE otherwise
1460 */
1461 gboolean
e_cal_backend_sexp_match_object(ECalBackendSExp * sexp,const gchar * object,ETimezoneCache * cache)1462 e_cal_backend_sexp_match_object (ECalBackendSExp *sexp,
1463 const gchar *object,
1464 ETimezoneCache *cache)
1465 {
1466 ECalComponent *comp;
1467 gboolean retval;
1468
1469 g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), FALSE);
1470 g_return_val_if_fail (object != NULL, FALSE);
1471 g_return_val_if_fail (E_IS_TIMEZONE_CACHE (cache), FALSE);
1472
1473 comp = e_cal_component_new_from_string (object);
1474 if (!comp)
1475 return FALSE;
1476
1477 retval = e_cal_backend_sexp_match_comp (sexp, comp, cache);
1478
1479 g_object_unref (comp);
1480
1481 return retval;
1482 }
1483
1484 /**
1485 * e_cal_backend_sexp_lock:
1486 * @sexp: an #ECalBackendSExp
1487 *
1488 * Locks the @sexp. Other threads cannot use it until
1489 * it's unlocked with e_cal_backend_sexp_unlock().
1490 *
1491 * Since: 3.34
1492 **/
1493 void
e_cal_backend_sexp_lock(ECalBackendSExp * sexp)1494 e_cal_backend_sexp_lock (ECalBackendSExp *sexp)
1495 {
1496 g_return_if_fail (E_IS_CAL_BACKEND_SEXP (sexp));
1497
1498 g_rec_mutex_lock (&sexp->priv->search_context_lock);
1499 }
1500
1501 /**
1502 * e_cal_backend_sexp_unlock:
1503 * @sexp: an #ECalBackendSExp
1504 *
1505 * Unlocks the @sexp, previously locked by e_cal_backend_sexp_lock().
1506 *
1507 * Since: 3.34
1508 **/
1509 void
e_cal_backend_sexp_unlock(ECalBackendSExp * sexp)1510 e_cal_backend_sexp_unlock (ECalBackendSExp *sexp)
1511 {
1512 g_return_if_fail (E_IS_CAL_BACKEND_SEXP (sexp));
1513
1514 g_rec_mutex_unlock (&sexp->priv->search_context_lock);
1515 }
1516
1517 /**
1518 * e_cal_backend_sexp_func_time_now: (skip)
1519 * @esexp: An #ESExp object.
1520 * @argc: Number of arguments.
1521 * @argv: The arguments.
1522 * @data: Closure data.
1523 *
1524 * Processes the (time-now) sexp expression.
1525 *
1526 * Returns: The result of the function.
1527 */
1528 ESExpResult *
e_cal_backend_sexp_func_time_now(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1529 e_cal_backend_sexp_func_time_now (ESExp *esexp,
1530 gint argc,
1531 ESExpResult **argv,
1532 gpointer data)
1533 {
1534 ESExpResult *result;
1535
1536 g_return_val_if_fail (esexp != NULL, NULL);
1537
1538 if (argc != 0) {
1539 e_sexp_fatal_error (
1540 esexp, _("“%s” expects no arguments"),
1541 "time-now");
1542 return NULL;
1543 }
1544
1545 result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
1546 result->value.time = time (NULL);
1547
1548 return result;
1549 }
1550
1551 /**
1552 * e_cal_backend_sexp_func_make_time: (skip)
1553 * @esexp: An #ESExp object.
1554 * @argc: Number of arguments.
1555 * @argv: The arguments.
1556 * @data: Closure data.
1557 *
1558 * (make-time ISODATE)
1559 * ISODATE - string, ISO 8601 date/time representation
1560 *
1561 * Constructs a time_t value for the specified date.
1562 *
1563 * Returns: The result of the function.
1564 */
1565 ESExpResult *
e_cal_backend_sexp_func_make_time(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1566 e_cal_backend_sexp_func_make_time (ESExp *esexp,
1567 gint argc,
1568 ESExpResult **argv,
1569 gpointer data)
1570 {
1571 const gchar *str;
1572 time_t t;
1573 ESExpResult *result;
1574
1575 g_return_val_if_fail (esexp != NULL, NULL);
1576
1577 if (argc != 1) {
1578 e_sexp_fatal_error (
1579 esexp, _("“%s” expects one argument"),
1580 "make-time");
1581 return NULL;
1582 }
1583
1584 if (argv[0]->type != ESEXP_RES_STRING) {
1585 e_sexp_fatal_error (
1586 esexp, _("“%s” expects the first "
1587 "argument to be a string"),
1588 "make-time");
1589 return NULL;
1590 }
1591 str = argv[0]->value.string;
1592 if (!str || !*str) {
1593 e_sexp_fatal_error (
1594 esexp, _("“%s” expects the first "
1595 "argument to be a string"),
1596 "make-time");
1597 return NULL;
1598 }
1599
1600 t = time_from_isodate (str);
1601 if (t == -1) {
1602 e_sexp_fatal_error (
1603 esexp, _("“%s” expects the first "
1604 "argument to be an ISO 8601 "
1605 "date/time string"),
1606 "make-time");
1607 return NULL;
1608 }
1609
1610 result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
1611 result->value.time = t;
1612
1613 return result;
1614 }
1615
1616 /**
1617 * e_cal_backend_sexp_func_time_add_day: (skip)
1618 * @esexp: An #ESExp object.
1619 * @argc: Number of arguments.
1620 * @argv: The arguments.
1621 * @data: Closure data.
1622 *
1623 * (time-add-day TIME N)
1624 * TIME - time_t, base time
1625 * N - int, number of days to add
1626 *
1627 * Adds the specified number of days to a time value.
1628 *
1629 * FIXME: TIMEZONES - need to use a timezone or daylight saving changes will
1630 * make the result incorrect.
1631 *
1632 * Returns: The result of the function.
1633 */
1634 ESExpResult *
e_cal_backend_sexp_func_time_add_day(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1635 e_cal_backend_sexp_func_time_add_day (ESExp *esexp,
1636 gint argc,
1637 ESExpResult **argv,
1638 gpointer data)
1639 {
1640 ESExpResult *result;
1641 time_t t;
1642 gint n;
1643
1644 g_return_val_if_fail (esexp != NULL, NULL);
1645
1646 if (argc != 2) {
1647 e_sexp_fatal_error (
1648 esexp, _("“%s” expects two arguments"),
1649 "time-add-day");
1650 return NULL;
1651 }
1652
1653 if (argv[0]->type != ESEXP_RES_TIME) {
1654 e_sexp_fatal_error (
1655 esexp, _("“%s” expects the first "
1656 "argument to be a time_t"),
1657 "time-add-day");
1658 return NULL;
1659 }
1660 t = argv[0]->value.time;
1661
1662 if (argv[1]->type != ESEXP_RES_INT) {
1663 e_sexp_fatal_error (
1664 esexp, _("“%s” expects the second "
1665 "argument to be an integer"),
1666 "time-add-day");
1667 return NULL;
1668 }
1669 n = argv[1]->value.number;
1670
1671 result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
1672 result->value.time = time_add_day (t, n);
1673
1674 return result;
1675 }
1676
1677 /**
1678 * e_cal_backend_sexp_func_time_day_begin: (skip)
1679 * @esexp: An #ESExp object.
1680 * @argc: Number of arguments.
1681 * @argv: The arguments.
1682 * @data: Closure data.
1683 *
1684 * (time-day-begin TIME)
1685 * TIME - time_t, base time
1686 *
1687 * Returns the start of the day, according to the local time.
1688 *
1689 * FIXME: TIMEZONES - this uses the current Unix timezone.
1690 *
1691 * Returns: The result of the function.
1692 */
1693 ESExpResult *
e_cal_backend_sexp_func_time_day_begin(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1694 e_cal_backend_sexp_func_time_day_begin (ESExp *esexp,
1695 gint argc,
1696 ESExpResult **argv,
1697 gpointer data)
1698 {
1699 time_t t;
1700 ESExpResult *result;
1701
1702 g_return_val_if_fail (esexp != NULL, NULL);
1703
1704 if (argc != 1) {
1705 e_sexp_fatal_error (
1706 esexp, _("“%s” expects one argument"),
1707 "time-day-begin");
1708 return NULL;
1709 }
1710
1711 if (argv[0]->type != ESEXP_RES_TIME) {
1712 e_sexp_fatal_error (
1713 esexp, _("“%s” expects the first "
1714 "argument to be a time_t"),
1715 "time-day-begin");
1716 return NULL;
1717 }
1718 t = argv[0]->value.time;
1719
1720 result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
1721 result->value.time = time_day_begin (t);
1722
1723 return result;
1724 }
1725
1726 /**
1727 * e_cal_backend_sexp_func_time_day_end: (skip)
1728 * @esexp: An #ESExp object.
1729 * @argc: Number of arguments.
1730 * @argv: The arguments.
1731 * @data: Closure data.
1732 *
1733 * (time-day-end TIME)
1734 * TIME - time_t, base time
1735 *
1736 * Returns the end of the day, according to the local time.
1737 *
1738 * FIXME: TIMEZONES - this uses the current Unix timezone.
1739 *
1740 * Returns: The result of the function.
1741 */
1742 ESExpResult *
e_cal_backend_sexp_func_time_day_end(ESExp * esexp,gint argc,ESExpResult ** argv,gpointer data)1743 e_cal_backend_sexp_func_time_day_end (ESExp *esexp,
1744 gint argc,
1745 ESExpResult **argv,
1746 gpointer data)
1747 {
1748 time_t t;
1749 ESExpResult *result;
1750
1751 g_return_val_if_fail (esexp != NULL, NULL);
1752
1753 if (argc != 1) {
1754 e_sexp_fatal_error (
1755 esexp, _("“%s” expects one argument"),
1756 "time-day-end");
1757 return NULL;
1758 }
1759
1760 if (argv[0]->type != ESEXP_RES_TIME) {
1761 e_sexp_fatal_error (
1762 esexp, _("“%s” expects the first "
1763 "argument to be a time_t"),
1764 "time-day-end");
1765 return NULL;
1766 }
1767 t = argv[0]->value.time;
1768
1769 result = e_sexp_result_new (esexp, ESEXP_RES_TIME);
1770 result->value.time = time_day_end (t);
1771
1772 return result;
1773 }
1774
1775 /**
1776 * e_cal_backend_sexp_evaluate_occur_times:
1777 * @sexp: An #ESExp object.
1778 * @start: Start of the time window will be stored here.
1779 * @end: End of the time window will be stored here.
1780 *
1781 * Determines biggest time window given by expressions "occur-in-range" in sexp.
1782 *
1783 * Returns: %TRUE on success, %FALSE otherwise
1784 *
1785 * Since: 2.32
1786 */
1787 gboolean
e_cal_backend_sexp_evaluate_occur_times(ECalBackendSExp * sexp,time_t * start,time_t * end)1788 e_cal_backend_sexp_evaluate_occur_times (ECalBackendSExp *sexp,
1789 time_t *start,
1790 time_t *end)
1791 {
1792 g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), FALSE);
1793 g_return_val_if_fail (start != NULL, FALSE);
1794 g_return_val_if_fail (end != NULL, FALSE);
1795
1796 if (!sexp->priv->search_context.expr_range_set)
1797 return FALSE;
1798
1799 *start = sexp->priv->search_context.expr_range_start;
1800 *end = sexp->priv->search_context.expr_range_end;
1801
1802 return TRUE;
1803 }
1804
1805