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