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