1 /* Evolution calendar utilities and types
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  * Authors: Federico Mena-Quintero <federico@ximian.com>
18  */
19 
20 #include "evolution-data-server-config.h"
21 
22 #include <stdlib.h>
23 #include <string.h>
24 #include <glib/gstdio.h>
25 #include <glib/gi18n-lib.h>
26 
27 #include <libedataserver/libedataserver.h>
28 
29 #include "e-cal-check-timezones.h"
30 #include "e-cal-client.h"
31 #include "e-cal-system-timezone.h"
32 #include "e-cal-recur.h"
33 
34 #include "e-cal-util.h"
35 
36 #define _TIME_MIN	((time_t) 0)		/* Min valid time_t	*/
37 #define _TIME_MAX	((time_t) INT_MAX)
38 
39 /**
40  * e_cal_util_new_top_level:
41  *
42  * Creates a new VCALENDAR component. Free it with g_object_unref(),
43  * when no longer needed.
44  *
45  * Returns: (transfer full): the newly created top level component.
46  */
47 ICalComponent *
e_cal_util_new_top_level(void)48 e_cal_util_new_top_level (void)
49 {
50 	ICalComponent *icalcomp;
51 	ICalProperty *prop;
52 
53 	icalcomp = i_cal_component_new (I_CAL_VCALENDAR_COMPONENT);
54 
55 	/* RFC 2445, section 4.7.1 */
56 	prop = i_cal_property_new_calscale ("GREGORIAN");
57 	i_cal_component_take_property (icalcomp, prop);
58 
59        /* RFC 2445, section 4.7.3 */
60 	prop = i_cal_property_new_prodid ("-//Ximian//NONSGML Evolution Calendar//EN");
61 	i_cal_component_take_property (icalcomp, prop);
62 
63 	/* RFC 2445, section 4.7.4.  This is the iCalendar spec version, *NOT*
64 	 * the product version!  Do not change this!
65 	 */
66 	prop = i_cal_property_new_version ("2.0");
67 	i_cal_component_take_property (icalcomp, prop);
68 
69 	return icalcomp;
70 }
71 
72 /**
73  * e_cal_util_new_component:
74  * @kind: Kind of the component to create, as #ICalComponentKind.
75  *
76  * Creates a new #ICalComponent of the specified kind. Free it
77  * with g_object_unref(), when no longer needed.
78  *
79  * Returns: (transfer full): the newly created component.
80  */
81 ICalComponent *
e_cal_util_new_component(ICalComponentKind kind)82 e_cal_util_new_component (ICalComponentKind kind)
83 {
84 	ICalComponent *icalcomp;
85 	ICalTime *dtstamp;
86 	gchar *uid;
87 
88 	icalcomp = i_cal_component_new (kind);
89 	uid = e_util_generate_uid ();
90 	i_cal_component_set_uid (icalcomp, uid);
91 	g_free (uid);
92 	dtstamp = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
93 	i_cal_component_set_dtstamp (icalcomp, dtstamp);
94 	g_object_unref (dtstamp);
95 
96 	return icalcomp;
97 }
98 
99 /**
100  * e_cal_util_copy_timezone:
101  * @zone: an ICalTimezone
102  *
103  * Copies the @zone together with its inner component and
104  * returns it as a new #ICalTimezone object. Free it with
105  * g_object_unref(), when no longer needed.
106  *
107  * Returns: (transfer full): a copy of the @zone
108  *
109  * Since: 3.34
110  **/
111 ICalTimezone *
e_cal_util_copy_timezone(const ICalTimezone * zone)112 e_cal_util_copy_timezone (const ICalTimezone *zone)
113 {
114 	ICalComponent *comp;
115 	ICalTimezone *zone_copy;
116 
117 	g_return_val_if_fail (zone != NULL, NULL);
118 
119 	zone_copy = i_cal_timezone_copy (zone);
120 	if (!zone_copy)
121 		return NULL;
122 
123 	/* If the original component is one of the built-in, then libcal
124 	   loads it during the i_cal_timezone_get_component() call and
125 	   assigns a component to it. */
126 	comp = i_cal_timezone_get_component (zone_copy);
127 	if (comp) {
128 		g_object_unref (comp);
129 		return zone_copy;
130 	}
131 
132 	comp = i_cal_timezone_get_component (zone);
133 	if (comp) {
134 		ICalComponent *comp_copy;
135 
136 		comp_copy = i_cal_component_clone (comp);
137 		if (!i_cal_timezone_set_component (zone_copy, comp_copy))
138 			g_clear_object (&zone_copy);
139 		g_object_unref (comp_copy);
140 		g_object_unref (comp);
141 	}
142 
143 	return zone_copy;
144 }
145 
146 static gchar *
read_line(const gchar * string)147 read_line (const gchar *string)
148 {
149 	GString *line_str = NULL;
150 
151 	for (; *string; string++) {
152 		if (!line_str)
153 			line_str = g_string_new ("");
154 
155 		g_string_append_c (line_str, *string);
156 		if (*string == '\n')
157 			break;
158 	}
159 
160 	return g_string_free (line_str, FALSE);
161 }
162 
163 /**
164  * e_cal_util_parse_ics_string:
165  * @string: iCalendar string to be parsed.
166  *
167  * Parses an iCalendar string and returns a new #ICalComponent representing
168  * that string. Note that this function deals with multiple VCALENDAR's in the
169  * string, something that Mozilla used to do and which libical does not
170  * support.
171  *
172  * Free the returned non-NULL component with g_object_unref(), when no longer needed.
173  *
174  * Returns: (transfer full) (nullable): a newly created #ICalComponent, or %NULL,
175  *    if the string isn't a valid iCalendar string.
176  */
177 ICalComponent *
e_cal_util_parse_ics_string(const gchar * string)178 e_cal_util_parse_ics_string (const gchar *string)
179 {
180 	GString *comp_str = NULL;
181 	gchar *s;
182 	ICalComponent *icalcomp = NULL;
183 
184 	g_return_val_if_fail (string != NULL, NULL);
185 
186 	/* Split string into separated VCALENDAR's, if more than one */
187 	s = g_strstr_len (string, strlen (string), "BEGIN:VCALENDAR");
188 
189 	if (s == NULL)
190 		return i_cal_parser_parse_string (string);
191 
192 	while (*s != '\0') {
193 		gchar *line = read_line (s);
194 
195 		if (!comp_str)
196 			comp_str = g_string_new (line);
197 		else
198 			g_string_append (comp_str, line);
199 
200 		if (strncmp (line, "END:VCALENDAR", 13) == 0) {
201 			ICalComponent *tmp;
202 
203 			tmp = i_cal_parser_parse_string (comp_str->str);
204 			if (tmp && i_cal_component_isa (tmp) == I_CAL_VCALENDAR_COMPONENT) {
205 				if (icalcomp) {
206 					i_cal_component_merge_component (icalcomp, tmp);
207 					g_object_unref (tmp);
208 				} else
209 					icalcomp = tmp;
210 			} else {
211 				g_warning (
212 					"Could not merge the components, "
213 					"the component is either invalid "
214 					"or not a toplevel component \n");
215 			}
216 
217 			g_string_free (comp_str, TRUE);
218 			comp_str = NULL;
219 		}
220 
221 		s += strlen (line);
222 
223 		g_free (line);
224 	}
225 
226 	if (comp_str)
227 		g_string_free (comp_str, TRUE);
228 
229 	return icalcomp;
230 }
231 
232 struct ics_file {
233 	FILE *file;
234 	gboolean bof;
235 };
236 
237 static gchar *
get_line_fn(gchar * buf,gsize size,gpointer user_data)238 get_line_fn (gchar *buf,
239 	     gsize size,
240 	     gpointer user_data)
241 {
242 	struct ics_file *fl = user_data;
243 
244 	/* Skip the UTF-8 marker at the beginning of the file */
245 	if (fl->bof) {
246 		gchar *orig_buf = buf;
247 		gchar tmp[4];
248 
249 		fl->bof = FALSE;
250 
251 		if (fread (tmp, sizeof (gchar), 3, fl->file) != 3 || feof (fl->file))
252 			return NULL;
253 
254 		if (((guchar) tmp[0]) != 0xEF ||
255 		    ((guchar) tmp[1]) != 0xBB ||
256 		    ((guchar) tmp[2]) != 0xBF) {
257 			if (size <= 3)
258 				return NULL;
259 
260 			buf[0] = tmp[0];
261 			buf[1] = tmp[1];
262 			buf[2] = tmp[2];
263 			buf += 3;
264 			size -= 3;
265 		}
266 
267 		if (!fgets (buf, size, fl->file))
268 			return NULL;
269 
270 		return orig_buf;
271 	}
272 
273 	return fgets (buf, size, fl->file);
274 }
275 
276 /**
277  * e_cal_util_parse_ics_file:
278  * @filename: Name of the file to be parsed.
279  *
280  * Parses the given file, and, if it contains a valid iCalendar object,
281  * parse it and return a new #ICalComponent.
282  *
283  * Free the returned non-NULL component with g_object_unref(), when no longer needed.
284  *
285  * Returns: (transfer full) (nullable): a newly created #ICalComponent, or %NULL,
286  *    if the file doesn't contain a valid iCalendar object.
287  */
288 ICalComponent *
e_cal_util_parse_ics_file(const gchar * filename)289 e_cal_util_parse_ics_file (const gchar *filename)
290 {
291 	ICalParser *parser;
292 	ICalComponent *icalcomp;
293 	struct ics_file fl;
294 
295 	fl.file = g_fopen (filename, "rb");
296 	if (!fl.file)
297 		return NULL;
298 
299 	fl.bof = TRUE;
300 
301 	parser = i_cal_parser_new ();
302 
303 	icalcomp = i_cal_parser_parse (parser, get_line_fn, &fl);
304 	g_object_unref (parser);
305 	fclose (fl.file);
306 
307 	return icalcomp;
308 }
309 
310 /* Computes the range of time in which recurrences should be generated for a
311  * component in order to compute alarm trigger times.
312  */
313 static void
compute_alarm_range(ECalComponent * comp,GSList * alarm_uids,time_t start,time_t end,time_t * alarm_start,time_t * alarm_end)314 compute_alarm_range (ECalComponent *comp,
315                      GSList *alarm_uids,
316                      time_t start,
317                      time_t end,
318                      time_t *alarm_start,
319                      time_t *alarm_end)
320 {
321 	GSList *link;
322 	time_t repeat_time;
323 
324 	*alarm_start = start;
325 	*alarm_end = end;
326 
327 	repeat_time = 0;
328 
329 	for (link = alarm_uids; link; link = g_slist_next (link)) {
330 		const gchar *auid;
331 		ECalComponentAlarm *alarm;
332 		ECalComponentAlarmTrigger *trigger;
333 		ICalDuration *dur;
334 		time_t dur_time;
335 		ECalComponentAlarmRepeat *repeat;
336 
337 		auid = link->data;
338 		alarm = e_cal_component_get_alarm (comp, auid);
339 		g_return_if_fail (alarm != NULL);
340 
341 		trigger = e_cal_component_alarm_get_trigger (alarm);
342 		repeat = e_cal_component_alarm_get_repeat (alarm);
343 
344 		if (!trigger) {
345 			e_cal_component_alarm_free (alarm);
346 			continue;
347 		}
348 
349 		switch (e_cal_component_alarm_trigger_get_kind (trigger)) {
350 		case E_CAL_COMPONENT_ALARM_TRIGGER_NONE:
351 		case E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE:
352 			break;
353 
354 		case E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START:
355 		case E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END:
356 			dur = e_cal_component_alarm_trigger_get_duration (trigger);
357 			dur_time = i_cal_duration_as_int (dur);
358 
359 			if (repeat && e_cal_component_alarm_repeat_get_repetitions (repeat) != 0) {
360 				gint rdur;
361 
362 				rdur = e_cal_component_alarm_repeat_get_repetitions (repeat) *
363 					e_cal_component_alarm_repeat_get_interval_seconds (repeat);
364 				repeat_time = MAX (repeat_time, rdur);
365 			}
366 
367 			if (i_cal_duration_is_neg (dur))
368 				/* If the duration is negative then dur_time
369 				 * will be negative as well; that is why we
370 				 * subtract to expand the range.
371 				 */
372 				*alarm_end = MAX (*alarm_end, end - dur_time);
373 			else
374 				*alarm_start = MIN (*alarm_start, start - dur_time);
375 
376 			break;
377 		}
378 
379 		e_cal_component_alarm_free (alarm);
380 	}
381 
382 	*alarm_start -= repeat_time;
383 	g_warn_if_fail (*alarm_start <= *alarm_end);
384 }
385 
386 /* Closure data to generate alarm occurrences */
387 struct alarm_occurrence_data {
388 	ECalComponent *comp;
389 
390 	/* These are the info we have */
391 	GSList *alarm_uids; /* gchar * */
392 	time_t start;
393 	time_t end;
394 	ECalComponentAlarmAction *omit;
395 
396 	/* This is what we compute */
397 	GSList *triggers; /* ECalComponentAlarmInstance * */
398 	gint n_triggers;
399 };
400 
401 static void
add_trigger(struct alarm_occurrence_data * aod,const gchar * auid,const gchar * rid,time_t instance_time,time_t occur_start,time_t occur_end)402 add_trigger (struct alarm_occurrence_data *aod,
403              const gchar *auid,
404 	     const gchar *rid,
405              time_t instance_time,
406              time_t occur_start,
407              time_t occur_end)
408 {
409 	ECalComponentAlarmInstance *instance;
410 
411 	instance = e_cal_component_alarm_instance_new (auid, instance_time, occur_start, occur_end);
412 
413 	if (rid && *rid)
414 		e_cal_component_alarm_instance_set_rid (instance, rid);
415 
416 	aod->triggers = g_slist_prepend (aod->triggers, instance);
417 	aod->n_triggers++;
418 }
419 
420 /* Callback used from cal_recur_generate_instances(); generates triggers for all
421  * of a component's RELATIVE alarms.
422  */
423 static gboolean
add_alarm_occurrences_cb(ICalComponent * icalcomp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)424 add_alarm_occurrences_cb (ICalComponent *icalcomp,
425 			  ICalTime *instance_start,
426 			  ICalTime *instance_end,
427 			  gpointer user_data,
428 			  GCancellable *cancellable,
429 			  GError **error)
430 {
431 	struct alarm_occurrence_data *aod;
432 	time_t start, end;
433 	GSList *link;
434 	gchar *rid;
435 
436 	aod = user_data;
437 	start = i_cal_time_as_timet_with_zone (instance_start, i_cal_time_get_timezone (instance_start));
438 	end = i_cal_time_as_timet_with_zone (instance_end, i_cal_time_get_timezone (instance_end));
439 	rid = e_cal_util_component_get_recurid_as_string (icalcomp);
440 
441 	if (!rid || !*rid) {
442 		g_clear_pointer (&rid, g_free);
443 		rid = i_cal_time_as_ical_string (instance_start);
444 	}
445 
446 	for (link = aod->alarm_uids; link; link = g_slist_next (link)) {
447 		const gchar *auid;
448 		ECalComponentAlarm *alarm;
449 		ECalComponentAlarmAction action;
450 		ECalComponentAlarmTrigger *trigger;
451 		ECalComponentAlarmRepeat *repeat;
452 		ICalDuration *dur;
453 		time_t dur_time;
454 		time_t occur_time, trigger_time;
455 		gint i;
456 
457 		auid = link->data;
458 		alarm = e_cal_component_get_alarm (aod->comp, auid);
459 		g_return_val_if_fail (alarm != NULL, FALSE);
460 
461 		action = e_cal_component_alarm_get_action (alarm);
462 		trigger = e_cal_component_alarm_get_trigger (alarm);
463 		repeat = e_cal_component_alarm_get_repeat (alarm);
464 
465 		if (!trigger) {
466 			e_cal_component_alarm_free (alarm);
467 			continue;
468 		}
469 
470 		for (i = 0; aod->omit[i] != -1; i++) {
471 			if (aod->omit[i] == action)
472 				break;
473 		}
474 
475 		if (aod->omit[i] != -1) {
476 			e_cal_component_alarm_free (alarm);
477 			continue;
478 		}
479 
480 		if (e_cal_component_alarm_trigger_get_kind (trigger) != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START &&
481 		    e_cal_component_alarm_trigger_get_kind (trigger) != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END) {
482 			e_cal_component_alarm_free (alarm);
483 			continue;
484 		}
485 
486 		dur = e_cal_component_alarm_trigger_get_duration (trigger);
487 		dur_time = i_cal_duration_as_int (dur);
488 
489 		if (e_cal_component_alarm_trigger_get_kind (trigger) == E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START)
490 			occur_time = start;
491 		else
492 			occur_time = end;
493 
494 		/* If dur->is_neg is true then dur_time will already be
495 		 * negative.  So we do not need to test for dur->is_neg here; we
496 		 * can simply add the dur_time value to the occur_time and get
497 		 * the correct result.
498 		 */
499 
500 		trigger_time = occur_time + dur_time;
501 
502 		/* Add repeating alarms */
503 
504 		if (repeat && e_cal_component_alarm_repeat_get_repetitions (repeat) != 0) {
505 			gint ii, repetitions;
506 			time_t repeat_time;
507 
508 			repeat_time = e_cal_component_alarm_repeat_get_interval_seconds (repeat);
509 			repetitions = e_cal_component_alarm_repeat_get_repetitions (repeat);
510 
511 			for (ii = 0; ii < repetitions; ii++) {
512 				time_t t;
513 
514 				t = trigger_time + (ii + 1) * repeat_time;
515 
516 				if (t >= aod->start && t < aod->end)
517 					add_trigger (aod, auid, rid, t, start, end);
518 			}
519 		}
520 
521 		/* Add the trigger itself */
522 
523 		if (trigger_time >= aod->start && trigger_time < aod->end)
524 			add_trigger (aod, auid, rid, trigger_time, start, end);
525 
526 		e_cal_component_alarm_free (alarm);
527 	}
528 
529 	g_free (rid);
530 
531 	return TRUE;
532 }
533 
534 /* Generates the absolute triggers for a component */
535 static void
generate_absolute_triggers(ECalComponent * comp,struct alarm_occurrence_data * aod,ECalRecurResolveTimezoneCb resolve_tzid,gpointer user_data,ICalTimezone * default_timezone)536 generate_absolute_triggers (ECalComponent *comp,
537                             struct alarm_occurrence_data *aod,
538                             ECalRecurResolveTimezoneCb resolve_tzid,
539                             gpointer user_data,
540                             ICalTimezone *default_timezone)
541 {
542 	GSList *link;
543 	ECalComponentDateTime *dtstart, *dtend;
544 	time_t occur_start, occur_end;
545 	gchar *rid;
546 
547 	dtstart = e_cal_component_get_dtstart (comp);
548 	dtend = e_cal_component_get_dtend (comp);
549 	rid = e_cal_component_get_recurid_as_string (comp);
550 
551 	/* No particular occurrence, so just use the times from the
552 	 * component */
553 
554 	if (dtstart && e_cal_component_datetime_get_value (dtstart)) {
555 		ICalTimezone *zone;
556 		const gchar *tzid = e_cal_component_datetime_get_tzid (dtstart);
557 
558 		if (tzid && !i_cal_time_is_date (e_cal_component_datetime_get_value (dtstart)))
559 			zone = (* resolve_tzid) (tzid, user_data, NULL, NULL);
560 		else
561 			zone = default_timezone;
562 
563 		occur_start = i_cal_time_as_timet_with_zone (e_cal_component_datetime_get_value (dtstart), zone);
564 	} else
565 		occur_start = -1;
566 
567 	if (dtend && e_cal_component_datetime_get_value (dtend)) {
568 		ICalTimezone *zone;
569 		const gchar *tzid = e_cal_component_datetime_get_tzid (dtend);
570 
571 		if (tzid && !i_cal_time_is_date (e_cal_component_datetime_get_value (dtend)))
572 			zone = (* resolve_tzid) (tzid, user_data, NULL, NULL);
573 		else
574 			zone = default_timezone;
575 
576 		occur_end = i_cal_time_as_timet_with_zone (e_cal_component_datetime_get_value (dtend), zone);
577 	} else {
578 		e_cal_component_datetime_free (dtend);
579 		dtend = e_cal_component_get_due (comp);
580 
581 		if (dtend && e_cal_component_datetime_get_value (dtend)) {
582 			ICalTimezone *zone;
583 			const gchar *tzid = e_cal_component_datetime_get_tzid (dtend);
584 
585 			if (tzid && !i_cal_time_is_date (e_cal_component_datetime_get_value (dtend)))
586 				zone = (* resolve_tzid) (tzid, user_data, NULL, NULL);
587 			else
588 				zone = default_timezone;
589 
590 			occur_end = i_cal_time_as_timet_with_zone (e_cal_component_datetime_get_value (dtend), zone);
591 		} else
592 			occur_end = -1;
593 	}
594 
595 	for (link = aod->alarm_uids; link; link = g_slist_next (link)) {
596 		const gchar *auid;
597 		ECalComponentAlarm *alarm;
598 		ECalComponentAlarmAction action;
599 		ECalComponentAlarmRepeat *repeat;
600 		ECalComponentAlarmTrigger *trigger;
601 		time_t abs_time;
602 		ICalTimezone *zone;
603 		gint i;
604 
605 		auid = link->data;
606 		alarm = e_cal_component_get_alarm (comp, auid);
607 		g_return_if_fail (alarm != NULL);
608 
609 		action = e_cal_component_alarm_get_action (alarm);
610 		trigger = e_cal_component_alarm_get_trigger (alarm);
611 		repeat = e_cal_component_alarm_get_repeat (alarm);
612 
613 		for (i = 0; aod->omit[i] != -1; i++) {
614 			if (aod->omit[i] == action)
615 				break;
616 		}
617 
618 		if (aod->omit[i] != -1) {
619 			e_cal_component_alarm_free (alarm);
620 			continue;
621 		}
622 
623 		if (e_cal_component_alarm_trigger_get_kind (trigger) != E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE) {
624 			e_cal_component_alarm_free (alarm);
625 			continue;
626 		}
627 
628 		/* Absolute triggers are always in UTC;
629 		 * see RFC 2445 section 4.8.6.3 */
630 		zone = i_cal_timezone_get_utc_timezone ();
631 
632 		abs_time = i_cal_time_as_timet_with_zone (e_cal_component_alarm_trigger_get_absolute_time (trigger), zone);
633 
634 		/* Add repeating alarms */
635 
636 		if (repeat && e_cal_component_alarm_repeat_get_repetitions (repeat) > 0) {
637 			gint ii, repetitions;
638 			time_t repeat_time;
639 
640 			repeat_time = e_cal_component_alarm_repeat_get_interval_seconds (repeat);
641 			repetitions = e_cal_component_alarm_repeat_get_repetitions (repeat);
642 
643 			for (ii = 0; ii < repetitions; ii++) {
644 				time_t tt;
645 
646 				tt = abs_time + (ii + 1) * repeat_time;
647 
648 				if (tt >= aod->start && tt < aod->end)
649 					add_trigger (aod, auid, rid, tt, occur_start, occur_end);
650 			}
651 		}
652 
653 		/* Add the trigger itself */
654 
655 		if (abs_time >= aod->start && abs_time < aod->end)
656 			add_trigger (aod, auid, rid, abs_time, occur_start, occur_end);
657 
658 		e_cal_component_alarm_free (alarm);
659 	}
660 
661 	e_cal_component_datetime_free (dtstart);
662 	e_cal_component_datetime_free (dtend);
663 	g_free (rid);
664 }
665 
666 /* Compares two alarm instances; called from g_slist_sort() */
667 static gint
compare_alarm_instance(gconstpointer a,gconstpointer b)668 compare_alarm_instance (gconstpointer a,
669                         gconstpointer b)
670 {
671 	const ECalComponentAlarmInstance *aia, *aib;
672 	time_t atime, btime;
673 
674 	aia = a;
675 	aib = b;
676 
677 	atime = e_cal_component_alarm_instance_get_time (aia);
678 	btime = e_cal_component_alarm_instance_get_time (aib);
679 
680 	if (atime < btime)
681 		return -1;
682 	else if (atime > btime)
683 		return 1;
684 	else
685 		return 0;
686 }
687 
688 /**
689  * e_cal_util_generate_alarms_for_comp:
690  * @comp: The #ECalComponent to generate alarms from
691  * @start: Start time
692  * @end: End time
693  * @omit: Alarm types to omit
694  * @resolve_tzid: (closure user_data) (scope call): Callback for resolving
695  * timezones
696  * @user_data: (closure): Data to be passed to the resolve_tzid callback
697  * @default_timezone: The timezone used to resolve DATE and floating DATE-TIME
698  * values.
699  *
700  * Generates alarm instances for a calendar component. Returns the instances
701  * structure, or %NULL if no alarm instances occurred in the specified time
702  * range. Free the returned structure with e_cal_component_alarms_free(),
703  * when no longer needed.
704  *
705  * Returns: (transfer full) (nullable): a list of all the alarms found
706  *    for the given component in the given time range.
707  */
708 ECalComponentAlarms *
e_cal_util_generate_alarms_for_comp(ECalComponent * comp,time_t start,time_t end,ECalComponentAlarmAction * omit,ECalRecurResolveTimezoneCb resolve_tzid,gpointer user_data,ICalTimezone * default_timezone)709 e_cal_util_generate_alarms_for_comp (ECalComponent *comp,
710                                      time_t start,
711                                      time_t end,
712                                      ECalComponentAlarmAction *omit,
713                                      ECalRecurResolveTimezoneCb resolve_tzid,
714                                      gpointer user_data,
715                                      ICalTimezone *default_timezone)
716 {
717 	GSList *alarm_uids;
718 	time_t alarm_start, alarm_end;
719 	struct alarm_occurrence_data aod;
720 	ICalTime *alarm_start_tt, *alarm_end_tt;
721 	ECalComponentAlarms *alarms;
722 
723 	if (!e_cal_component_has_alarms (comp))
724 		return NULL;
725 
726 	alarm_uids = e_cal_component_get_alarm_uids (comp);
727 	compute_alarm_range (comp, alarm_uids, start, end, &alarm_start, &alarm_end);
728 
729 	aod.comp = comp;
730 	aod.alarm_uids = alarm_uids;
731 	aod.start = start;
732 	aod.end = end;
733 	aod.omit = omit;
734 	aod.triggers = NULL;
735 	aod.n_triggers = 0;
736 
737 	alarm_start_tt = i_cal_time_new_from_timet_with_zone (alarm_start, FALSE, i_cal_timezone_get_utc_timezone ());
738 	alarm_end_tt = i_cal_time_new_from_timet_with_zone (alarm_end, FALSE, i_cal_timezone_get_utc_timezone ());
739 
740 	e_cal_recur_generate_instances_sync (e_cal_component_get_icalcomponent (comp),
741 		alarm_start_tt, alarm_end_tt,
742 		add_alarm_occurrences_cb, &aod,
743 		resolve_tzid, user_data,
744 		default_timezone, NULL, NULL);
745 
746 	g_clear_object (&alarm_start_tt);
747 	g_clear_object (&alarm_end_tt);
748 
749 	/* We add the ABSOLUTE triggers separately */
750 	generate_absolute_triggers (comp, &aod, resolve_tzid, user_data, default_timezone);
751 
752 	g_slist_free_full (alarm_uids, g_free);
753 
754 	if (aod.n_triggers == 0)
755 		return NULL;
756 
757 	/* Create the component alarm instances structure */
758 
759 	alarms = e_cal_component_alarms_new (comp);
760 	e_cal_component_alarms_take_instances (alarms, g_slist_sort (aod.triggers, compare_alarm_instance));
761 
762 	return alarms;
763 }
764 
765 /**
766  * e_cal_util_generate_alarms_for_list:
767  * @comps: (element-type ECalComponent): List of #ECalComponent<!-- -->s
768  * @start: Start time
769  * @end: End time
770  * @omit: Alarm types to omit
771  * @comp_alarms: (out) (transfer full) (element-type ECalComponentAlarms): List
772  * to be returned
773  * @resolve_tzid: (closure user_data) (scope call): Callback for resolving
774  * timezones
775  * @user_data: (closure): Data to be passed to the resolve_tzid callback
776  * @default_timezone: The timezone used to resolve DATE and floating DATE-TIME
777  * values.
778  *
779  * Iterates through all the components in the @comps list and generates alarm
780  * instances for them; putting them in the @comp_alarms list. Free the @comp_alarms
781  * with g_slist_free_full (comp_alarms, e_cal_component_alarms_free);, when
782  * no longer neeed.
783  *
784  * Returns: the number of elements it added to the list
785  */
786 gint
e_cal_util_generate_alarms_for_list(GList * comps,time_t start,time_t end,ECalComponentAlarmAction * omit,GSList ** comp_alarms,ECalRecurResolveTimezoneCb resolve_tzid,gpointer user_data,ICalTimezone * default_timezone)787 e_cal_util_generate_alarms_for_list (GList *comps,
788                                      time_t start,
789                                      time_t end,
790                                      ECalComponentAlarmAction *omit,
791                                      GSList **comp_alarms,
792                                      ECalRecurResolveTimezoneCb resolve_tzid,
793                                      gpointer user_data,
794                                      ICalTimezone *default_timezone)
795 {
796 	GList *l;
797 	gint n;
798 
799 	n = 0;
800 
801 	for (l = comps; l; l = l->next) {
802 		ECalComponent *comp;
803 		ECalComponentAlarms *alarms;
804 
805 		comp = E_CAL_COMPONENT (l->data);
806 		alarms = e_cal_util_generate_alarms_for_comp (
807 			comp, start, end, omit, resolve_tzid,
808 			user_data, default_timezone);
809 
810 		if (alarms) {
811 			*comp_alarms = g_slist_prepend (*comp_alarms, alarms);
812 			n++;
813 		}
814 	}
815 
816 	return n;
817 }
818 
819 /**
820  * e_cal_util_priority_to_string:
821  * @priority: Priority value.
822  *
823  * Converts an iCalendar PRIORITY value to a translated string. Any unknown
824  * priority value (i.e. not 0-9) will be returned as "" (undefined).
825  *
826  * Returns: a string representing the PRIORITY value. This value is a
827  * constant, so it should never be freed.
828  */
829 const gchar *
e_cal_util_priority_to_string(gint priority)830 e_cal_util_priority_to_string (gint priority)
831 {
832 	const gchar *retval;
833 
834 	if (priority <= 0)
835 		retval = "";
836 	else if (priority <= 4)
837 		retval = C_("Priority", "High");
838 	else if (priority == 5)
839 		retval = C_("Priority", "Normal");
840 	else if (priority <= 9)
841 		retval = C_("Priority", "Low");
842 	else
843 		retval = "";
844 
845 	return retval;
846 }
847 
848 /**
849  * e_cal_util_priority_from_string:
850  * @string: A string representing the PRIORITY value.
851  *
852  * Converts a translated priority string to an iCalendar priority value.
853  *
854  * Returns: the priority (0-9) or -1 if the priority string is not valid.
855 */
856 gint
e_cal_util_priority_from_string(const gchar * string)857 e_cal_util_priority_from_string (const gchar *string)
858 {
859 	gint priority;
860 
861 	/* An empty string is the same as 'None'. */
862 	if (!string || !string[0] || !e_util_utf8_strcasecmp (string, C_("Priority", "Undefined")))
863 		priority = 0;
864 	else if (!e_util_utf8_strcasecmp (string, C_("Priority", "High")))
865 		priority = 3;
866 	else if (!e_util_utf8_strcasecmp (string, C_("Priority", "Normal")))
867 		priority = 5;
868 	else if (!e_util_utf8_strcasecmp (string, C_("Priority", "Low")))
869 		priority = 7;
870 	else
871 		priority = -1;
872 
873 	return priority;
874 }
875 
876 /**
877  * e_cal_util_seconds_to_string:
878  * @seconds: actual time, in seconds
879  *
880  * Converts time, in seconds, into a string representation readable by humans
881  * and localized into the current locale. This can be used to convert event
882  * duration to string or similar use cases.
883  *
884  * Free the returned string with g_free(), when no longer needed.
885  *
886  * Returns: (transfer full): a newly allocated string with localized description
887  *    of the given time in seconds.
888  *
889  * Since: 3.30
890  **/
891 gchar *
e_cal_util_seconds_to_string(gint64 seconds)892 e_cal_util_seconds_to_string (gint64 seconds)
893 {
894 	gchar *times[6], *text;
895 	gint ii;
896 
897 	ii = 0;
898 	if (seconds >= 7 * 24 * 3600) {
899 		gint weeks;
900 
901 		weeks = seconds / (7 * 24 * 3600);
902 		seconds %= (7 * 24 * 3600);
903 
904 		times[ii++] = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d week", "%d weeks", weeks), weeks);
905 	}
906 
907 	if (seconds >= 24 * 3600) {
908 		gint days;
909 
910 		days = seconds / (24 * 3600);
911 		seconds %= (24 * 3600);
912 
913 		times[ii++] = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d day", "%d days", days), days);
914 	}
915 
916 	if (seconds >= 3600) {
917 		gint hours;
918 
919 		hours = seconds / 3600;
920 		seconds %= 3600;
921 
922 		times[ii++] = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d hour", "%d hours", hours), hours);
923 	}
924 
925 	if (seconds >= 60) {
926 		gint minutes;
927 
928 		minutes = seconds / 60;
929 		seconds %= 60;
930 
931 		times[ii++] = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d minute", "%d minutes", minutes), minutes);
932 	}
933 
934 	if (seconds != 0) {
935 		/* Translators: here, "second" is the time division (like "minute"), not the ordinal number (like "third") */
936 		times[ii++] = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d second", "%d seconds", seconds), (gint) seconds);
937 	}
938 
939 	times[ii] = NULL;
940 	text = g_strjoinv (" ", times);
941 	while (ii > 0) {
942 		g_free (times[--ii]);
943 	}
944 
945 	return text;
946 }
947 
948 /* callback for icalcomponent_foreach_tzid */
949 typedef struct {
950 	ICalComponent *vcal_comp;
951 	ICalComponent *icalcomp;
952 } ForeachTzidData;
953 
954 static void
add_timezone_cb(ICalParameter * param,gpointer user_data)955 add_timezone_cb (ICalParameter *param,
956                  gpointer user_data)
957 {
958 	ICalTimezone *tz;
959 	const gchar *tzid;
960 	ICalComponent *vtz_comp;
961 	ForeachTzidData *f_data = user_data;
962 
963 	tzid = i_cal_parameter_get_tzid (param);
964 	if (!tzid)
965 		return;
966 
967 	tz = i_cal_component_get_timezone (f_data->vcal_comp, tzid);
968 	if (tz) {
969 		g_object_unref (tz);
970 		return;
971 	}
972 
973 	tz = i_cal_component_get_timezone (f_data->icalcomp, tzid);
974 	if (!tz) {
975 		tz = i_cal_timezone_get_builtin_timezone_from_tzid (tzid);
976 		if (!tz)
977 			return;
978 
979 		g_object_ref (tz);
980 	}
981 
982 	vtz_comp = i_cal_timezone_get_component (tz);
983 	if (vtz_comp) {
984 		i_cal_component_take_component (f_data->vcal_comp, i_cal_component_clone (vtz_comp));
985 		g_object_unref (vtz_comp);
986 	}
987 
988 	g_object_unref (tz);
989 }
990 
991 /**
992  * e_cal_util_add_timezones_from_component:
993  * @vcal_comp: A VCALENDAR component.
994  * @icalcomp: An iCalendar component, of any type.
995  *
996  * Adds VTIMEZONE components to a VCALENDAR for all tzid's
997  * in the given @icalcomp.
998  */
999 void
e_cal_util_add_timezones_from_component(ICalComponent * vcal_comp,ICalComponent * icalcomp)1000 e_cal_util_add_timezones_from_component (ICalComponent *vcal_comp,
1001 					 ICalComponent *icalcomp)
1002 {
1003 	ForeachTzidData f_data;
1004 
1005 	g_return_if_fail (vcal_comp != NULL);
1006 	g_return_if_fail (icalcomp != NULL);
1007 
1008 	f_data.vcal_comp = vcal_comp;
1009 	f_data.icalcomp = icalcomp;
1010 	i_cal_component_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
1011 }
1012 
1013 /**
1014  * e_cal_util_property_has_parameter:
1015  * @prop: an #ICalProperty
1016  * @param_kind: a parameter kind to look for, as an %ICalParameterKind
1017  *
1018  * Returns, whether the @prop has a parameter of @param_kind.
1019  *
1020  * Returns: whether the @prop has a parameter of @prop_kind
1021  *
1022  * Since: 3.34
1023  **/
1024 gboolean
e_cal_util_property_has_parameter(ICalProperty * prop,ICalParameterKind param_kind)1025 e_cal_util_property_has_parameter (ICalProperty *prop,
1026 				   ICalParameterKind param_kind)
1027 {
1028 	ICalParameter *param;
1029 
1030 	g_return_val_if_fail (I_CAL_IS_PROPERTY (prop), FALSE);
1031 
1032 	param = i_cal_property_get_first_parameter (prop, param_kind);
1033 
1034 	if (!param)
1035 		return FALSE;
1036 
1037 	g_object_unref (param);
1038 
1039 	return TRUE;
1040 }
1041 
1042 /**
1043  * e_cal_util_component_has_property:
1044  * @icalcomp: an #ICalComponent
1045  * @prop_kind: a property kind to look for, as an %ICalPropertyKind
1046  *
1047  * Returns, whether the @icalcomp has a property of @prop_kind. To check
1048  * for a specific X property use e_cal_util_component_has_x_property().
1049  *
1050  * Returns: whether the @icalcomp has a property of @prop_kind
1051  *
1052  * Since: 3.34
1053  **/
1054 gboolean
e_cal_util_component_has_property(ICalComponent * icalcomp,ICalPropertyKind prop_kind)1055 e_cal_util_component_has_property (ICalComponent *icalcomp,
1056 				   ICalPropertyKind prop_kind)
1057 {
1058 	ICalProperty *prop;
1059 
1060 	g_return_val_if_fail (I_CAL_IS_COMPONENT (icalcomp), FALSE);
1061 
1062 	prop = i_cal_component_get_first_property (icalcomp, prop_kind);
1063 
1064 	if (!prop)
1065 		return FALSE;
1066 
1067 	g_object_unref (prop);
1068 
1069 	return TRUE;
1070 }
1071 
1072 /**
1073  * e_cal_util_component_is_instance:
1074  * @icalcomp: An #ICalComponent.
1075  *
1076  * Checks whether an #ICalComponent is an instance of a recurring appointment.
1077  *
1078  * Returns: TRUE if it is an instance, FALSE if not.
1079  */
1080 gboolean
e_cal_util_component_is_instance(ICalComponent * icalcomp)1081 e_cal_util_component_is_instance (ICalComponent *icalcomp)
1082 {
1083 	g_return_val_if_fail (icalcomp != NULL, FALSE);
1084 
1085 	return e_cal_util_component_has_property (icalcomp, I_CAL_RECURRENCEID_PROPERTY);
1086 }
1087 
1088 /**
1089  * e_cal_util_component_has_alarms:
1090  * @icalcomp: An #ICalComponent.
1091  *
1092  * Checks whether an #ICalComponent has any alarm.
1093  *
1094  * Returns: TRUE if it has alarms, FALSE otherwise.
1095  */
1096 gboolean
e_cal_util_component_has_alarms(ICalComponent * icalcomp)1097 e_cal_util_component_has_alarms (ICalComponent *icalcomp)
1098 {
1099 	ICalComponent *alarm;
1100 
1101 	g_return_val_if_fail (icalcomp != NULL, FALSE);
1102 
1103 	alarm = i_cal_component_get_first_component (icalcomp, I_CAL_VALARM_COMPONENT);
1104 
1105 	if (!alarm)
1106 		return FALSE;
1107 
1108 	g_object_unref (alarm);
1109 
1110 	return TRUE;
1111 }
1112 
1113 /**
1114  * e_cal_util_component_has_organizer:
1115  * @icalcomp: An #ICalComponent.
1116  *
1117  * Checks whether an #ICalComponent has an organizer.
1118  *
1119  * Returns: TRUE if there is an organizer, FALSE if not.
1120  */
1121 gboolean
e_cal_util_component_has_organizer(ICalComponent * icalcomp)1122 e_cal_util_component_has_organizer (ICalComponent *icalcomp)
1123 {
1124 	g_return_val_if_fail (icalcomp != NULL, FALSE);
1125 
1126 	return e_cal_util_component_has_property (icalcomp, I_CAL_ORGANIZER_PROPERTY);
1127 }
1128 
1129 /**
1130  * e_cal_util_component_has_attendee:
1131  * @icalcomp: An #ICalComponent.
1132  *
1133  * Checks if an #ICalComponent has any attendees.
1134  *
1135  * Returns: TRUE if there are attendees, FALSE if not.
1136  */
1137 gboolean
e_cal_util_component_has_attendee(ICalComponent * icalcomp)1138 e_cal_util_component_has_attendee (ICalComponent *icalcomp)
1139 {
1140 	g_return_val_if_fail (icalcomp != NULL, FALSE);
1141 
1142 	return e_cal_util_component_has_property (icalcomp, I_CAL_ATTENDEE_PROPERTY);
1143 }
1144 
1145 /**
1146  * e_cal_util_component_get_recurid_as_string:
1147  * @icalcomp: an #ICalComponent
1148  *
1149  * Returns: (transfer full) (nullable): a RECURRENCEID property as string,
1150  *    or %NULL, when the @icalcomp is not an instance. Free the returned
1151  *    string with g_free(), when no longer needed.
1152  *
1153  * Since: 3.34
1154  **/
1155 gchar *
e_cal_util_component_get_recurid_as_string(ICalComponent * icalcomp)1156 e_cal_util_component_get_recurid_as_string (ICalComponent *icalcomp)
1157 {
1158 	ICalProperty *prop;
1159 	ICalTime *recurid;
1160 	gchar *rid;
1161 
1162 	g_return_val_if_fail (icalcomp != NULL, NULL);
1163 
1164 	prop = i_cal_component_get_first_property (icalcomp, I_CAL_RECURRENCEID_PROPERTY);
1165 	if (!prop)
1166 		return NULL;
1167 
1168 	recurid = i_cal_property_get_recurrenceid (prop);
1169 	if (!recurid ||
1170 	    !i_cal_time_is_valid_time (recurid) ||
1171 	    i_cal_time_is_null_time (recurid)) {
1172 		rid = g_strdup ("0");
1173 	} else {
1174 		rid = i_cal_time_as_ical_string (recurid);
1175 	}
1176 
1177 	g_clear_object (&recurid);
1178 	g_object_unref (prop);
1179 
1180 	return rid;
1181 }
1182 
1183 /**
1184  * e_cal_util_component_has_recurrences:
1185  * @icalcomp: An #ICalComponent.
1186  *
1187  * Checks if an #ICalComponent has recurrence dates or rules.
1188  *
1189  * Returns: TRUE if there are recurrence dates/rules, FALSE if not.
1190  */
1191 gboolean
e_cal_util_component_has_recurrences(ICalComponent * icalcomp)1192 e_cal_util_component_has_recurrences (ICalComponent *icalcomp)
1193 {
1194 	g_return_val_if_fail (icalcomp != NULL, FALSE);
1195 
1196 	return e_cal_util_component_has_rdates (icalcomp) ||
1197 		e_cal_util_component_has_rrules (icalcomp);
1198 }
1199 
1200 /**
1201  * e_cal_util_component_has_rdates:
1202  * @icalcomp: An #ICalComponent.
1203  *
1204  * Checks if an #ICalComponent has recurrence dates.
1205  *
1206  * Returns: TRUE if there are recurrence dates, FALSE if not.
1207  */
1208 gboolean
e_cal_util_component_has_rdates(ICalComponent * icalcomp)1209 e_cal_util_component_has_rdates (ICalComponent *icalcomp)
1210 {
1211 	g_return_val_if_fail (icalcomp != NULL, FALSE);
1212 
1213 	return e_cal_util_component_has_property (icalcomp, I_CAL_RDATE_PROPERTY);
1214 }
1215 
1216 /**
1217  * e_cal_util_component_has_rrules:
1218  * @icalcomp: An #ICalComponent.
1219  *
1220  * Checks if an #ICalComponent has recurrence rules.
1221  *
1222  * Returns: TRUE if there are recurrence rules, FALSE if not.
1223  */
1224 gboolean
e_cal_util_component_has_rrules(ICalComponent * icalcomp)1225 e_cal_util_component_has_rrules (ICalComponent *icalcomp)
1226 {
1227 	g_return_val_if_fail (icalcomp != NULL, FALSE);
1228 
1229 	return e_cal_util_component_has_property (icalcomp, I_CAL_RRULE_PROPERTY);
1230 }
1231 
1232 /* Individual instances management */
1233 
1234 struct instance_data {
1235 	time_t start;
1236 	gboolean found;
1237 };
1238 
1239 static void
check_instance(ICalComponent * comp,ICalTimeSpan * span,gpointer user_data)1240 check_instance (ICalComponent *comp,
1241 		ICalTimeSpan *span,
1242 		gpointer user_data)
1243 {
1244 	struct instance_data *instance = user_data;
1245 
1246 	if (i_cal_time_span_get_start (span) == instance->start)
1247 		instance->found = TRUE;
1248 }
1249 
1250 /**
1251  * e_cal_util_construct_instance:
1252  * @icalcomp: A recurring #ICalComponent
1253  * @rid: The RECURRENCE-ID to construct a component for
1254  *
1255  * This checks that @rid indicates a valid recurrence of @icalcomp, and
1256  * if so, generates a copy of @icalcomp containing a RECURRENCE-ID of @rid.
1257  *
1258  * Free the returned non-NULL component with g_object_unref(), when
1259  * no longer needed.
1260  *
1261  * Returns: (transfer full) (nullable): the instance as a new #ICalComponent, or %NULL.
1262  **/
1263 ICalComponent *
e_cal_util_construct_instance(ICalComponent * icalcomp,const ICalTime * rid)1264 e_cal_util_construct_instance (ICalComponent *icalcomp,
1265 			       const ICalTime *rid)
1266 {
1267 	struct instance_data instance;
1268 	ICalTime *start, *end;
1269 
1270 	g_return_val_if_fail (icalcomp != NULL, NULL);
1271 
1272 	/* Make sure this is really recurring */
1273 	if (!e_cal_util_component_has_recurrences (icalcomp))
1274 		return NULL;
1275 
1276 	/* Make sure the specified instance really exists */
1277 	start = i_cal_time_convert_to_zone ((ICalTime *) rid, i_cal_timezone_get_utc_timezone ());
1278 	end = i_cal_time_clone (start);
1279 	i_cal_time_adjust (end, 0, 0, 0, 1);
1280 
1281 	instance.start = i_cal_time_as_timet (start);
1282 	instance.found = FALSE;
1283 	i_cal_component_foreach_recurrence (icalcomp, start, end, check_instance, &instance);
1284 
1285 	g_object_unref (start);
1286 	g_object_unref (end);
1287 
1288 	if (!instance.found)
1289 		return NULL;
1290 
1291 	/* Make the instance */
1292 	icalcomp = i_cal_component_clone (icalcomp);
1293 	i_cal_component_set_recurrenceid (icalcomp, (ICalTime *) rid);
1294 
1295 	return icalcomp;
1296 }
1297 
1298 static inline gboolean
time_matches_rid(const ICalTime * itt,const ICalTime * rid,ECalObjModType mod)1299 time_matches_rid (const ICalTime *itt,
1300                   const ICalTime *rid,
1301                   ECalObjModType mod)
1302 {
1303 	gint compare;
1304 
1305 	compare = i_cal_time_compare ((ICalTime *) itt, (ICalTime *) rid);
1306 	if (compare == 0)
1307 		return TRUE;
1308 	else if (compare < 0 && (mod & E_CAL_OBJ_MOD_THIS_AND_PRIOR))
1309 		return TRUE;
1310 	else if (compare > 0 && (mod & E_CAL_OBJ_MOD_THIS_AND_FUTURE))
1311 		return TRUE;
1312 
1313 	return FALSE;
1314 }
1315 
1316 /**
1317  * e_cal_util_normalize_rrule_until_value:
1318  * @icalcomp: An #ICalComponent
1319  * @ttuntil: An UNTIL value to validate
1320  * @tz_cb: (closure tz_cb_data) (scope call): The #ECalRecurResolveTimezoneCb to call
1321  * @tz_cb_data: (closure): User data to be passed to the @tz_cb callback
1322  *
1323  * Makes sure the @ttuntil value matches the value type with
1324  * the DTSTART value, as required by RFC 5545 section 3.3.10.
1325  * Uses @tz_cb with @tz_cb_data to resolve time zones when needed.
1326  *
1327  * Since: 3.38
1328  **/
1329 void
e_cal_util_normalize_rrule_until_value(ICalComponent * icalcomp,ICalTime * ttuntil,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data)1330 e_cal_util_normalize_rrule_until_value (ICalComponent *icalcomp,
1331 					ICalTime *ttuntil,
1332 					ECalRecurResolveTimezoneCb tz_cb,
1333 					gpointer tz_cb_data)
1334 {
1335 	ICalProperty *prop;
1336 
1337 	g_return_if_fail (I_CAL_IS_COMPONENT (icalcomp));
1338 	g_return_if_fail (I_CAL_IS_TIME (ttuntil));
1339 
1340 	prop = i_cal_component_get_first_property (icalcomp, I_CAL_DTSTART_PROPERTY);
1341 
1342 	if (prop) {
1343 		ICalTime *dtstart;
1344 
1345 		dtstart = i_cal_component_get_dtstart (icalcomp);
1346 
1347 		if (dtstart) {
1348 			if (i_cal_time_is_date (dtstart)) {
1349 				i_cal_time_set_time (ttuntil, 0, 0, 0);
1350 				i_cal_time_set_is_date (ttuntil, TRUE);
1351 			} else {
1352 				if (i_cal_time_is_date (ttuntil)) {
1353 					gint hour = 0, minute = 0, second = 0;
1354 
1355 					i_cal_time_set_is_date (ttuntil, FALSE);
1356 
1357 					i_cal_time_get_time (dtstart, &hour, &minute, &second);
1358 					i_cal_time_set_time (ttuntil, hour, minute, second);
1359 				}
1360 
1361 				if (!i_cal_time_is_utc (dtstart)) {
1362 					ICalParameter *param;
1363 
1364 					param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
1365 
1366 					if (param) {
1367 						const gchar *tzid;
1368 
1369 						tzid = i_cal_parameter_get_tzid (param);
1370 
1371 						if (tzid && *tzid && g_ascii_strcasecmp (tzid, "UTC") != 0) {
1372 							ICalTimezone *tz;
1373 
1374 							tz = i_cal_time_get_timezone (dtstart);
1375 
1376 							if (!tz && tz_cb)
1377 								tz = tz_cb (tzid, tz_cb_data, NULL, NULL);
1378 
1379 							if (tz) {
1380 								i_cal_time_set_timezone (ttuntil, tz);
1381 								i_cal_time_convert_to_zone_inplace (ttuntil, i_cal_timezone_get_utc_timezone ());
1382 							}
1383 						}
1384 
1385 						g_object_unref (param);
1386 					}
1387 				}
1388 			}
1389 
1390 			g_object_unref (dtstart);
1391 		}
1392 
1393 		g_object_unref (prop);
1394 	}
1395 }
1396 
1397 static void
e_cal_util_remove_instances_impl(ICalComponent * icalcomp,const ICalTime * rid,ECalObjModType mod,gboolean keep_rid,gboolean can_add_exrule,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data)1398 e_cal_util_remove_instances_impl (ICalComponent *icalcomp,
1399 				  const ICalTime *rid,
1400 				  ECalObjModType mod,
1401 				  gboolean keep_rid,
1402 				  gboolean can_add_exrule,
1403 				  ECalRecurResolveTimezoneCb tz_cb,
1404 				  gpointer tz_cb_data)
1405 {
1406 	ICalProperty *prop;
1407 	ICalTime *itt, *recur;
1408 	ICalRecurrence *rule;
1409 	ICalRecurIterator *iter;
1410 	GSList *remove_props = NULL, *rrules = NULL, *link;
1411 
1412 	g_return_if_fail (icalcomp != NULL);
1413 	g_return_if_fail (mod != E_CAL_OBJ_MOD_ALL);
1414 
1415 	/* First remove RDATEs and EXDATEs in the indicated range. */
1416 	for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_RDATE_PROPERTY);
1417 	     prop;
1418 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_RDATE_PROPERTY)) {
1419 		ICalDatetimeperiod *period;
1420 		ICalTime *period_time;
1421 
1422 		period = i_cal_property_get_rdate (prop);
1423 		if (!period)
1424 			continue;
1425 
1426 		period_time = i_cal_datetimeperiod_get_time (period);
1427 
1428 		if (time_matches_rid (period_time, rid, mod) && (!keep_rid ||
1429 		    i_cal_time_compare (period_time, (ICalTime *) rid) != 0))
1430 			remove_props = g_slist_prepend (remove_props, g_object_ref (prop));
1431 
1432 		g_clear_object (&period_time);
1433 		g_object_unref (period);
1434 	}
1435 
1436 	for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_EXDATE_PROPERTY);
1437 	     prop;
1438 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_EXDATE_PROPERTY)) {
1439 		itt = i_cal_property_get_exdate (prop);
1440 		if (!itt)
1441 			continue;
1442 
1443 		if (time_matches_rid (itt, rid, mod) && (!keep_rid || i_cal_time_compare (itt, (ICalTime *) rid) != 0))
1444 			remove_props = g_slist_prepend (remove_props, g_object_ref (prop));
1445 
1446 		g_object_unref (itt);
1447 	}
1448 
1449 	for (link = remove_props; link; link = g_slist_next (link)) {
1450 		prop = link->data;
1451 
1452 		i_cal_component_remove_property (icalcomp, prop);
1453 	}
1454 
1455 	g_slist_free_full (remove_props, g_object_unref);
1456 	remove_props = NULL;
1457 
1458 	/* If we're only removing one instance, just add an EXDATE. */
1459 	if (mod == E_CAL_OBJ_MOD_THIS) {
1460 		prop = i_cal_property_new_exdate ((ICalTime *) rid);
1461 		i_cal_component_take_property (icalcomp, prop);
1462 		return;
1463 	}
1464 
1465 	/* Otherwise, iterate through RRULEs */
1466 	/* FIXME: this may generate duplicate EXRULEs */
1467 	for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_RRULE_PROPERTY);
1468 	     prop;
1469 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_RRULE_PROPERTY)) {
1470 		rrules = g_slist_prepend (rrules, g_object_ref (prop));
1471 	}
1472 
1473 	for (link = rrules; link; link = g_slist_next (link)) {
1474 		prop = link->data;
1475 
1476 		rule = i_cal_property_get_rrule (prop);
1477 		if (!rule)
1478 			continue;
1479 
1480 		iter = i_cal_recur_iterator_new (rule, (ICalTime *) rid);
1481 		recur = i_cal_recur_iterator_next (iter);
1482 
1483 		if (!recur) {
1484 			g_object_unref (rule);
1485 			g_object_unref (iter);
1486 			continue;
1487 		}
1488 
1489 		if (mod & E_CAL_OBJ_MOD_THIS_AND_FUTURE) {
1490 			/* Truncate the rule at rid. */
1491 			if (!i_cal_time_is_null_time (recur)) {
1492 				gint rule_count = i_cal_recurrence_get_count (rule);
1493 
1494 				/* Use count if it was used */
1495 				if (rule_count > 0) {
1496 					gint occurrences_count = 0;
1497 					ICalRecurIterator *count_iter;
1498 					ICalTime *count_recur, *dtstart;
1499 
1500 					dtstart = i_cal_component_get_dtstart (icalcomp);
1501 					count_iter = i_cal_recur_iterator_new (rule, dtstart);
1502 					while (count_recur = i_cal_recur_iterator_next (count_iter),
1503 					       count_recur && !i_cal_time_is_null_time (count_recur) && occurrences_count < rule_count) {
1504 						if (i_cal_time_compare (count_recur, (ICalTime *) rid) >= 0)
1505 							break;
1506 
1507 						occurrences_count++;
1508 						g_object_unref (count_recur);
1509 					}
1510 
1511 					if (keep_rid && count_recur && i_cal_time_compare (count_recur, (ICalTime *) rid) == 0)
1512 						occurrences_count++;
1513 
1514 					/* The caller should make sure that the remove will keep at least one instance */
1515 					g_warn_if_fail (occurrences_count > 0);
1516 
1517 					i_cal_recurrence_set_count (rule, occurrences_count);
1518 
1519 					g_clear_object (&count_recur);
1520 					g_clear_object (&count_iter);
1521 					g_clear_object (&dtstart);
1522 				} else {
1523 					ICalTime *ttuntil;
1524 					gboolean is_date;
1525 
1526 					if (keep_rid && i_cal_time_compare (recur, (ICalTime *) rid) == 0) {
1527 						ICalDuration *dur;
1528 
1529 						dur = i_cal_component_get_duration (icalcomp);
1530 						ttuntil = i_cal_time_add ((ICalTime *) rid, dur);
1531 						g_clear_object (&dur);
1532 					} else {
1533 						ttuntil = i_cal_time_clone (rid);
1534 					}
1535 
1536 					e_cal_util_normalize_rrule_until_value (icalcomp, ttuntil, tz_cb, tz_cb_data);
1537 
1538 					is_date = i_cal_time_is_date (ttuntil);
1539 
1540 					i_cal_time_adjust (ttuntil, is_date ? -1 : 0, 0, 0, is_date ? 0 : -1);
1541 
1542 					i_cal_recurrence_set_until (rule, ttuntil);
1543 					g_object_unref (ttuntil);
1544 				}
1545 
1546 				i_cal_property_set_rrule (prop, rule);
1547 				i_cal_property_remove_parameter_by_name (prop, E_CAL_EVOLUTION_ENDDATE_PARAMETER);
1548 			}
1549 		} else {
1550 			/* (If recur == rid, skip to the next occurrence) */
1551 			if (!keep_rid && i_cal_time_compare (recur, (ICalTime *) rid) == 0) {
1552 				g_object_unref (recur);
1553 				recur = i_cal_recur_iterator_next (iter);
1554 			}
1555 
1556 			/* If there is a recurrence after rid, add
1557 			 * an EXRULE to block instances up to rid.
1558 			 * Otherwise, just remove the RRULE.
1559 			 */
1560 			if (!i_cal_time_is_null_time (recur)) {
1561 				if (can_add_exrule) {
1562 					ICalTime *ttuntil;
1563 					ICalDuration *dur = i_cal_component_get_duration (icalcomp);
1564 
1565 					i_cal_recurrence_set_count (rule, 0);
1566 
1567 					/* iCalendar says we should just use rid
1568 					 * here, but Outlook/Exchange handle
1569 					 * UNTIL incorrectly.
1570 					 */
1571 					if (keep_rid && i_cal_time_compare (recur, (ICalTime *) rid) == 0) {
1572 						i_cal_duration_set_is_neg (dur, !i_cal_duration_is_neg (dur));
1573 						ttuntil = i_cal_time_add ((ICalTime *) rid, dur);
1574 					} else {
1575 						ttuntil = i_cal_time_add ((ICalTime *) rid, dur);
1576 					}
1577 
1578 					e_cal_util_normalize_rrule_until_value (icalcomp, ttuntil, tz_cb, tz_cb_data);
1579 					i_cal_recurrence_set_until (rule, ttuntil);
1580 
1581 					g_clear_object (&ttuntil);
1582 					g_clear_object (&dur);
1583 
1584 					prop = i_cal_property_new_exrule (rule);
1585 					i_cal_component_take_property (icalcomp, prop);
1586 				}
1587 			} else {
1588 				remove_props = g_slist_prepend (remove_props, g_object_ref (prop));
1589 			}
1590 		}
1591 
1592 		g_object_unref (recur);
1593 		g_object_unref (rule);
1594 		g_object_unref (iter);
1595 	}
1596 
1597 	for (link = remove_props; link; link = g_slist_next (link)) {
1598 		prop = link->data;
1599 
1600 		i_cal_component_remove_property (icalcomp, prop);
1601 	}
1602 
1603 	g_slist_free_full (remove_props, g_object_unref);
1604 	g_slist_free_full (rrules, g_object_unref);
1605 }
1606 
1607 /**
1608  * e_cal_util_remove_instances:
1609  * @icalcomp: A (recurring) #ICalComponent
1610  * @rid: The base RECURRENCE-ID to remove
1611  * @mod: How to interpret @rid
1612  *
1613  * Removes one or more instances from @icalcomp according to @rid and @mod.
1614  *
1615  * Deprecated: 3.38: Use e_cal_util_remove_instances_ex() instead, with provided
1616  *    timezone resolve function.
1617  **/
1618 void
e_cal_util_remove_instances(ICalComponent * icalcomp,const ICalTime * rid,ECalObjModType mod)1619 e_cal_util_remove_instances (ICalComponent *icalcomp,
1620                              const ICalTime *rid,
1621                              ECalObjModType mod)
1622 {
1623 	g_return_if_fail (icalcomp != NULL);
1624 	g_return_if_fail (rid != NULL);
1625 	g_return_if_fail (mod != E_CAL_OBJ_MOD_ALL);
1626 
1627 	e_cal_util_remove_instances_ex (icalcomp, rid, mod, NULL, NULL);
1628 }
1629 
1630 /**
1631  * e_cal_util_remove_instances_ex:
1632  * @icalcomp: A (recurring) #ICalComponent
1633  * @rid: The base RECURRENCE-ID to remove
1634  * @mod: How to interpret @rid
1635  * @tz_cb: (closure tz_cb_data) (scope call): The #ECalRecurResolveTimezoneCb to call
1636  * @tz_cb_data: (closure): User data to be passed to the @tz_cb callback
1637  *
1638  * Removes one or more instances from @icalcomp according to @rid and @mod.
1639  * Uses @tz_cb with @tz_cb_data to resolve time zones when needed.
1640  *
1641  * Since: 3.38
1642  **/
1643 void
e_cal_util_remove_instances_ex(ICalComponent * icalcomp,const ICalTime * rid,ECalObjModType mod,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data)1644 e_cal_util_remove_instances_ex (ICalComponent *icalcomp,
1645 				const ICalTime *rid,
1646 				ECalObjModType mod,
1647 				ECalRecurResolveTimezoneCb tz_cb,
1648 				gpointer tz_cb_data)
1649 {
1650 	g_return_if_fail (icalcomp != NULL);
1651 	g_return_if_fail (rid != NULL);
1652 	g_return_if_fail (mod != E_CAL_OBJ_MOD_ALL);
1653 
1654 	e_cal_util_remove_instances_impl (icalcomp, rid, mod, FALSE, TRUE, tz_cb, tz_cb_data);
1655 }
1656 
1657 /**
1658  * e_cal_util_split_at_instance:
1659  * @icalcomp: A (recurring) #ICalComponent
1660  * @rid: The base RECURRENCE-ID to remove
1661  * @master_dtstart: (nullable): The DTSTART of the master object
1662  *
1663  * Splits a recurring @icalcomp into two at time @rid. The returned #ICalComponent
1664  * is modified @icalcomp which contains recurrences beginning at @rid, inclusive.
1665  * The instance identified by @rid should exist. The @master_dtstart can be
1666  * a null time, then it is read from the @icalcomp.
1667  *
1668  * Use e_cal_util_remove_instances_ex() with E_CAL_OBJ_MOD_THIS_AND_FUTURE mode
1669  * on the @icalcomp to remove the overlapping interval from it, if needed.
1670  *
1671  * Free the returned non-NULL component with g_object_unref(), when
1672  * done with it.
1673  *
1674  * Returns: (transfer full) (nullable): the split @icalcomp, or %NULL.
1675  *
1676  * Since: 3.16
1677  *
1678  * Deprecated: 3.38: Use e_cal_util_split_at_instance_ex() instead, with provided
1679  *    timezone resolve function.
1680  **/
1681 ICalComponent *
e_cal_util_split_at_instance(ICalComponent * icalcomp,const ICalTime * rid,const ICalTime * master_dtstart)1682 e_cal_util_split_at_instance (ICalComponent *icalcomp,
1683 			      const ICalTime *rid,
1684 			      const ICalTime *master_dtstart)
1685 {
1686 	return e_cal_util_split_at_instance_ex (icalcomp, rid, master_dtstart, NULL, NULL);
1687 }
1688 
1689 /**
1690  * e_cal_util_split_at_instance_ex:
1691  * @icalcomp: A (recurring) #ICalComponent
1692  * @rid: The base RECURRENCE-ID to remove
1693  * @master_dtstart: (nullable): The DTSTART of the master object
1694  * @tz_cb: (closure tz_cb_data) (scope call): The #ECalRecurResolveTimezoneCb to call
1695  * @tz_cb_data: (closure): User data to be passed to the @tz_cb callback
1696  *
1697  * Splits a recurring @icalcomp into two at time @rid. The returned #ICalComponent
1698  * is modified @icalcomp which contains recurrences beginning at @rid, inclusive.
1699  * The instance identified by @rid should exist. The @master_dtstart can be
1700  * a null time, then it is read from the @icalcomp.
1701  *
1702  * Uses @tz_cb with @tz_cb_data to resolve time zones when needed.
1703  *
1704  * Use e_cal_util_remove_instances_ex() with E_CAL_OBJ_MOD_THIS_AND_FUTURE mode
1705  * on the @icalcomp to remove the overlapping interval from it, if needed.
1706  *
1707  * Free the returned non-NULL component with g_object_unref(), when
1708  * done with it.
1709  *
1710  * Returns: (transfer full) (nullable): the split @icalcomp, or %NULL.
1711  *
1712  * Since: 3.38
1713  **/
1714 ICalComponent *
e_cal_util_split_at_instance_ex(ICalComponent * icalcomp,const ICalTime * rid,const ICalTime * master_dtstart,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data)1715 e_cal_util_split_at_instance_ex (ICalComponent *icalcomp,
1716 				 const ICalTime *rid,
1717 				 const ICalTime *master_dtstart,
1718 				 ECalRecurResolveTimezoneCb tz_cb,
1719 				 gpointer tz_cb_data)
1720 {
1721 	ICalProperty *prop;
1722 	struct instance_data instance;
1723 	ICalTime *start, *end, *dtstart = NULL;
1724 	ICalDuration *duration;
1725 	GSList *remove_props = NULL, *link;
1726 
1727 	g_return_val_if_fail (icalcomp != NULL, NULL);
1728 	g_return_val_if_fail (rid != NULL, NULL);
1729 	g_return_val_if_fail (!i_cal_time_is_null_time ((ICalTime *) rid), NULL);
1730 
1731 	/* Make sure this is really recurring */
1732 	if (!e_cal_util_component_has_recurrences (icalcomp))
1733 		return NULL;
1734 
1735 	/* Make sure the specified instance really exists */
1736 	start = i_cal_time_convert_to_zone ((ICalTime *) rid, i_cal_timezone_get_utc_timezone ());
1737 	end = i_cal_time_clone (start);
1738 	i_cal_time_adjust (end, 0, 0, 0, 1);
1739 
1740 	instance.start = i_cal_time_as_timet (start);
1741 	instance.found = FALSE;
1742 	i_cal_component_foreach_recurrence (icalcomp, start, end, check_instance, &instance);
1743 	g_clear_object (&start);
1744 	g_clear_object (&end);
1745 
1746 	/* Make the copy */
1747 	icalcomp = i_cal_component_clone (icalcomp);
1748 
1749 	e_cal_util_remove_instances_impl (icalcomp, rid, E_CAL_OBJ_MOD_THIS_AND_PRIOR, TRUE, FALSE, tz_cb, tz_cb_data);
1750 
1751 	start = i_cal_time_clone ((ICalTime *) rid);
1752 
1753 	if (!master_dtstart || i_cal_time_is_null_time ((ICalTime *) master_dtstart)) {
1754 		dtstart = i_cal_component_get_dtstart (icalcomp);
1755 		master_dtstart = dtstart;
1756 	}
1757 
1758 	duration = i_cal_component_get_duration (icalcomp);
1759 
1760 	/* Expect that DTSTART and DTEND are already set when the instance could not be found */
1761 	if (instance.found) {
1762 		ICalTime *dtend;
1763 
1764 		dtend = i_cal_component_get_dtend (icalcomp);
1765 
1766 		i_cal_component_set_dtstart (icalcomp, start);
1767 
1768 		/* Update either DURATION or DTEND */
1769 		if (i_cal_time_is_null_time (dtend)) {
1770 			i_cal_component_set_duration (icalcomp, duration);
1771 		} else {
1772 			end = i_cal_time_clone (start);
1773 			if (i_cal_duration_is_neg (duration))
1774 				i_cal_time_adjust (end, -i_cal_duration_get_days (duration)
1775 							- 7 * i_cal_duration_get_weeks (duration),
1776 							-i_cal_duration_get_hours (duration),
1777 							-i_cal_duration_get_minutes (duration),
1778 							-i_cal_duration_get_seconds (duration));
1779 			else
1780 				i_cal_time_adjust (end, i_cal_duration_get_days (duration)
1781 							+ 7 * i_cal_duration_get_weeks (duration),
1782 							i_cal_duration_get_hours (duration),
1783 							i_cal_duration_get_minutes (duration),
1784 							i_cal_duration_get_seconds (duration));
1785 
1786 			i_cal_component_set_dtend (icalcomp, end);
1787 		}
1788 
1789 		g_clear_object (&dtend);
1790 	}
1791 
1792 	g_clear_object (&start);
1793 	g_clear_object (&end);
1794 	g_clear_object (&duration);
1795 
1796 	/* any RRULE with 'count' should be shortened */
1797 	for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_RRULE_PROPERTY);
1798 	     prop;
1799 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_RRULE_PROPERTY)) {
1800 		ICalTime *recur;
1801 		ICalRecurrence *rule;
1802 		gint rule_count;
1803 
1804 		rule = i_cal_property_get_rrule (prop);
1805 		if (!rule)
1806 			continue;
1807 
1808 		rule_count = i_cal_recurrence_get_count (rule);
1809 
1810 		if (rule_count != 0) {
1811 			gint occurrences_count = 0;
1812 			ICalRecurIterator *iter;
1813 
1814 			iter = i_cal_recur_iterator_new (rule, (ICalTime *) master_dtstart);
1815 			while (recur = i_cal_recur_iterator_next (iter),
1816 			       recur && !i_cal_time_is_null_time (recur) && occurrences_count < rule_count) {
1817 				if (i_cal_time_compare (recur, (ICalTime *) rid) >= 0)
1818 					break;
1819 
1820 				occurrences_count++;
1821 				g_object_unref (recur);
1822 			}
1823 
1824 			if (!recur || i_cal_time_is_null_time (recur)) {
1825 				remove_props = g_slist_prepend (remove_props, g_object_ref (prop));
1826 			} else {
1827 				i_cal_recurrence_set_count (rule, rule_count - occurrences_count);
1828 				i_cal_property_set_rrule (prop, rule);
1829 				i_cal_property_remove_parameter_by_name (prop, E_CAL_EVOLUTION_ENDDATE_PARAMETER);
1830 			}
1831 
1832 			g_clear_object (&iter);
1833 			g_clear_object (&recur);
1834 		}
1835 
1836 		g_object_unref (rule);
1837 	}
1838 
1839 	for (link = remove_props; link; link = g_slist_next (link)) {
1840 		prop = link->data;
1841 
1842 		i_cal_component_remove_property (icalcomp, prop);
1843 	}
1844 
1845 	g_slist_free_full (remove_props, g_object_unref);
1846 	g_clear_object (&dtstart);
1847 
1848 	return icalcomp;
1849 }
1850 
1851 typedef struct {
1852 	const ICalTime *rid;
1853 	gboolean matches;
1854 } CheckFirstInstanceData;
1855 
1856 static gboolean
check_first_instance_cb(ICalComponent * icalcomp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)1857 check_first_instance_cb (ICalComponent *icalcomp,
1858 			 ICalTime *instance_start,
1859 			 ICalTime *instance_end,
1860 			 gpointer user_data,
1861 			 GCancellable *cancellable,
1862 			 GError **error)
1863 {
1864 	CheckFirstInstanceData *ifs = user_data;
1865 	ICalProperty *prop;
1866 	ICalTime *rid;
1867 
1868 	g_return_val_if_fail (ifs != NULL, FALSE);
1869 
1870 	prop = i_cal_component_get_first_property (icalcomp, I_CAL_RECURRENCEID_PROPERTY);
1871 	if (prop) {
1872 		rid = i_cal_property_get_recurrenceid (prop);
1873 		g_object_unref (prop);
1874 	} else {
1875 		ICalTime *dtstart;
1876 		ICalTimezone *zone;
1877 
1878 		dtstart = i_cal_component_get_dtstart (icalcomp);
1879 		zone = i_cal_time_get_timezone (dtstart);
1880 
1881 		rid = i_cal_time_new_from_timet_with_zone (i_cal_time_as_timet (instance_start), i_cal_time_is_date (dtstart), zone);
1882 
1883 		g_clear_object (&dtstart);
1884 	}
1885 
1886 	ifs->matches = i_cal_time_compare ((ICalTime *) ifs->rid, rid) == 0;
1887 
1888 	g_clear_object (&rid);
1889 
1890 	return FALSE;
1891 }
1892 
1893 /**
1894  * e_cal_util_is_first_instance:
1895  * @comp: an #ECalComponent instance
1896  * @rid: a recurrence ID
1897  * @tz_cb: (closure tz_cb_data) (scope call): The #ECalRecurResolveTimezoneCb to call
1898  * @tz_cb_data: (closure): User data to be passed to the @tz_cb callback
1899  *
1900  * Returns whether the given @rid is the first instance of
1901  * the recurrence defined in the @comp.
1902  *
1903  * Return: Whether the @rid identifies the first instance of @comp.
1904  *
1905  * Since: 3.16
1906  **/
1907 gboolean
e_cal_util_is_first_instance(ECalComponent * comp,const ICalTime * rid,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data)1908 e_cal_util_is_first_instance (ECalComponent *comp,
1909 			      const ICalTime *rid,
1910 			      ECalRecurResolveTimezoneCb tz_cb,
1911 			      gpointer tz_cb_data)
1912 {
1913 	CheckFirstInstanceData ifs;
1914 	ICalComponent *icalcomp;
1915 	ICalTime *start, *end;
1916 
1917 	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
1918 	g_return_val_if_fail (rid && !i_cal_time_is_null_time ((ICalTime *) rid), FALSE);
1919 
1920 	ifs.rid = rid;
1921 	ifs.matches = FALSE;
1922 
1923 	icalcomp = e_cal_component_get_icalcomponent (comp);
1924 
1925 	start = i_cal_component_get_dtstart (icalcomp);
1926 	i_cal_time_adjust (start, -1, 0, 0, 0);
1927 
1928 	end = i_cal_component_get_dtend (icalcomp);
1929 	i_cal_time_adjust (end, +1, 0, 0, 0);
1930 
1931 	e_cal_recur_generate_instances_sync (e_cal_component_get_icalcomponent (comp),
1932 		start, end,
1933 		check_first_instance_cb, &ifs,
1934 		tz_cb, tz_cb_data, i_cal_timezone_get_utc_timezone (),
1935 		NULL, NULL);
1936 
1937 	g_clear_object (&start);
1938 	g_clear_object (&end);
1939 
1940 	return ifs.matches;
1941 }
1942 
1943 /**
1944  * e_cal_util_get_system_timezone_location:
1945  *
1946  * Fetches system timezone localtion string.
1947  *
1948  * Returns: (transfer full): system timezone location string, %NULL on an error.
1949  *
1950  * Since: 2.28
1951  **/
1952 gchar *
e_cal_util_get_system_timezone_location(void)1953 e_cal_util_get_system_timezone_location (void)
1954 {
1955 	return e_cal_system_timezone_get_location ();
1956 }
1957 
1958 /**
1959  * e_cal_util_get_system_timezone:
1960  *
1961  * Fetches system timezone ICalTimezone object.
1962  *
1963  * The returned pointer is part of the built-in timezones and should not be freed.
1964  *
1965  * Returns: (transfer none) (nullable): The ICalTimezone object of the system timezone, or %NULL on an error.
1966  *
1967  * Since: 2.28
1968  **/
1969 ICalTimezone *
e_cal_util_get_system_timezone(void)1970 e_cal_util_get_system_timezone (void)
1971 {
1972 	gchar *location;
1973 	ICalTimezone *zone;
1974 
1975 	location = e_cal_system_timezone_get_location ();
1976 
1977 	/* Can be NULL when failed to detect system time zone */
1978 	if (!location)
1979 		return NULL;
1980 
1981 	zone = i_cal_timezone_get_builtin_timezone (location);
1982 
1983 	g_free (location);
1984 
1985 	return zone;
1986 }
1987 
1988 static time_t
componenttime_to_utc_timet(const ECalComponentDateTime * dt_time,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data,const ICalTimezone * default_zone)1989 componenttime_to_utc_timet (const ECalComponentDateTime *dt_time,
1990                             ECalRecurResolveTimezoneCb tz_cb,
1991                             gpointer tz_cb_data,
1992                             const ICalTimezone *default_zone)
1993 {
1994 	ICalTime *value = NULL;
1995 	time_t timet = -1;
1996 
1997 	if (dt_time)
1998 		value = e_cal_component_datetime_get_value (dt_time);
1999 
2000 	if (value) {
2001 		ICalTimezone *zone = NULL;
2002 		const gchar *tzid = e_cal_component_datetime_get_tzid (dt_time);
2003 
2004 		if (tzid)
2005 			zone = tz_cb (tzid, tz_cb_data, NULL, NULL);
2006 
2007 		timet = i_cal_time_as_timet_with_zone (value, zone ? zone : (ICalTimezone *) default_zone);
2008 	}
2009 
2010 	return timet;
2011 }
2012 
2013 /**
2014  * e_cal_util_get_component_occur_times:
2015  * @comp: an #ECalComponent
2016  * @out_start: (out): Location to store the start time
2017  * @out_end: (out): Location to store the end time
2018  * @tz_cb: (closure tz_cb_data) (scope call): The #ECalRecurResolveTimezoneCb to call
2019  * @tz_cb_data: (closure): User data to be passed to the @tz_cb callback
2020  * @default_timezone: The default timezone
2021  * @kind: the type of component, indicated with an #ICalComponentKind
2022  *
2023  * Find out when the component starts and stops, being careful about
2024  * recurrences.
2025  *
2026  * Since: 2.32
2027  **/
2028 void
e_cal_util_get_component_occur_times(ECalComponent * comp,time_t * out_start,time_t * out_end,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data,const ICalTimezone * default_timezone,ICalComponentKind kind)2029 e_cal_util_get_component_occur_times (ECalComponent *comp,
2030                                       time_t *out_start,
2031                                       time_t *out_end,
2032                                       ECalRecurResolveTimezoneCb tz_cb,
2033                                       gpointer tz_cb_data,
2034                                       const ICalTimezone *default_timezone,
2035                                       ICalComponentKind kind)
2036 {
2037 	ICalTimezone *utc_zone;
2038 	ECalComponentDateTime *dtstart, *dtend;
2039 	time_t duration;
2040 
2041 	g_return_if_fail (comp != NULL);
2042 	g_return_if_fail (out_start != NULL);
2043 	g_return_if_fail (out_end != NULL);
2044 
2045 	utc_zone = i_cal_timezone_get_utc_timezone ();
2046 	e_cal_recur_ensure_end_dates (comp, FALSE, tz_cb, tz_cb_data, NULL, NULL);
2047 
2048 	/* Get dtstart of the component and convert it to UTC */
2049 	dtstart = e_cal_component_get_dtstart (comp);
2050 
2051 	if ((*out_start = componenttime_to_utc_timet (dtstart, tz_cb, tz_cb_data, default_timezone)) == -1)
2052 		*out_start = _TIME_MIN;
2053 
2054 	e_cal_component_datetime_free (dtstart);
2055 
2056 	dtend = e_cal_component_get_dtend (comp);
2057 	duration = componenttime_to_utc_timet (dtend, tz_cb, tz_cb_data, default_timezone);
2058 	if (duration <= 0 || *out_start == _TIME_MIN || *out_start > duration)
2059 		duration = 0;
2060 	else
2061 		duration = duration - *out_start;
2062 	e_cal_component_datetime_free (dtend);
2063 
2064 	/* find out end date of component */
2065 	*out_end = _TIME_MAX;
2066 
2067 	if (kind == I_CAL_VTODO_COMPONENT) {
2068 		/* max from COMPLETED and DUE properties */
2069 		ICalTime *tt;
2070 		time_t completed_time = -1, due_time = -1, max_time;
2071 		ECalComponentDateTime *dtdue;
2072 
2073 		tt = e_cal_component_get_completed (comp);
2074 		if (tt) {
2075 			/* COMPLETED must be in UTC. */
2076 			completed_time = i_cal_time_as_timet_with_zone (tt, utc_zone);
2077 			g_object_unref (tt);
2078 		}
2079 
2080 		dtdue = e_cal_component_get_due (comp);
2081 		if (dtdue)
2082 			due_time = componenttime_to_utc_timet (dtdue, tz_cb, tz_cb_data, default_timezone);
2083 
2084 		e_cal_component_datetime_free (dtdue);
2085 
2086 		max_time = MAX (completed_time, due_time);
2087 
2088 		if (max_time != -1)
2089 			*out_end = max_time;
2090 
2091 	} else {
2092 		/* ALARMS, EVENTS: DTEND and reccurences */
2093 
2094 		time_t may_end = _TIME_MIN;
2095 
2096 		if (e_cal_component_has_recurrences (comp)) {
2097 			GSList *rrules = NULL;
2098 			GSList *exrules = NULL;
2099 			GSList *rdates = NULL;
2100 			GSList *elem;
2101 
2102 			/* Do the RRULEs, EXRULEs and RDATEs*/
2103 			rrules = e_cal_component_get_rrule_properties (comp);
2104 			exrules = e_cal_component_get_exrule_properties (comp);
2105 			rdates = e_cal_component_get_rdates (comp);
2106 
2107 			for (elem = rrules; elem; elem = g_slist_next (elem)) {
2108 				ICalProperty *prop = elem->data;
2109 				ICalRecurrence *ir;
2110 				time_t rule_end;
2111 
2112 				ir = i_cal_property_get_rrule (prop);
2113 				rule_end = e_cal_recur_obtain_enddate (ir, prop, utc_zone, TRUE);
2114 
2115 				if (rule_end == -1) /* repeats forever */
2116 					may_end = _TIME_MAX;
2117 				else if (rule_end + duration > may_end) /* new maximum */
2118 					may_end = rule_end + duration;
2119 
2120 				g_clear_object (&ir);
2121 			}
2122 
2123 			/* Do the EXRULEs. */
2124 			for (elem = exrules; elem; elem = g_slist_next (elem)) {
2125 				ICalProperty *prop = elem->data;
2126 				ICalRecurrence *ir;
2127 				time_t rule_end;
2128 
2129 				ir = i_cal_property_get_exrule (prop);
2130 
2131 				rule_end = e_cal_recur_obtain_enddate (ir, prop, utc_zone, TRUE);
2132 
2133 				if (rule_end == -1) /* repeats forever */
2134 					may_end = _TIME_MAX;
2135 				else if (rule_end + duration > may_end)
2136 					may_end = rule_end + duration;
2137 
2138 				g_clear_object (&ir);
2139 			}
2140 
2141 			/* Do the RDATEs */
2142 			for (elem = rdates; elem; elem = g_slist_next (elem)) {
2143 				const ECalComponentPeriod *period = elem->data;
2144 				time_t rdate_end = _TIME_MAX;
2145 
2146 				/* FIXME: We currently assume RDATEs are in the same timezone
2147 				 * as DTSTART. We should get the RDATE timezone and convert
2148 				 * to the DTSTART timezone first. */
2149 
2150 				if (e_cal_component_period_get_kind (period) != E_CAL_COMPONENT_PERIOD_DATETIME) {
2151 					ICalTime *tt;
2152 					tt = i_cal_time_add (e_cal_component_period_get_start (period), e_cal_component_period_get_duration (period));
2153 					rdate_end = i_cal_time_as_timet (tt);
2154 					g_object_unref (tt);
2155 				} else if (e_cal_component_period_get_end (period)) {
2156 					rdate_end = i_cal_time_as_timet (e_cal_component_period_get_end (period));
2157 				} else {
2158 					rdate_end = (time_t) -1;
2159 				}
2160 
2161 				if (rdate_end == -1) /* repeats forever */
2162 					may_end = _TIME_MAX;
2163 				else if (rdate_end > may_end)
2164 					may_end = rdate_end;
2165 			}
2166 
2167 			g_slist_free_full (rrules, g_object_unref);
2168 			g_slist_free_full (exrules, g_object_unref);
2169 			g_slist_free_full (rdates, e_cal_component_period_free);
2170 		} else if (*out_start != _TIME_MIN) {
2171 			may_end = *out_start;
2172 		}
2173 
2174 		/* Get dtend of the component and convert it to UTC */
2175 		dtend = e_cal_component_get_dtend (comp);
2176 
2177 		if (dtend) {
2178 			time_t dtend_time;
2179 
2180 			dtend_time = componenttime_to_utc_timet (dtend, tz_cb, tz_cb_data, default_timezone);
2181 
2182 			if (dtend_time == -1 || (dtend_time > may_end))
2183 				may_end = dtend_time;
2184 		} else {
2185 			may_end = _TIME_MAX;
2186 		}
2187 
2188 		e_cal_component_datetime_free (dtend);
2189 
2190 		*out_end = may_end == _TIME_MIN ? _TIME_MAX : may_end;
2191 	}
2192 }
2193 
2194 /**
2195  * e_cal_util_component_has_x_property:
2196  * @icalcomp: an #ICalComponent
2197  * @x_name: name of the X property
2198  *
2199  * Returns, whether the @icalcomp contains X property named @x_name. To check
2200  * for standard property use e_cal_util_component_has_property().
2201  *
2202  * Returns: whether the @icalcomp contains X property named @x_name
2203  *
2204  * Since: 3.34
2205  **/
2206 gboolean
e_cal_util_component_has_x_property(ICalComponent * icalcomp,const gchar * x_name)2207 e_cal_util_component_has_x_property (ICalComponent *icalcomp,
2208 				     const gchar *x_name)
2209 {
2210 	ICalProperty *prop;
2211 
2212 	g_return_val_if_fail (I_CAL_IS_COMPONENT (icalcomp), FALSE);
2213 	g_return_val_if_fail (x_name != NULL, FALSE);
2214 
2215 	prop = e_cal_util_component_find_x_property (icalcomp, x_name);
2216 
2217 	if (!prop)
2218 		return FALSE;
2219 
2220 	g_object_unref (prop);
2221 
2222 	return TRUE;
2223 }
2224 
2225 
2226 /**
2227  * e_cal_util_component_find_x_property:
2228  * @icalcomp: an #ICalComponent
2229  * @x_name: name of the X property
2230  *
2231  * Searches for an X property named @x_name within X properties
2232  * of @icalcomp and returns it. Free the non-NULL object
2233  * with g_object_unref(), when no longer needed.
2234  *
2235  * Returns: (transfer full) (nullable): the first X ICalProperty named
2236  *    @x_name, or %NULL, when none found.
2237  *
2238  * Since: 3.34
2239  **/
2240 ICalProperty *
e_cal_util_component_find_x_property(ICalComponent * icalcomp,const gchar * x_name)2241 e_cal_util_component_find_x_property (ICalComponent *icalcomp,
2242 				      const gchar *x_name)
2243 {
2244 	ICalProperty *prop;
2245 
2246 	g_return_val_if_fail (icalcomp != NULL, NULL);
2247 	g_return_val_if_fail (x_name != NULL, NULL);
2248 
2249 	for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_X_PROPERTY);
2250 	     prop;
2251 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_X_PROPERTY)) {
2252 		const gchar *prop_name = i_cal_property_get_x_name (prop);
2253 
2254 		if (g_strcmp0 (prop_name, x_name) == 0)
2255 			break;
2256 	}
2257 
2258 	return prop;
2259 }
2260 
2261 /**
2262  * e_cal_util_component_dup_x_property:
2263  * @icalcomp: an #ICalComponent
2264  * @x_name: name of the X property
2265  *
2266  * Searches for an X property named @x_name within X properties
2267  * of @icalcomp and returns its value as a newly allocated string.
2268  * Free it with g_free(), when no longer needed.
2269  *
2270  * Returns: (nullable) (transfer full): Newly allocated value of the first @x_name
2271  *    X property in @icalcomp, or %NULL, if not found.
2272  *
2273  * Since: 3.34
2274  **/
2275 gchar *
e_cal_util_component_dup_x_property(ICalComponent * icalcomp,const gchar * x_name)2276 e_cal_util_component_dup_x_property (ICalComponent *icalcomp,
2277 				     const gchar *x_name)
2278 {
2279 	ICalProperty *prop;
2280 	gchar *x_value;
2281 
2282 	g_return_val_if_fail (icalcomp != NULL, NULL);
2283 	g_return_val_if_fail (x_name != NULL, NULL);
2284 
2285 	prop = e_cal_util_component_find_x_property (icalcomp, x_name);
2286 
2287 	if (!prop)
2288 		return NULL;
2289 
2290 	x_value = i_cal_property_get_value_as_string (prop);
2291 
2292 	g_object_unref (prop);
2293 
2294 	return x_value;
2295 }
2296 
2297 /**
2298  * e_cal_util_component_set_x_property:
2299  * @icalcomp: an #ICalComponent
2300  * @x_name: name of the X property
2301  * @value: (nullable): a value to set, or %NULL
2302  *
2303  * Sets a value of the first X property named @x_name in @icalcomp,
2304  * if any such already exists, or adds a new property with this name
2305  * and value. As a special case, if @value is %NULL, then removes
2306  * the first X property named @x_name from @icalcomp instead.
2307  *
2308  * Since: 3.34
2309  **/
2310 void
e_cal_util_component_set_x_property(ICalComponent * icalcomp,const gchar * x_name,const gchar * value)2311 e_cal_util_component_set_x_property (ICalComponent *icalcomp,
2312 				     const gchar *x_name,
2313 				     const gchar *value)
2314 {
2315 	ICalProperty *prop;
2316 
2317 	g_return_if_fail (icalcomp != NULL);
2318 	g_return_if_fail (x_name != NULL);
2319 
2320 	if (!value) {
2321 		e_cal_util_component_remove_x_property (icalcomp, x_name);
2322 		return;
2323 	}
2324 
2325 	prop = e_cal_util_component_find_x_property (icalcomp, x_name);
2326 	if (prop) {
2327 		i_cal_property_set_value_from_string (prop, value, "NO");
2328 		g_object_unref (prop);
2329 	} else {
2330 		prop = i_cal_property_new_x (value);
2331 		i_cal_property_set_x_name (prop, x_name);
2332 		i_cal_component_take_property (icalcomp, prop);
2333 	}
2334 }
2335 
2336 /**
2337  * e_cal_util_component_remove_x_property:
2338  * @icalcomp: an #ICalComponent
2339  * @x_name: name of the X property
2340  *
2341  * Removes the first X property named @x_name in @icalcomp.
2342  *
2343  * Returns: %TRUE, when any such had been found and removed, %FALSE otherwise.
2344  *
2345  * Since: 3.34
2346  **/
2347 gboolean
e_cal_util_component_remove_x_property(ICalComponent * icalcomp,const gchar * x_name)2348 e_cal_util_component_remove_x_property (ICalComponent *icalcomp,
2349 					const gchar *x_name)
2350 {
2351 	ICalProperty *prop;
2352 
2353 	g_return_val_if_fail (icalcomp != NULL, FALSE);
2354 	g_return_val_if_fail (x_name != NULL, FALSE);
2355 
2356 	prop = e_cal_util_component_find_x_property (icalcomp, x_name);
2357 	if (!prop)
2358 		return FALSE;
2359 
2360 	i_cal_component_remove_property (icalcomp, prop);
2361 	g_object_unref (prop);
2362 
2363 	return TRUE;
2364 }
2365 
2366 /**
2367  * e_cal_util_component_remove_property_by_kind:
2368  * @icalcomp: an #ICalComponent
2369  * @kind: the kind of the property to remove
2370  * @all: %TRUE to remove all, or %FALSE to remove only the first property of the @kind
2371  *
2372  * Removes all or only the first property of kind @kind in @icalcomp.
2373  *
2374  * Returns: How many properties had been removed.
2375  *
2376  * Since: 3.30
2377  **/
2378 guint
e_cal_util_component_remove_property_by_kind(ICalComponent * icalcomp,ICalPropertyKind kind,gboolean all)2379 e_cal_util_component_remove_property_by_kind (ICalComponent *icalcomp,
2380 					      ICalPropertyKind kind,
2381 					      gboolean all)
2382 {
2383 	ICalProperty *prop;
2384 	guint count = 0;
2385 
2386 	g_return_val_if_fail (icalcomp != NULL, 0);
2387 
2388 	while (prop = i_cal_component_get_first_property (icalcomp, kind), prop) {
2389 		i_cal_component_remove_property (icalcomp, prop);
2390 		g_object_unref (prop);
2391 
2392 		count++;
2393 
2394 		if (!all)
2395 			break;
2396 	}
2397 
2398 	return count;
2399 }
2400 
2401 typedef struct _NextOccurrenceData {
2402 	ICalTime *interval_start;
2403 	ICalTime *next;
2404 	gboolean found_next;
2405 	gboolean any_hit;
2406 } NextOccurrenceData;
2407 
2408 static gboolean
ecu_find_next_occurrence_cb(ICalComponent * comp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)2409 ecu_find_next_occurrence_cb (ICalComponent *comp,
2410 			     ICalTime *instance_start,
2411 			     ICalTime *instance_end,
2412 			     gpointer user_data,
2413 			     GCancellable *cancellable,
2414 			     GError **error)
2415 {
2416 	NextOccurrenceData *nod = user_data;
2417 
2418 	g_return_val_if_fail (nod != NULL, FALSE);
2419 
2420 	nod->any_hit = TRUE;
2421 
2422 	if (i_cal_time_compare (nod->interval_start, instance_start) < 0) {
2423 		g_clear_object (&nod->next);
2424 		nod->next = g_object_ref (instance_start);
2425 		nod->found_next = TRUE;
2426 		return FALSE;
2427 	}
2428 
2429 	return TRUE;
2430 }
2431 
2432 /* the returned FALSE means failure in timezone resolution, not in @out_time */
2433 static gboolean
e_cal_util_find_next_occurrence(ICalComponent * vtodo,ICalTime * for_time,ICalTime ** out_time,ECalClient * cal_client,GCancellable * cancellable,GError ** error)2434 e_cal_util_find_next_occurrence (ICalComponent *vtodo,
2435 				 ICalTime *for_time,
2436 				 ICalTime **out_time, /* set to NULL on failure */
2437 				 ECalClient *cal_client,
2438 				 GCancellable *cancellable,
2439 				 GError **error)
2440 {
2441 	NextOccurrenceData nod;
2442 	ICalTime *interval_start, *interval_end = NULL, *orig_dtstart, *orig_due;
2443 	gint advance_days = 8;
2444 	ICalProperty *prop;
2445 	gboolean success;
2446 	GError *local_error = NULL;
2447 
2448 	g_return_val_if_fail (vtodo != NULL, FALSE);
2449 	g_return_val_if_fail (out_time != NULL, FALSE);
2450 	g_return_val_if_fail (E_IS_CAL_CLIENT (cal_client), FALSE);
2451 
2452 	orig_dtstart = i_cal_component_get_dtstart (vtodo);
2453 	orig_due = i_cal_component_get_due (vtodo);
2454 
2455 	e_cal_util_component_remove_property_by_kind (vtodo, I_CAL_DUE_PROPERTY, TRUE);
2456 
2457 	if (for_time && !i_cal_time_is_null_time (for_time) && i_cal_time_is_valid_time (for_time)) {
2458 		i_cal_component_set_dtstart (vtodo, for_time);
2459 	}
2460 
2461 	interval_start = i_cal_component_get_dtstart (vtodo);
2462 	if (!interval_start || i_cal_time_is_null_time (interval_start) || !i_cal_time_is_valid_time (interval_start)) {
2463 		g_clear_object (&interval_start);
2464 		interval_start = i_cal_time_new_current_with_zone (e_cal_client_get_default_timezone (cal_client));
2465 	}
2466 
2467 	prop = i_cal_component_get_first_property (vtodo, I_CAL_RRULE_PROPERTY);
2468 	if (prop) {
2469 		ICalRecurrence *rrule;
2470 
2471 		rrule = i_cal_property_get_rrule (prop);
2472 
2473 		if (rrule) {
2474 			if (i_cal_recurrence_get_freq (rrule) == I_CAL_WEEKLY_RECURRENCE && i_cal_recurrence_get_interval (rrule) > 1)
2475 				advance_days = (i_cal_recurrence_get_interval (rrule) * 7) + 1;
2476 			else if (i_cal_recurrence_get_freq (rrule) == I_CAL_MONTHLY_RECURRENCE)
2477 				advance_days = (i_cal_recurrence_get_interval (rrule) >= 1 ? i_cal_recurrence_get_interval (rrule) * 31 : 31) + 1;
2478 			else if (i_cal_recurrence_get_freq (rrule) == I_CAL_YEARLY_RECURRENCE)
2479 				advance_days = (i_cal_recurrence_get_interval (rrule) >= 1 ? i_cal_recurrence_get_interval (rrule) * 365 : 365) + 2;
2480 		}
2481 
2482 		g_clear_object (&rrule);
2483 		g_clear_object (&prop);
2484 	}
2485 
2486 	nod.next = NULL;
2487 
2488 	do {
2489 		interval_end = i_cal_time_clone (interval_start);
2490 		i_cal_time_adjust (interval_end, advance_days, 0, 0, 0);
2491 
2492 		g_clear_object (&(nod.next));
2493 
2494 		nod.interval_start = interval_start;
2495 		nod.next = i_cal_time_new_null_time ();
2496 		nod.found_next = FALSE;
2497 		nod.any_hit = FALSE;
2498 
2499 		success = e_cal_recur_generate_instances_sync (vtodo, interval_start, interval_end,
2500 			ecu_find_next_occurrence_cb, &nod,
2501 			e_cal_client_tzlookup_cb, cal_client,
2502 			e_cal_client_get_default_timezone (cal_client),
2503 			cancellable, &local_error) || nod.found_next;
2504 
2505 		g_object_unref (interval_start);
2506 		interval_start = interval_end;
2507 		interval_end = NULL;
2508 		i_cal_time_adjust (interval_start, -1, 0, 0, 0);
2509 
2510 	} while (!local_error && !g_cancellable_is_cancelled (cancellable) && !nod.found_next && nod.any_hit);
2511 
2512 	if (success)
2513 		*out_time = (nod.next && !i_cal_time_is_null_time (nod.next)) ? g_object_ref (nod.next) : NULL;
2514 
2515 	if (local_error)
2516 		g_propagate_error (error, local_error);
2517 
2518 	if (for_time && !i_cal_time_is_null_time (for_time) && i_cal_time_is_valid_time (for_time)) {
2519 		if (!orig_dtstart || i_cal_time_is_null_time (orig_dtstart) || !i_cal_time_is_valid_time (orig_dtstart))
2520 			e_cal_util_component_remove_property_by_kind (vtodo, I_CAL_DTSTART_PROPERTY, FALSE);
2521 		else
2522 			i_cal_component_set_dtstart (vtodo, orig_dtstart);
2523 	}
2524 
2525 	if (!orig_due || i_cal_time_is_null_time (orig_due) || !i_cal_time_is_valid_time (orig_due))
2526 		e_cal_util_component_remove_property_by_kind (vtodo, I_CAL_DUE_PROPERTY, FALSE);
2527 	else
2528 		i_cal_component_set_due (vtodo, orig_due);
2529 
2530 	g_clear_object (&interval_start);
2531 	g_clear_object (&interval_end);
2532 	g_clear_object (&orig_dtstart);
2533 	g_clear_object (&orig_due);
2534 	g_clear_object (&(nod.next));
2535 
2536 	return success;
2537 }
2538 
2539 /**
2540  * e_cal_util_init_recur_task_sync:
2541  * @vtodo: a VTODO component
2542  * @cal_client: (type ECalClient): an #ECalClient to which the @vtodo belongs
2543  * @cancellable: optional #GCancellable object, or %NULL
2544  * @error: return location for a #GError, or %NULL
2545  *
2546  * Initializes properties of a recurring @vtodo, like normalizing
2547  * the Due date and eventually the Start date. The function does
2548  * nothing when the @vtodo is not recurring.
2549  *
2550  * The function doesn't change LAST-MODIFIED neither the SEQUENCE
2551  * property, it's up to the caller to do it.
2552  *
2553  * Note the @cal_client, @cancellable and @error is used only
2554  * for timezone resolution. The function doesn't store the @vtodo
2555  * to the @cal_client, it only updates the @vtodo component.
2556  *
2557  * Returns: Whether succeeded.
2558  *
2559  * Since: 3.30
2560  **/
2561 gboolean
e_cal_util_init_recur_task_sync(ICalComponent * vtodo,ECalClient * cal_client,GCancellable * cancellable,GError ** error)2562 e_cal_util_init_recur_task_sync (ICalComponent *vtodo,
2563 				 ECalClient *cal_client,
2564 				 GCancellable *cancellable,
2565 				 GError **error)
2566 {
2567 	ICalTime *dtstart, *due;
2568 	gboolean success = TRUE;
2569 
2570 	g_return_val_if_fail (vtodo != NULL, FALSE);
2571 	g_return_val_if_fail (i_cal_component_isa (vtodo) == I_CAL_VTODO_COMPONENT, FALSE);
2572 	g_return_val_if_fail (E_IS_CAL_CLIENT (cal_client), FALSE);
2573 
2574 	if (!e_cal_util_component_has_recurrences (vtodo))
2575 		return TRUE;
2576 
2577 	/* DTSTART is required for recurring components */
2578 	dtstart = i_cal_component_get_dtstart (vtodo);
2579 	if (!dtstart || i_cal_time_is_null_time (dtstart) || !i_cal_time_is_valid_time (dtstart)) {
2580 		g_clear_object (&dtstart);
2581 
2582 		dtstart = i_cal_time_new_current_with_zone (e_cal_client_get_default_timezone (cal_client));
2583 		i_cal_component_set_dtstart (vtodo, dtstart);
2584 	}
2585 
2586 	due = i_cal_component_get_due (vtodo);
2587 	if (!due || i_cal_time_is_null_time (due) || !i_cal_time_is_valid_time (due) ||
2588 	    i_cal_time_compare (dtstart, due) < 0) {
2589 		g_clear_object (&due);
2590 
2591 		success = e_cal_util_find_next_occurrence (vtodo, NULL, &due, cal_client, cancellable, error);
2592 
2593 		if (due && !i_cal_time_is_null_time (due) && i_cal_time_is_valid_time (due))
2594 			i_cal_component_set_due (vtodo, due);
2595 	}
2596 
2597 	g_clear_object (&dtstart);
2598 	g_clear_object (&due);
2599 
2600 	return success;
2601 }
2602 
2603 /**
2604  * e_cal_util_mark_task_complete_sync:
2605  * @vtodo: a VTODO component
2606  * @completed_time: completed time to set, or (time_t) -1 to use current time
2607  * @cal_client: (type ECalClient): an #ECalClient to which the @vtodo belongs
2608  * @cancellable: optional #GCancellable object, or %NULL
2609  * @error: return location for a #GError, or %NULL
2610  *
2611  * Marks the @vtodo as complete with eventual update of other
2612  * properties. This is useful also for recurring tasks, for which
2613  * it moves the @vtodo into the next occurrence according to
2614  * the recurrence rule.
2615  *
2616  * When the @vtodo is marked as completed, then the existing COMPLETED
2617  * date-time is preserved if exists, otherwise it's set either to @completed_time,
2618  * or to the current time, when the @completed_time is (time_t) -1.
2619  *
2620  * The function doesn't change LAST-MODIFIED neither the SEQUENCE
2621  * property, it's up to the caller to do it.
2622  *
2623  * Note the @cal_client, @cancellable and @error is used only
2624  * for timezone resolution. The function doesn't store the @vtodo
2625  * to the @cal_client, it only updates the @vtodo component.
2626  *
2627  * Returns: Whether succeeded.
2628  *
2629  * Since: 3.30
2630  **/
2631 gboolean
e_cal_util_mark_task_complete_sync(ICalComponent * vtodo,time_t completed_time,ECalClient * cal_client,GCancellable * cancellable,GError ** error)2632 e_cal_util_mark_task_complete_sync (ICalComponent *vtodo,
2633 				    time_t completed_time,
2634 				    ECalClient *cal_client,
2635 				    GCancellable *cancellable,
2636 				    GError **error)
2637 {
2638 	ICalProperty *prop;
2639 
2640 	g_return_val_if_fail (vtodo != NULL, FALSE);
2641 	g_return_val_if_fail (i_cal_component_isa (vtodo) == I_CAL_VTODO_COMPONENT, FALSE);
2642 	g_return_val_if_fail (E_IS_CAL_CLIENT (cal_client), FALSE);
2643 
2644 	if (e_cal_util_component_has_recurrences (vtodo) &&
2645 	    !e_client_check_capability (E_CLIENT (cal_client), E_CAL_STATIC_CAPABILITY_TASK_HANDLE_RECUR)) {
2646 		gboolean is_last = FALSE, change_count = FALSE;
2647 		ICalTime *new_dtstart = NULL, *new_due = NULL;
2648 
2649 		for (prop = i_cal_component_get_first_property (vtodo, I_CAL_RRULE_PROPERTY);
2650 		     prop && !is_last;
2651 		     g_object_unref (prop), prop = i_cal_component_get_next_property (vtodo, I_CAL_RRULE_PROPERTY)) {
2652 			ICalRecurrence *rrule;
2653 
2654 			rrule = i_cal_property_get_rrule (prop);
2655 
2656 			if (rrule && i_cal_recurrence_get_interval (rrule) > 0) {
2657 				gint count = i_cal_recurrence_get_count (rrule);
2658 
2659 				if (count > 0) {
2660 					is_last = count == 1;
2661 					change_count = TRUE;
2662 				}
2663 			}
2664 
2665 			g_clear_object (&rrule);
2666 		}
2667 
2668 		g_clear_object (&prop);
2669 
2670 		if (!is_last) {
2671 			if (!e_cal_util_find_next_occurrence (vtodo, NULL, &new_dtstart, cal_client, cancellable, error)) {
2672 				g_clear_object (&new_dtstart);
2673 				return FALSE;
2674 			}
2675 
2676 			if (new_dtstart && !i_cal_time_is_null_time (new_dtstart) && i_cal_time_is_valid_time (new_dtstart)) {
2677 				ICalTime *old_dtstart, *old_due;
2678 
2679 				old_dtstart = i_cal_component_get_dtstart (vtodo);
2680 				old_due = i_cal_component_get_due (vtodo);
2681 
2682 				/* Move relatively also the DUE date, to keep the difference... */
2683 				if (old_due && !i_cal_time_is_null_time (old_due) && i_cal_time_is_valid_time (old_due)) {
2684 					if (old_dtstart && !i_cal_time_is_null_time (old_dtstart) && i_cal_time_is_valid_time (old_dtstart)) {
2685 						gint64 diff;
2686 
2687 						diff = i_cal_time_as_timet (old_due) - i_cal_time_as_timet (old_dtstart);
2688 						new_due = i_cal_time_clone (new_dtstart);
2689 						i_cal_time_adjust (new_due, diff / (24 * 60 * 60), (diff / (60 * 60)) % 24,
2690 							(diff / 60) % 60, diff % 60);
2691 					} else if (!e_cal_util_find_next_occurrence (vtodo, old_due, &new_due, cal_client, cancellable, error)) {
2692 						g_clear_object (&new_dtstart);
2693 						g_clear_object (&new_due);
2694 						g_clear_object (&old_dtstart);
2695 						g_clear_object (&old_due);
2696 						return FALSE;
2697 					}
2698 				}
2699 
2700 				g_clear_object (&old_dtstart);
2701 
2702 				/* ...  otherwise set the new DUE as the next-next-DTSTART ... */
2703 				if (!new_due || i_cal_time_is_null_time (new_due) || !i_cal_time_is_valid_time (new_due)) {
2704 					g_clear_object (&new_due);
2705 
2706 					if (!e_cal_util_find_next_occurrence (vtodo, new_dtstart, &new_due, cal_client, cancellable, error)) {
2707 						g_clear_object (&new_dtstart);
2708 						g_clear_object (&new_due);
2709 						g_clear_object (&old_due);
2710 						return FALSE;
2711 					}
2712 				}
2713 
2714 				/* ... eventually fallback to the new DTSTART for the new DUE */
2715 				if (!new_due || i_cal_time_is_null_time (new_due) || !i_cal_time_is_valid_time (new_due)) {
2716 					g_clear_object (&new_due);
2717 					new_due = i_cal_time_clone (new_dtstart);
2718 				}
2719 
2720 				g_clear_object (&old_due);
2721 			}
2722 		}
2723 
2724 		if (!is_last && new_dtstart && new_due &&
2725 		    !i_cal_time_is_null_time (new_dtstart) && i_cal_time_is_valid_time (new_dtstart) &&
2726 		    !i_cal_time_is_null_time (new_due) && i_cal_time_is_valid_time (new_due)) {
2727 			/* Move to the next occurrence */
2728 			if (change_count) {
2729 				for (prop = i_cal_component_get_first_property (vtodo, I_CAL_RRULE_PROPERTY);
2730 				     prop;
2731 				     g_object_unref (prop), prop = i_cal_component_get_next_property (vtodo, I_CAL_RRULE_PROPERTY)) {
2732 					ICalRecurrence *rrule;
2733 
2734 					rrule = i_cal_property_get_rrule (prop);
2735 
2736 					if (rrule && i_cal_recurrence_get_interval (rrule) > 0) {
2737 						gint count = i_cal_recurrence_get_count (rrule);
2738 
2739 						if (count > 0) {
2740 							i_cal_recurrence_set_count (rrule, count - 1);
2741 							i_cal_property_set_rrule (prop, rrule);
2742 						}
2743 					}
2744 
2745 					g_clear_object (&rrule);
2746 				}
2747 			}
2748 
2749 			i_cal_component_set_dtstart (vtodo, new_dtstart);
2750 			i_cal_component_set_due (vtodo, new_due);
2751 
2752 			e_cal_util_component_remove_property_by_kind (vtodo, I_CAL_COMPLETED_PROPERTY, TRUE);
2753 
2754 			prop = i_cal_component_get_first_property (vtodo, I_CAL_PERCENTCOMPLETE_PROPERTY);
2755 			if (prop) {
2756 				i_cal_property_set_percentcomplete (prop, 0);
2757 				g_object_unref (prop);
2758 			}
2759 
2760 			prop = i_cal_component_get_first_property (vtodo, I_CAL_STATUS_PROPERTY);
2761 			if (prop) {
2762 				i_cal_property_set_status (prop, I_CAL_STATUS_NEEDSACTION);
2763 				g_object_unref (prop);
2764 			}
2765 
2766 			g_clear_object (&new_dtstart);
2767 			g_clear_object (&new_due);
2768 
2769 			return TRUE;
2770 		}
2771 	}
2772 
2773 	prop = i_cal_component_get_first_property (vtodo, I_CAL_COMPLETED_PROPERTY);
2774 	if (prop) {
2775 		g_object_unref (prop);
2776 	} else {
2777 		ICalTime *tt;
2778 
2779 		tt = completed_time != (time_t) -1 ?
2780 			i_cal_time_new_from_timet_with_zone (completed_time, FALSE, i_cal_timezone_get_utc_timezone ()) :
2781 			i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
2782 		prop = i_cal_property_new_completed (tt);
2783 		i_cal_component_take_property (vtodo, prop);
2784 		g_object_unref (tt);
2785 	}
2786 
2787 	prop = i_cal_component_get_first_property (vtodo, I_CAL_PERCENTCOMPLETE_PROPERTY);
2788 	if (prop) {
2789 		i_cal_property_set_percentcomplete (prop, 100);
2790 		g_object_unref (prop);
2791 	} else {
2792 		prop = i_cal_property_new_percentcomplete (100);
2793 		i_cal_component_take_property (vtodo, prop);
2794 	}
2795 
2796 	prop = i_cal_component_get_first_property (vtodo, I_CAL_STATUS_PROPERTY);
2797 	if (prop) {
2798 		i_cal_property_set_status (prop, I_CAL_STATUS_COMPLETED);
2799 		g_object_unref (prop);
2800 	} else {
2801 		prop = i_cal_property_new_status (I_CAL_STATUS_COMPLETED);
2802 		i_cal_component_take_property (vtodo, prop);
2803 	}
2804 
2805 	return TRUE;
2806 }
2807 
2808 /**
2809  * e_cal_util_operation_flags_to_conflict_resolution:
2810  * @flags: bit-or of #ECalOperationFlags
2811  *
2812  * Decodes the #EConflictResolution from the bit-or of #ECalOperationFlags.
2813  *
2814  * Returns: an #EConflictResolution as stored in the @flags
2815  *
2816  * Since: 3.34
2817  **/
2818 EConflictResolution
e_cal_util_operation_flags_to_conflict_resolution(guint32 flags)2819 e_cal_util_operation_flags_to_conflict_resolution (guint32 flags)
2820 {
2821 	if ((flags & E_CAL_OPERATION_FLAG_CONFLICT_FAIL) != 0)
2822 		return E_CONFLICT_RESOLUTION_FAIL;
2823 	else if ((flags & E_CAL_OPERATION_FLAG_CONFLICT_USE_NEWER) != 0)
2824 		return E_CONFLICT_RESOLUTION_USE_NEWER;
2825 	else if ((flags & E_CAL_OPERATION_FLAG_CONFLICT_KEEP_SERVER) != 0)
2826 		return E_CONFLICT_RESOLUTION_KEEP_SERVER;
2827 	else if ((flags & E_CAL_OPERATION_FLAG_CONFLICT_WRITE_COPY) != 0)
2828 		return E_CONFLICT_RESOLUTION_WRITE_COPY;
2829 
2830 	/* E_CAL_OPERATION_FLAG_CONFLICT_KEEP_LOCAL is the default */
2831 	return E_CONFLICT_RESOLUTION_KEEP_LOCAL;
2832 }
2833 
2834 /**
2835  * e_cal_util_conflict_resolution_to_operation_flags:
2836  * @conflict_resolution: an #EConflictResolution
2837  *
2838  * Encodes the #EConflictResolution into the bit-or of #ECalOperationFlags.
2839  * The returned value can be bit-or-ed with other #ECalOperationFlags values.
2840  *
2841  * Returns: a bit-or of #ECalOperationFlags, corresponding to the @conflict_resolution
2842  *
2843  * Since: 3.34
2844  **/
2845 guint32
e_cal_util_conflict_resolution_to_operation_flags(EConflictResolution conflict_resolution)2846 e_cal_util_conflict_resolution_to_operation_flags (EConflictResolution conflict_resolution)
2847 {
2848 	switch (conflict_resolution) {
2849 	case E_CONFLICT_RESOLUTION_FAIL:
2850 		return E_CAL_OPERATION_FLAG_CONFLICT_FAIL;
2851 	case E_CONFLICT_RESOLUTION_USE_NEWER:
2852 		return E_CAL_OPERATION_FLAG_CONFLICT_USE_NEWER;
2853 	case E_CONFLICT_RESOLUTION_KEEP_SERVER:
2854 		return E_CAL_OPERATION_FLAG_CONFLICT_KEEP_SERVER;
2855 	case E_CONFLICT_RESOLUTION_KEEP_LOCAL:
2856 		return E_CAL_OPERATION_FLAG_CONFLICT_KEEP_LOCAL;
2857 	case E_CONFLICT_RESOLUTION_WRITE_COPY:
2858 		return E_CAL_OPERATION_FLAG_CONFLICT_WRITE_COPY;
2859 	}
2860 
2861 	return E_CAL_OPERATION_FLAG_CONFLICT_KEEP_LOCAL;
2862 }
2863 
2864 static void
ecu_remove_all_but_filename_parameter(ICalProperty * prop)2865 ecu_remove_all_but_filename_parameter (ICalProperty *prop)
2866 {
2867 	ICalParameter *param;
2868 
2869 	g_return_if_fail (prop != NULL);
2870 
2871 	while (param = i_cal_property_get_first_parameter (prop, I_CAL_ANY_PARAMETER), param) {
2872 		if (i_cal_parameter_isa (param) == I_CAL_FILENAME_PARAMETER) {
2873 			g_object_unref (param);
2874 			param = i_cal_property_get_next_parameter (prop, I_CAL_ANY_PARAMETER);
2875 			if (!param)
2876 				break;
2877 		}
2878 
2879 		i_cal_property_remove_parameter_by_ref (prop, param);
2880 		g_object_unref (param);
2881 	}
2882 }
2883 
2884 /**
2885  * e_cal_util_inline_local_attachments_sync:
2886  * @component: an #ICalComponent to work with
2887  * @cancellable: optional #GCancellable object, or %NULL
2888  * @error: return location for a #GError, or %NULL
2889  *
2890  * Changes all URL attachments which point to a local file in @component
2891  * to inline attachments, aka adds the file content into the @component.
2892  * It also populates FILENAME parameter on the attachment.
2893  *
2894  * Returns: Whether succeeded.
2895  *
2896  * Since: 3.40
2897  **/
2898 gboolean
e_cal_util_inline_local_attachments_sync(ICalComponent * component,GCancellable * cancellable,GError ** error)2899 e_cal_util_inline_local_attachments_sync (ICalComponent *component,
2900 					  GCancellable *cancellable,
2901 					  GError **error)
2902 {
2903 	ICalProperty *prop;
2904 	const gchar *uid;
2905 	gboolean success = TRUE;
2906 
2907 	g_return_val_if_fail (component != NULL, FALSE);
2908 
2909 	uid = i_cal_component_get_uid (component);
2910 
2911 	for (prop = i_cal_component_get_first_property (component, I_CAL_ATTACH_PROPERTY);
2912 	     prop && success;
2913 	     g_object_unref (prop), prop = i_cal_component_get_next_property (component, I_CAL_ATTACH_PROPERTY)) {
2914 		ICalAttach *attach;
2915 
2916 		attach = i_cal_property_get_attach (prop);
2917 		if (attach && i_cal_attach_get_is_url (attach)) {
2918 			const gchar *url;
2919 
2920 			url = i_cal_attach_get_url (attach);
2921 			if (g_str_has_prefix (url, "file://")) {
2922 				GFile *file;
2923 				gchar *basename;
2924 				gchar *content;
2925 				gsize len;
2926 
2927 				file = g_file_new_for_uri (url);
2928 				basename = g_file_get_basename (file);
2929 				if (g_file_load_contents (file, cancellable, &content, &len, NULL, error)) {
2930 					ICalAttach *new_attach;
2931 					ICalParameter *param;
2932 					gchar *base64;
2933 
2934 					base64 = g_base64_encode ((const guchar *) content, len);
2935 					new_attach = i_cal_attach_new_from_data (base64, (GFunc) g_free, NULL);
2936 					g_free (content);
2937 
2938 					ecu_remove_all_but_filename_parameter (prop);
2939 
2940 					i_cal_property_set_attach (prop, new_attach);
2941 					g_object_unref (new_attach);
2942 
2943 					param = i_cal_parameter_new_value (I_CAL_VALUE_BINARY);
2944 					i_cal_property_take_parameter (prop, param);
2945 
2946 					param = i_cal_parameter_new_encoding (I_CAL_ENCODING_BASE64);
2947 					i_cal_property_take_parameter (prop, param);
2948 
2949 					/* Preserve existing FILENAME parameter */
2950 					if (!e_cal_util_property_has_parameter (prop, I_CAL_FILENAME_PARAMETER)) {
2951 						const gchar *use_filename = basename;
2952 
2953 						/* generated filename by Evolution */
2954 						if (uid && g_str_has_prefix (use_filename, uid) &&
2955 						    use_filename[strlen (uid)] == '-') {
2956 							use_filename += strlen (uid) + 1;
2957 						}
2958 
2959 						param = i_cal_parameter_new_filename (use_filename);
2960 						i_cal_property_take_parameter (prop, param);
2961 					}
2962 				} else {
2963 					success = FALSE;
2964 				}
2965 
2966 				g_object_unref (file);
2967 				g_free (basename);
2968 			}
2969 		}
2970 
2971 		g_clear_object (&attach);
2972 	}
2973 
2974 	g_clear_object (&prop);
2975 
2976 	return success;
2977 }
2978 
2979 /**
2980  * e_cal_util_set_alarm_acknowledged:
2981  * @component: an #ECalComponent
2982  * @auid: an alarm UID to modify
2983  * @when: a time, in UTC, when to set the acknowledged property, or 0 for the current time
2984  *
2985  * Sets the ACKNOWLEDGED property on the @component's alarm with UID @auid
2986  * to the time @when (in UTC), or to the current time, when the @when is 0.
2987  *
2988  * Returns: Whether succeeded.
2989  *
2990  * Since: 3.40
2991  **/
2992 gboolean
e_cal_util_set_alarm_acknowledged(ECalComponent * component,const gchar * auid,gint64 when)2993 e_cal_util_set_alarm_acknowledged (ECalComponent *component,
2994 				   const gchar *auid,
2995 				   gint64 when)
2996 {
2997 	ECalComponentAlarm *alarm, *copy;
2998 	ICalTime *tt;
2999 
3000 	g_return_val_if_fail (E_IS_CAL_COMPONENT (component), FALSE);
3001 	g_return_val_if_fail (auid != NULL, FALSE);
3002 
3003 	alarm = e_cal_component_get_alarm (component, auid);
3004 
3005 	if (!alarm)
3006 		return FALSE;
3007 
3008 	if (when)
3009 		tt = i_cal_time_new_from_timet_with_zone (when, 0, i_cal_timezone_get_utc_timezone ());
3010 	else
3011 		tt = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
3012 
3013 	copy = e_cal_component_alarm_copy (alarm);
3014 
3015 	e_cal_component_alarm_take_acknowledged (copy, tt);
3016 	e_cal_component_remove_alarm (component, auid);
3017 	e_cal_component_add_alarm (component, copy);
3018 
3019 	e_cal_component_alarm_free (copy);
3020 	e_cal_component_alarm_free (alarm);
3021 
3022 	return TRUE;
3023 }
3024 
3025 static void
e_cal_util_clamp_vtimezone_subcomps(ICalComponent * vtimezone,ICalComponentKind kind,const ICalTime * from,const ICalTime * to)3026 e_cal_util_clamp_vtimezone_subcomps (ICalComponent *vtimezone,
3027 				     ICalComponentKind kind,
3028 				     const ICalTime *from,
3029 				     const ICalTime *to)
3030 {
3031 	ICalComponent *subcomp;
3032 	ICalComponent *nearest_from_comp = NULL, *nearest_to_comp = NULL;
3033 	ICalTime *nearest_from_time = NULL, *nearest_to_time = NULL;
3034 	GSList *remove = NULL, *link;
3035 
3036 	for (subcomp = i_cal_component_get_first_component (vtimezone, kind);
3037 	     subcomp;
3038 	     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (vtimezone, kind)) {
3039 		ICalTime *dtstart;
3040 
3041 		dtstart = i_cal_component_get_dtstart (subcomp);
3042 		if (dtstart && !i_cal_time_is_null_time (dtstart) && i_cal_time_is_valid_time (dtstart)) {
3043 			gint cmp;
3044 
3045 			cmp = i_cal_time_compare (dtstart, from);
3046 			if (cmp < 0) {
3047 				if (nearest_from_time) {
3048 					if (i_cal_time_compare (dtstart, nearest_from_time) > 0) {
3049 						g_clear_object (&nearest_from_time);
3050 						nearest_from_time = g_object_ref (dtstart);
3051 						remove = g_slist_prepend (remove, nearest_from_comp);
3052 						nearest_from_comp = g_object_ref (subcomp);
3053 					} else {
3054 						remove = g_slist_prepend (remove, g_object_ref (subcomp));
3055 					}
3056 				} else {
3057 					nearest_from_time = g_object_ref (dtstart);
3058 					nearest_from_comp = g_object_ref (subcomp);
3059 				}
3060 			} else if (cmp > 0 && to) {
3061 				cmp = i_cal_time_compare (to, dtstart);
3062 				if (cmp < 0)
3063 					remove = g_slist_prepend (remove, g_object_ref (subcomp));
3064 			}
3065 		}
3066 
3067 		g_clear_object (&dtstart);
3068 	}
3069 
3070 	g_clear_object (&nearest_from_comp);
3071 	g_clear_object (&nearest_from_time);
3072 	g_clear_object (&nearest_to_comp);
3073 	g_clear_object (&nearest_to_time);
3074 
3075 	for (link = remove; link; link = g_slist_next (link)) {
3076 		subcomp = link->data;
3077 
3078 		i_cal_component_remove_component (vtimezone, subcomp);
3079 	}
3080 
3081 	g_slist_free_full (remove, g_object_unref);
3082 }
3083 
3084 /**
3085  * e_cal_util_clamp_vtimezone:
3086  * @vtimezone: (inout): a VTIMEZONE component to modify
3087  * @from: an #ICalTime for the minimum time
3088  * @to: (nullable): until which time to clamp, or %NULL for infinity
3089  *
3090  * Modifies the @vtimezone to include only subcomponents influencing
3091  * the passed-in time interval between @from and @to.
3092  *
3093  * Since: 3.40
3094  **/
3095 void
e_cal_util_clamp_vtimezone(ICalComponent * vtimezone,const ICalTime * from,const ICalTime * to)3096 e_cal_util_clamp_vtimezone (ICalComponent *vtimezone,
3097 			    const ICalTime *from,
3098 			    const ICalTime *to)
3099 {
3100 	g_return_if_fail (I_CAL_IS_COMPONENT (vtimezone));
3101 	g_return_if_fail (i_cal_component_isa (vtimezone) == I_CAL_VTIMEZONE_COMPONENT);
3102 	g_return_if_fail (I_CAL_IS_TIME ((ICalTime *) from));
3103 	if (to)
3104 		g_return_if_fail (I_CAL_IS_TIME ((ICalTime *) to));
3105 
3106 	e_cal_util_clamp_vtimezone_subcomps (vtimezone, I_CAL_XSTANDARD_COMPONENT, from, to);
3107 	e_cal_util_clamp_vtimezone_subcomps (vtimezone, I_CAL_XDAYLIGHT_COMPONENT, from, to);
3108 }
3109 
3110 /**
3111  * e_cal_util_clamp_vtimezone_by_component:
3112  * @vtimezone: (inout): a VTIMEZONE component to modify
3113  * @component: an #ICalComponent to read the times from
3114  *
3115  * Similar to e_cal_util_clamp_vtimezone(), only reads the clamp
3116  * times from the @component.
3117  *
3118  * Since: 3.40
3119  **/
3120 void
e_cal_util_clamp_vtimezone_by_component(ICalComponent * vtimezone,ICalComponent * component)3121 e_cal_util_clamp_vtimezone_by_component (ICalComponent *vtimezone,
3122 					 ICalComponent *component)
3123 {
3124 	ICalProperty *prop;
3125 	ICalTime *dtstart, *dtend = NULL;
3126 
3127 	g_return_if_fail (I_CAL_IS_COMPONENT (vtimezone));
3128 	g_return_if_fail (i_cal_component_isa (vtimezone) == I_CAL_VTIMEZONE_COMPONENT);
3129 	g_return_if_fail (I_CAL_IS_COMPONENT (component));
3130 
3131 	dtstart = i_cal_component_get_dtstart (component);
3132 	if (!dtstart)
3133 		return;
3134 
3135 	prop = i_cal_component_get_first_property (component, I_CAL_RECURRENCEID_PROPERTY);
3136 	if (prop) {
3137 		ICalTime *recurid;
3138 
3139 		recurid = i_cal_property_get_recurrenceid (prop);
3140 
3141 		dtend = i_cal_component_get_dtend (component);
3142 		if (dtend && i_cal_time_compare (recurid, dtend) >= 0) {
3143 			g_clear_object (&dtend);
3144 			dtend = recurid;
3145 			recurid = NULL;
3146 		}
3147 
3148 		g_clear_object (&recurid);
3149 		g_object_unref (prop);
3150 	} else if (!e_cal_util_component_has_rrules (component)) {
3151 		dtend = i_cal_component_get_dtend (component);
3152 		if (!dtend)
3153 			dtend = g_object_ref (dtstart);
3154 	}
3155 
3156 	e_cal_util_clamp_vtimezone (vtimezone, dtstart, dtend);
3157 
3158 	g_clear_object (&dtstart);
3159 	g_clear_object (&dtend);
3160 }
3161