1 /*
2  * Evolution calendar - Utilities for manipulating ECalComponent objects
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  *
17  * Authors:
18  *		Federico Mena-Quintero <federico@ximian.com>
19  *
20  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21  *
22  */
23 
24 #include "evolution-config.h"
25 
26 #include <glib/gi18n-lib.h>
27 
28 #include <string.h>
29 #include <time.h>
30 
31 #include "calendar-config.h"
32 #include "comp-util.h"
33 #include "e-calendar-view.h"
34 #include "itip-utils.h"
35 
36 #include "shell/e-shell-window.h"
37 #include "shell/e-shell-view.h"
38 
39 /**
40  * cal_comp_util_add_exdate:
41  * @comp: A calendar component object.
42  * @t: Time for the exception.
43  * @zone: an #ICalTimezone
44  *
45  * Adds an exception date to the current list of EXDATE properties in a calendar
46  * component object.
47  **/
48 void
cal_comp_util_add_exdate(ECalComponent * comp,time_t t,ICalTimezone * zone)49 cal_comp_util_add_exdate (ECalComponent *comp,
50 			  time_t t,
51 			  ICalTimezone *zone)
52 {
53 	GSList *exdates;
54 	ECalComponentDateTime *cdt;
55 
56 	g_return_if_fail (comp != NULL);
57 	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
58 
59 	exdates = e_cal_component_get_exdates (comp);
60 
61 	cdt = e_cal_component_datetime_new_take (i_cal_time_new_from_timet_with_zone (t, FALSE, zone),
62 		zone ? g_strdup (i_cal_timezone_get_tzid (zone)) : NULL);
63 
64 	exdates = g_slist_append (exdates, cdt);
65 	e_cal_component_set_exdates (comp, exdates);
66 
67 	g_slist_free_full (exdates, e_cal_component_datetime_free);
68 }
69 
70 /* Returns TRUE if the TZIDs are equivalent, i.e. both NULL or the same. */
71 static gboolean
cal_comp_util_tzid_equal(const gchar * tzid1,const gchar * tzid2)72 cal_comp_util_tzid_equal (const gchar *tzid1,
73 			  const gchar *tzid2)
74 {
75 	gboolean retval = TRUE;
76 
77 	if (tzid1) {
78 		if (!tzid2 || strcmp (tzid1, tzid2))
79 			retval = FALSE;
80 	} else {
81 		if (tzid2)
82 			retval = FALSE;
83 	}
84 
85 	return retval;
86 }
87 
88 /**
89  * cal_comp_util_compare_event_timezones:
90  * @comp: A calendar component object.
91  * @client: An #ECalClient.
92  *
93  * Checks if the component uses the given timezone for both the start and
94  * the end time, or if the UTC offsets of the start and end times are the same
95  * as in the given zone.
96  *
97  * Returns: TRUE if the component's start and end time are at the same UTC
98  * offset in the given timezone.
99  **/
100 gboolean
cal_comp_util_compare_event_timezones(ECalComponent * comp,ECalClient * client,ICalTimezone * zone)101 cal_comp_util_compare_event_timezones (ECalComponent *comp,
102 				       ECalClient *client,
103 				       ICalTimezone *zone)
104 {
105 	ECalComponentDateTime *start_datetime, *end_datetime;
106 	const gchar *tzid;
107 	gboolean retval = FALSE;
108 	ICalTimezone *start_zone = NULL;
109 	ICalTimezone *end_zone = NULL;
110 	gint offset1, offset2;
111 
112 	tzid = i_cal_timezone_get_tzid (zone);
113 
114 	start_datetime = e_cal_component_get_dtstart (comp);
115 	end_datetime = e_cal_component_get_dtend (comp);
116 
117 	/* If either the DTSTART or the DTEND is a DATE value, we return TRUE.
118 	 * Maybe if one was a DATE-TIME we should check that, but that should
119 	 * not happen often. */
120 	if ((start_datetime && i_cal_time_is_date (e_cal_component_datetime_get_value (start_datetime))) ||
121 	    (end_datetime && i_cal_time_is_date (e_cal_component_datetime_get_value (end_datetime)))) {
122 		retval = TRUE;
123 		goto out;
124 	}
125 
126 	if (!start_datetime || !end_datetime) {
127 		retval = FALSE;
128 		goto out;
129 	}
130 
131 	/* If the event uses UTC for DTSTART & DTEND, return TRUE. Outlook
132 	 * will send single events as UTC, so we don't want to mark all of
133 	 * these. */
134 	if (i_cal_time_is_utc (e_cal_component_datetime_get_value (start_datetime)) &&
135 	    i_cal_time_is_utc (e_cal_component_datetime_get_value (end_datetime))) {
136 		retval = TRUE;
137 		goto out;
138 	}
139 
140 	/* If the event uses floating time for DTSTART & DTEND, return TRUE.
141 	 * Imported vCalendar files will use floating times, so we don't want
142 	 * to mark all of these. */
143 	if (!e_cal_component_datetime_get_tzid (start_datetime) &&
144 	    !e_cal_component_datetime_get_tzid (end_datetime)) {
145 		retval = TRUE;
146 		goto out;
147 	}
148 
149 	/* FIXME: DURATION may be used instead. */
150 	if (cal_comp_util_tzid_equal (tzid, e_cal_component_datetime_get_tzid (start_datetime)) &&
151 	    cal_comp_util_tzid_equal (tzid, e_cal_component_datetime_get_tzid (end_datetime))) {
152 		/* If both TZIDs are the same as the given zone's TZID, then
153 		 * we know the timezones are the same so we return TRUE. */
154 		retval = TRUE;
155 	} else {
156 		gint is_daylight = 0; /* Its value is ignored, but libical-glib 3.0.5 API requires it */
157 
158 		/* If the TZIDs differ, we have to compare the UTC offsets
159 		 * of the start and end times, using their own timezones and
160 		 * the given timezone. */
161 		if (!e_cal_component_datetime_get_tzid (start_datetime) ||
162 		    !e_cal_client_get_timezone_sync (client, e_cal_component_datetime_get_tzid (start_datetime), &start_zone, NULL, NULL))
163 			start_zone = NULL;
164 
165 		if (start_zone == NULL)
166 			goto out;
167 
168 		if (e_cal_component_datetime_get_value (start_datetime)) {
169 			offset1 = i_cal_timezone_get_utc_offset (
170 				start_zone,
171 				e_cal_component_datetime_get_value (start_datetime),
172 				&is_daylight);
173 			offset2 = i_cal_timezone_get_utc_offset (
174 				zone,
175 				e_cal_component_datetime_get_value (start_datetime),
176 				&is_daylight);
177 			if (offset1 != offset2)
178 				goto out;
179 		}
180 
181 		if (!e_cal_component_datetime_get_tzid (end_datetime) ||
182 		    !e_cal_client_get_timezone_sync (client, e_cal_component_datetime_get_tzid (end_datetime), &end_zone, NULL, NULL))
183 			end_zone = NULL;
184 
185 		if (end_zone == NULL)
186 			goto out;
187 
188 		if (e_cal_component_datetime_get_value (end_datetime)) {
189 			offset1 = i_cal_timezone_get_utc_offset (
190 				end_zone,
191 				e_cal_component_datetime_get_value (end_datetime),
192 				&is_daylight);
193 			offset2 = i_cal_timezone_get_utc_offset (
194 				zone,
195 				e_cal_component_datetime_get_value (end_datetime),
196 				&is_daylight);
197 			if (offset1 != offset2)
198 				goto out;
199 		}
200 
201 		retval = TRUE;
202 	}
203 
204  out:
205 
206 	e_cal_component_datetime_free (start_datetime);
207 	e_cal_component_datetime_free (end_datetime);
208 
209 	return retval;
210 }
211 
212 /**
213  * cal_comp_is_on_server_sync:
214  * @comp: an #ECalComponent
215  * @client: an #ECalClient
216  * @cancellable: (allow none): a #GCancellable
217  * @error: (out): (allow none): a #GError
218  *
219  * Checks whether @client contains @comp. A "No Such Object" error is not
220  * propagated to the caller, any other errors are.
221  *
222  * Returns: #TRUE, when the @client contains @comp, #FALSE when not or on error.
223  *    The @error is not set when the @client doesn't contain the @comp.
224  **/
225 gboolean
cal_comp_is_on_server_sync(ECalComponent * comp,ECalClient * client,GCancellable * cancellable,GError ** error)226 cal_comp_is_on_server_sync (ECalComponent *comp,
227 			    ECalClient *client,
228 			    GCancellable *cancellable,
229 			    GError **error)
230 {
231 	const gchar *uid;
232 	gchar *rid = NULL;
233 	ICalComponent *icomp = NULL;
234 	GError *local_error = NULL;
235 
236 	g_return_val_if_fail (comp != NULL, FALSE);
237 	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
238 	g_return_val_if_fail (client != NULL, FALSE);
239 	g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
240 
241 	/* See if the component is on the server.  If it is not, then it likely
242 	 * means that the appointment is new, only in the day view, and we
243 	 * haven't added it yet to the server.  In that case, we don't need to
244 	 * confirm and we can just delete the event.  Otherwise, we ask
245 	 * the user.
246 	 */
247 	uid = e_cal_component_get_uid (comp);
248 
249 	/* TODO We should not be checking for this here. But since
250 	 *      e_cal_util_construct_instance does not create the instances
251 	 *      of all day events, so we default to old behaviour. */
252 	if (e_cal_client_check_recurrences_no_master (client)) {
253 		rid = e_cal_component_get_recurid_as_string (comp);
254 	}
255 
256 	if (e_cal_client_get_object_sync (client, uid, rid, &icomp, cancellable, &local_error) &&
257 	    icomp != NULL) {
258 		g_object_unref (icomp);
259 		g_free (rid);
260 
261 		return TRUE;
262 	}
263 
264 	if (g_error_matches (local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND))
265 		g_clear_error (&local_error);
266 	else
267 		g_propagate_error (error, local_error);
268 
269 	g_free (rid);
270 
271 	return FALSE;
272 }
273 
274 /**
275  * cal_comp_is_icalcomp_on_server_sync:
276  * The same as cal_comp_is_on_server_sync(), only the component parameter is
277  * ICalComponent, not the ECalComponent.
278  **/
279 gboolean
cal_comp_is_icalcomp_on_server_sync(ICalComponent * icomp,ECalClient * client,GCancellable * cancellable,GError ** error)280 cal_comp_is_icalcomp_on_server_sync (ICalComponent *icomp,
281 				     ECalClient *client,
282 				     GCancellable *cancellable,
283 				     GError **error)
284 {
285 	gboolean on_server;
286 	ECalComponent *comp;
287 
288 	if (!icomp || !client || !i_cal_component_get_uid (icomp))
289 		return FALSE;
290 
291 	comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
292 	if (!comp)
293 		return FALSE;
294 
295 	on_server = cal_comp_is_on_server_sync (comp, client, cancellable, error);
296 
297 	g_object_unref (comp);
298 
299 	return on_server;
300 }
301 
302 static ECalComponent *
cal_comp_util_ref_default_object(ECalClient * client,ICalComponentKind icomp_kind,ECalComponentVType ecomp_vtype,GCancellable * cancellable,GError ** error)303 cal_comp_util_ref_default_object (ECalClient *client,
304 				  ICalComponentKind icomp_kind,
305 				  ECalComponentVType ecomp_vtype,
306 				  GCancellable *cancellable,
307 				  GError **error)
308 {
309 	ICalComponent *icomp = NULL;
310 	ECalComponent *comp;
311 
312 	/* Simply ignore errors here */
313 	if (client && !e_cal_client_get_default_object_sync (client, &icomp, cancellable, NULL))
314 		icomp = NULL;
315 
316 	if (!icomp)
317 		icomp = i_cal_component_new (icomp_kind);
318 
319 	comp = e_cal_component_new ();
320 
321 	if (!e_cal_component_set_icalcomponent (comp, icomp)) {
322 		g_clear_object (&icomp);
323 
324 		e_cal_component_set_new_vtype (comp, ecomp_vtype);
325 	}
326 
327 	return comp;
328 }
329 
330 /**
331  * cal_comp_event_new_with_defaults_sync:
332  *
333  * Creates a new VEVENT component and adds any default alarms to it as set in
334  * the program's configuration values, but only if not the all_day event.
335  *
336  * Return value: A newly-created calendar component.
337  **/
338 ECalComponent *
cal_comp_event_new_with_defaults_sync(ECalClient * client,gboolean all_day,gboolean use_default_reminder,gint default_reminder_interval,EDurationType default_reminder_units,GCancellable * cancellable,GError ** error)339 cal_comp_event_new_with_defaults_sync (ECalClient *client,
340 				       gboolean all_day,
341 				       gboolean use_default_reminder,
342 				       gint default_reminder_interval,
343 				       EDurationType default_reminder_units,
344 				       GCancellable *cancellable,
345 				       GError **error)
346 {
347 	ECalComponent *comp;
348 	ECalComponentAlarm *alarm;
349 	ICalProperty *prop;
350 	ICalDuration *duration;
351 	ECalComponentAlarmTrigger *trigger;
352 
353 	comp = cal_comp_util_ref_default_object (client, I_CAL_VEVENT_COMPONENT, E_CAL_COMPONENT_EVENT, cancellable, error);
354 
355 	if (!comp)
356 		return NULL;
357 
358 	if (all_day || !use_default_reminder)
359 		return comp;
360 
361 	alarm = e_cal_component_alarm_new ();
362 
363 	/* We don't set the description of the alarm; we'll copy it from the
364 	 * summary when it gets committed to the server. For that, we add a
365 	 * X-EVOLUTION-NEEDS-DESCRIPTION property to the alarm's component.
366 	 */
367 	prop = i_cal_property_new_x ("1");
368 	i_cal_property_set_x_name (prop, "X-EVOLUTION-NEEDS-DESCRIPTION");
369 	e_cal_component_property_bag_take (e_cal_component_alarm_get_property_bag (alarm), prop);
370 
371 	e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
372 
373 	duration = i_cal_duration_new_null_duration ();
374 	i_cal_duration_set_is_neg (duration, TRUE);
375 
376 	switch (default_reminder_units) {
377 	case E_DURATION_MINUTES:
378 		i_cal_duration_set_minutes (duration, default_reminder_interval);
379 		break;
380 
381 	case E_DURATION_HOURS:
382 		i_cal_duration_set_hours (duration, default_reminder_interval);
383 		break;
384 
385 	case E_DURATION_DAYS:
386 		i_cal_duration_set_days (duration, default_reminder_interval);
387 		break;
388 
389 	default:
390 		g_warning ("wrong units %d\n", default_reminder_units);
391 	}
392 
393 	trigger = e_cal_component_alarm_trigger_new_relative (E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START, duration);
394 	g_clear_object (&duration);
395 
396 	e_cal_component_alarm_take_trigger (alarm, trigger);
397 
398 	e_cal_component_add_alarm (comp, alarm);
399 	e_cal_component_alarm_free (alarm);
400 
401 	return comp;
402 }
403 
404 ECalComponent *
cal_comp_event_new_with_current_time_sync(ECalClient * client,gboolean all_day,gboolean use_default_reminder,gint default_reminder_interval,EDurationType default_reminder_units,GCancellable * cancellable,GError ** error)405 cal_comp_event_new_with_current_time_sync (ECalClient *client,
406 					   gboolean all_day,
407 					   gboolean use_default_reminder,
408 					   gint default_reminder_interval,
409 					   EDurationType default_reminder_units,
410 					   GCancellable *cancellable,
411 					   GError **error)
412 {
413 	ECalComponent *comp;
414 	ICalTime *itt;
415 	ECalComponentDateTime *dt;
416 	ICalTimezone *zone;
417 
418 	comp = cal_comp_event_new_with_defaults_sync (
419 		client, all_day, use_default_reminder,
420 		default_reminder_interval, default_reminder_units,
421 		cancellable, error);
422 	if (!comp)
423 		return NULL;
424 
425 	zone = calendar_config_get_icaltimezone ();
426 
427 	if (all_day) {
428 		itt = i_cal_time_new_from_timet_with_zone (time (NULL), 1, zone);
429 
430 		dt = e_cal_component_datetime_new_take (itt, zone ? g_strdup (i_cal_timezone_get_tzid (zone)) : NULL);
431 
432 		e_cal_component_set_dtstart (comp, dt);
433 		e_cal_component_set_dtend (comp, dt);
434 	} else {
435 		itt = i_cal_time_new_current_with_zone (zone);
436 		i_cal_time_adjust (itt, 0, 1, -i_cal_time_get_minute (itt), -i_cal_time_get_second (itt));
437 
438 		dt = e_cal_component_datetime_new_take (itt, zone ? g_strdup (i_cal_timezone_get_tzid (zone)) : NULL);
439 
440 		e_cal_component_set_dtstart (comp, dt);
441 
442 		i_cal_time_adjust (e_cal_component_datetime_get_value (dt), 0, 1, 0, 0);
443 		e_cal_component_set_dtend (comp, dt);
444 	}
445 
446 	e_cal_component_datetime_free (dt);
447 
448 	return comp;
449 }
450 
451 ECalComponent *
cal_comp_task_new_with_defaults_sync(ECalClient * client,GCancellable * cancellable,GError ** error)452 cal_comp_task_new_with_defaults_sync (ECalClient *client,
453 				      GCancellable *cancellable,
454 				      GError **error)
455 {
456 	ECalComponent *comp;
457 
458 	comp = cal_comp_util_ref_default_object (client, I_CAL_VTODO_COMPONENT, E_CAL_COMPONENT_TODO, cancellable, error);
459 
460 	return comp;
461 }
462 
463 ECalComponent *
cal_comp_memo_new_with_defaults_sync(ECalClient * client,GCancellable * cancellable,GError ** error)464 cal_comp_memo_new_with_defaults_sync (ECalClient *client,
465 				      GCancellable *cancellable,
466 				      GError **error)
467 {
468 	ECalComponent *comp;
469 
470 	comp = cal_comp_util_ref_default_object (client, I_CAL_VJOURNAL_COMPONENT, E_CAL_COMPONENT_JOURNAL, cancellable, error);
471 
472 	return comp;
473 }
474 
475 void
cal_comp_update_time_by_active_window(ECalComponent * comp,EShell * shell)476 cal_comp_update_time_by_active_window (ECalComponent *comp,
477                                        EShell *shell)
478 {
479 	GtkWindow *window;
480 
481 	g_return_if_fail (comp != NULL);
482 	g_return_if_fail (shell != NULL);
483 
484 	window = e_shell_get_active_window (shell);
485 
486 	if (E_IS_SHELL_WINDOW (window)) {
487 		EShellWindow *shell_window;
488 		const gchar *active_view;
489 
490 		shell_window = E_SHELL_WINDOW (window);
491 		active_view = e_shell_window_get_active_view (shell_window);
492 
493 		if (g_strcmp0 (active_view, "calendar") == 0) {
494 			EShellContent *shell_content;
495 			EShellView *shell_view;
496 			ECalendarView *cal_view;
497 			time_t start = 0, end = 0;
498 			ICalTimezone *zone;
499 			ICalTime *itt;
500 			ICalComponent *icomp;
501 			ICalProperty *prop;
502 
503 			shell_view = e_shell_window_peek_shell_view (shell_window, "calendar");
504 			g_return_if_fail (shell_view != NULL);
505 
506 			cal_view = NULL;
507 			shell_content = e_shell_view_get_shell_content (shell_view);
508 			g_object_get (shell_content, "current-view", &cal_view, NULL);
509 			g_return_if_fail (cal_view != NULL);
510 			g_return_if_fail (e_calendar_view_get_visible_time_range (cal_view, &start, &end));
511 
512 			zone = e_cal_model_get_timezone (e_calendar_view_get_model (cal_view));
513 			itt = i_cal_time_new_from_timet_with_zone (start, FALSE, zone);
514 
515 			icomp = e_cal_component_get_icalcomponent (comp);
516 			prop = i_cal_component_get_first_property (icomp, I_CAL_DTSTART_PROPERTY);
517 			if (prop) {
518 				i_cal_property_set_dtstart (prop, itt);
519 				g_object_unref (prop);
520 			} else {
521 				prop = i_cal_property_new_dtstart (itt);
522 				i_cal_component_take_property (icomp, prop);
523 			}
524 
525 			g_clear_object (&cal_view);
526 			g_object_unref (itt);
527 		}
528 	}
529 }
530 
531 /**
532  * cal_comp_util_get_n_icons:
533  * @comp: A calendar component object.
534  * @pixbufs: List of pixbufs to use. Can be NULL.
535  *
536  * Get the number of icons owned by the component.
537  * Each member of pixmaps should be freed with g_object_unref
538  * and the list itself should be freed too.
539  *
540  * Returns: the number of icons owned by the component.
541  **/
542 gint
cal_comp_util_get_n_icons(ECalComponent * comp,GSList ** pixbufs)543 cal_comp_util_get_n_icons (ECalComponent *comp,
544                            GSList **pixbufs)
545 {
546 	GSList *categories_list, *elem;
547 	gint num_icons = 0;
548 
549 	g_return_val_if_fail (comp != NULL, 0);
550 	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), 0);
551 
552 	categories_list = e_cal_component_get_categories_list (comp);
553 	for (elem = categories_list; elem; elem = elem->next) {
554 		const gchar *category;
555 		GdkPixbuf *pixbuf = NULL;
556 
557 		category = elem->data;
558 		if (e_categories_config_get_icon_for (category, &pixbuf)) {
559 			if (!pixbuf)
560 				continue;
561 
562 			num_icons++;
563 
564 			if (pixbufs) {
565 				*pixbufs = g_slist_append (*pixbufs, pixbuf);
566 			} else {
567 				g_object_unref (pixbuf);
568 			}
569 		}
570 	}
571 	g_slist_free_full (categories_list, g_free);
572 
573 	return num_icons;
574 }
575 
576 /**
577  * cal_comp_selection_set_string_list
578  * @data: Selection data, where to put list of strings.
579  * @str_list: List of strings. (Each element is of type const gchar *.)
580  *
581  * Stores list of strings into selection target data.  Use
582  * cal_comp_selection_get_string_list() to get this list from target data.
583  **/
584 void
cal_comp_selection_set_string_list(GtkSelectionData * data,GSList * str_list)585 cal_comp_selection_set_string_list (GtkSelectionData *data,
586                                     GSList *str_list)
587 {
588 	/* format is "str1\0str2\0...strN\0" */
589 	GSList *p;
590 	GByteArray *array;
591 	GdkAtom target;
592 
593 	g_return_if_fail (data != NULL);
594 
595 	if (!str_list)
596 		return;
597 
598 	array = g_byte_array_new ();
599 	for (p = str_list; p; p = p->next) {
600 		const guint8 *c = p->data;
601 
602 		if (c)
603 			g_byte_array_append (array, c, strlen ((const gchar *) c) + 1);
604 	}
605 
606 	target = gtk_selection_data_get_target (data);
607 	gtk_selection_data_set (data, target, 8, array->data, array->len);
608 	g_byte_array_free (array, TRUE);
609 }
610 
611 /**
612  * cal_comp_selection_get_string_list
613  * @data: Selection data, where to put list of strings.
614  *
615  * Converts data from selection to list of strings. Data should be assigned
616  * to selection data with cal_comp_selection_set_string_list().
617  * Each string in newly created list should be freed by g_free().
618  * List itself should be freed by g_slist_free().
619  *
620  * Returns: Newly allocated #GSList of strings.
621  **/
622 GSList *
cal_comp_selection_get_string_list(GtkSelectionData * selection_data)623 cal_comp_selection_get_string_list (GtkSelectionData *selection_data)
624 {
625 	/* format is "str1\0str2\0...strN\0" */
626 	gchar *inptr, *inend;
627 	GSList *list;
628 	const guchar *data;
629 	gint length;
630 
631 	g_return_val_if_fail (selection_data != NULL, NULL);
632 
633 	data = gtk_selection_data_get_data (selection_data);
634 	length = gtk_selection_data_get_length (selection_data);
635 
636 	list = NULL;
637 	inptr = (gchar *) data;
638 	inend = (gchar *) (data + length);
639 
640 	while (inptr < inend) {
641 		gchar *start = inptr;
642 
643 		while (inptr < inend && *inptr)
644 			inptr++;
645 
646 		list = g_slist_prepend (list, g_strndup (start, inptr - start));
647 
648 		inptr++;
649 	}
650 
651 	return list;
652 }
653 
654 static void
datetime_to_zone(ECalClient * client,ECalComponentDateTime * date,const gchar * tzid)655 datetime_to_zone (ECalClient *client,
656                   ECalComponentDateTime *date,
657                   const gchar *tzid)
658 {
659 	ICalTimezone *from, *to;
660 
661 	g_return_if_fail (date != NULL);
662 
663 	if (!e_cal_component_datetime_get_tzid (date) || !tzid ||
664 	    e_cal_component_datetime_get_tzid (date) == tzid ||
665 	    g_str_equal (e_cal_component_datetime_get_tzid (date), tzid))
666 		return;
667 
668 	from = i_cal_timezone_get_builtin_timezone_from_tzid (e_cal_component_datetime_get_tzid (date));
669 	if (!from) {
670 		GError *error = NULL;
671 
672 		if (!e_cal_client_get_timezone_sync (client, e_cal_component_datetime_get_tzid (date), &from, NULL, &error))
673 			from = NULL;
674 
675 		if (error != NULL) {
676 			g_warning (
677 				"%s: Could not get timezone '%s' from server: %s",
678 				G_STRFUNC, e_cal_component_datetime_get_tzid (date) ? e_cal_component_datetime_get_tzid (date) : "",
679 				error->message);
680 			g_error_free (error);
681 		}
682 	}
683 
684 	to = i_cal_timezone_get_builtin_timezone_from_tzid (tzid);
685 	if (!to) {
686 		/* do not check failure here, maybe the zone is not available there */
687 		if (!e_cal_client_get_timezone_sync (client, tzid, &to, NULL, NULL))
688 			to = NULL;
689 	}
690 
691 	i_cal_time_convert_timezone (e_cal_component_datetime_get_value (date), from, to);
692 	e_cal_component_datetime_set_tzid (date, tzid);
693 }
694 
695 /**
696  * cal_comp_set_dtstart_with_oldzone:
697  * @client: ECalClient structure, to retrieve timezone from, when required.
698  * @comp: Component, where make the change.
699  * @pdate: Value, to change to.
700  *
701  * Changes 'dtstart' of the component, but converts time to the old timezone.
702  **/
703 void
cal_comp_set_dtstart_with_oldzone(ECalClient * client,ECalComponent * comp,const ECalComponentDateTime * pdate)704 cal_comp_set_dtstart_with_oldzone (ECalClient *client,
705                                    ECalComponent *comp,
706                                    const ECalComponentDateTime *pdate)
707 {
708 	ECalComponentDateTime *olddate, *date;
709 
710 	g_return_if_fail (comp != NULL);
711 	g_return_if_fail (pdate != NULL);
712 
713 	olddate = e_cal_component_get_dtstart (comp);
714 	date = e_cal_component_datetime_copy (pdate);
715 
716 	datetime_to_zone (client, date, e_cal_component_datetime_get_tzid (olddate));
717 	e_cal_component_set_dtstart (comp, date);
718 
719 	e_cal_component_datetime_free (olddate);
720 	e_cal_component_datetime_free (date);
721 }
722 
723 /**
724  * cal_comp_set_dtend_with_oldzone:
725  * @client: ECalClient structure, to retrieve timezone from, when required.
726  * @comp: Component, where make the change.
727  * @pdate: Value, to change to.
728  *
729  * Changes 'dtend' of the component, but converts time to the old timezone.
730  **/
731 void
cal_comp_set_dtend_with_oldzone(ECalClient * client,ECalComponent * comp,const ECalComponentDateTime * pdate)732 cal_comp_set_dtend_with_oldzone (ECalClient *client,
733                                  ECalComponent *comp,
734                                  const ECalComponentDateTime *pdate)
735 {
736 	ECalComponentDateTime *olddate, *date;
737 
738 	g_return_if_fail (comp != NULL);
739 	g_return_if_fail (pdate != NULL);
740 
741 	olddate = e_cal_component_get_dtend (comp);
742 	date = e_cal_component_datetime_copy (pdate);
743 
744 	datetime_to_zone (client, date, e_cal_component_datetime_get_tzid (olddate));
745 	e_cal_component_set_dtend (comp, date);
746 
747 	e_cal_component_datetime_free (olddate);
748 	e_cal_component_datetime_free (date);
749 }
750 
751 gboolean
comp_util_sanitize_recurrence_master_sync(ECalComponent * comp,ECalClient * client,GCancellable * cancellable,GError ** error)752 comp_util_sanitize_recurrence_master_sync (ECalComponent *comp,
753 					   ECalClient *client,
754 					   GCancellable *cancellable,
755 					   GError **error)
756 {
757 	ECalComponent *master = NULL;
758 	ICalComponent *icomp = NULL;
759 	ECalComponentRange *rid;
760 	ECalComponentDateTime *sdt, *rdt;
761 	const gchar *uid;
762 
763 	/* Get the master component */
764 	uid = e_cal_component_get_uid (comp);
765 
766 	if (!e_cal_client_get_object_sync (client, uid, NULL, &icomp, cancellable, error))
767 		return FALSE;
768 
769 	master = e_cal_component_new_from_icalcomponent (icomp);
770 	if (!master) {
771 		g_warn_if_reached ();
772 		return FALSE;
773 	}
774 
775 	/* Compare recur id and start date */
776 	rid = e_cal_component_get_recurid (comp);
777 	sdt = e_cal_component_get_dtstart (comp);
778 	rdt = rid ? e_cal_component_range_get_datetime (rid) : NULL;
779 
780 	if (rdt && sdt &&
781 	    i_cal_time_compare_date_only (e_cal_component_datetime_get_value (rdt), e_cal_component_datetime_get_value (sdt)) == 0) {
782 		ECalComponentDateTime *msdt, *medt, *edt;
783 		gint yy = 0, mm = 0, dd = 0;
784 		gint64 diff;
785 
786 		msdt = e_cal_component_get_dtstart (master);
787 		medt = e_cal_component_get_dtend (master);
788 
789 		edt = e_cal_component_get_dtend (comp);
790 
791 		if (!msdt || !medt || !edt) {
792 			g_warn_if_reached ();
793 			e_cal_component_datetime_free (msdt);
794 			e_cal_component_datetime_free (medt);
795 			e_cal_component_datetime_free (edt);
796 			e_cal_component_datetime_free (sdt);
797 			e_cal_component_range_free (rid);
798 			g_object_unref (master);
799 			return FALSE;
800 		}
801 
802 		/* Consider the day difference only, because the time is preserved */
803 		diff = (i_cal_time_as_timet (e_cal_component_datetime_get_value (edt)) -
804 			i_cal_time_as_timet (e_cal_component_datetime_get_value (sdt))) / (24 * 60 * 60);
805 
806 		i_cal_time_get_date (e_cal_component_datetime_get_value (msdt), &yy, &mm, &dd);
807 		i_cal_time_set_date (e_cal_component_datetime_get_value (sdt), yy, mm, dd);
808 		i_cal_time_set_date (e_cal_component_datetime_get_value (edt), yy, mm, dd);
809 
810 		if (diff)
811 			i_cal_time_adjust (e_cal_component_datetime_get_value (edt), diff, 0, 0, 0);
812 
813 		/* Make sure the DATE value of the DTSTART and DTEND do not match */
814 		if (i_cal_time_is_date (e_cal_component_datetime_get_value (sdt)) &&
815 		    i_cal_time_is_date (e_cal_component_datetime_get_value (edt)) &&
816 		    i_cal_time_compare_date_only (e_cal_component_datetime_get_value (sdt), e_cal_component_datetime_get_value (edt)) == 0) {
817 			i_cal_time_adjust (e_cal_component_datetime_get_value (edt), 1, 0, 0, 0);
818 		}
819 
820 		e_cal_component_set_dtstart (comp, sdt);
821 		e_cal_component_set_dtend (comp, edt);
822 
823 		e_cal_component_abort_sequence (comp);
824 
825 		e_cal_component_datetime_free (msdt);
826 		e_cal_component_datetime_free (medt);
827 		e_cal_component_datetime_free (edt);
828 	}
829 
830 	e_cal_component_set_recurid (comp, NULL);
831 
832 	e_cal_component_datetime_free (sdt);
833 	e_cal_component_range_free (rid);
834 	g_object_unref (master);
835 
836 	return TRUE;
837 }
838 
839 gchar *
comp_util_suggest_filename(ICalComponent * icomp,const gchar * default_name)840 comp_util_suggest_filename (ICalComponent *icomp,
841 			    const gchar *default_name)
842 {
843 	ICalProperty *prop;
844 	const gchar *summary = NULL;
845 	gchar *filename;
846 
847 	if (!icomp)
848 		return g_strconcat (default_name, ".ics", NULL);
849 
850 	prop = i_cal_component_get_first_property (icomp, I_CAL_SUMMARY_PROPERTY);
851 	if (prop)
852 		summary = i_cal_property_get_summary (prop);
853 
854 	if (!summary || !*summary)
855 		summary = default_name;
856 
857 	filename = g_strconcat (summary, ".ics", NULL);
858 
859 	g_clear_object (&prop);
860 
861 	return filename;
862 }
863 
864 void
cal_comp_get_instance_times(ECalClient * client,ICalComponent * icomp,const ICalTimezone * default_zone,ICalTime ** out_instance_start,ICalTime ** out_instance_end,GCancellable * cancellable)865 cal_comp_get_instance_times (ECalClient *client,
866 			     ICalComponent *icomp,
867 			     const ICalTimezone *default_zone,
868 			     ICalTime **out_instance_start,
869 			     ICalTime **out_instance_end,
870 			     GCancellable *cancellable)
871 {
872 	ICalTime *start_time, *end_time;
873 	const ICalTimezone *zone;
874 
875 	g_return_if_fail (E_IS_CAL_CLIENT (client));
876 	g_return_if_fail (icomp != NULL);
877 	g_return_if_fail (out_instance_start != NULL);
878 	g_return_if_fail (out_instance_end != NULL);
879 
880 	start_time = i_cal_component_get_dtstart (icomp);
881 	end_time = i_cal_component_get_dtend (icomp);
882 
883 	/* Some event can have missing DTEND, then use the start_time for them */
884 	if (!end_time || i_cal_time_is_null_time (end_time)) {
885 		g_clear_object (&end_time);
886 
887 		end_time = i_cal_time_clone (start_time);
888 	}
889 
890 	zone = NULL;
891 
892 	if (i_cal_time_get_timezone (start_time)) {
893 		zone = i_cal_time_get_timezone (start_time);
894 	} else {
895 		ICalParameter *param = NULL;
896 		ICalProperty *prop = i_cal_component_get_first_property (icomp, I_CAL_DTSTART_PROPERTY);
897 
898 		if (prop) {
899 			param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
900 
901 			if (param) {
902 				const gchar *tzid = NULL;
903 				ICalTimezone *st_zone = NULL;
904 
905 				tzid = i_cal_parameter_get_tzid (param);
906 				if (tzid && !e_cal_client_get_timezone_sync (client, tzid, &st_zone, cancellable, NULL))
907 					st_zone = NULL;
908 
909 				if (st_zone)
910 					zone = st_zone;
911 
912 				g_object_unref (param);
913 			}
914 
915 			g_object_unref (prop);
916 		}
917 	}
918 
919 	if (!zone)
920 		zone = default_zone;
921 
922 	*out_instance_start = i_cal_time_clone (start_time);
923 	if (i_cal_time_is_date (*out_instance_start)) {
924 		i_cal_time_set_is_date (*out_instance_start, FALSE);
925 		i_cal_time_set_timezone (*out_instance_start, zone);
926 		i_cal_time_set_is_date (*out_instance_start, TRUE);
927 	} else {
928 		i_cal_time_set_timezone (*out_instance_start, zone);
929 	}
930 
931 	zone = NULL;
932 
933 	if (i_cal_time_get_timezone (end_time)) {
934 		zone = i_cal_time_get_timezone (end_time);
935 	} else {
936 		ICalParameter *param = NULL;
937 		ICalProperty *prop = i_cal_component_get_first_property (icomp, I_CAL_DTEND_PROPERTY);
938 
939 		if (!prop)
940 			prop = i_cal_component_get_first_property (icomp, I_CAL_DTSTART_PROPERTY);
941 
942 		if (prop) {
943 			param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
944 
945 			if (param) {
946 				const gchar *tzid = NULL;
947 				ICalTimezone *end_zone = NULL;
948 
949 				tzid = i_cal_parameter_get_tzid (param);
950 				if (tzid && !e_cal_client_get_timezone_sync (client, tzid, &end_zone, cancellable, NULL))
951 					end_zone = NULL;
952 
953 				if (end_zone)
954 					zone = end_zone;
955 
956 				g_object_unref (param);
957 			}
958 
959 			g_object_unref (prop);
960 		}
961 	}
962 
963 	if (!zone)
964 		zone = default_zone;
965 
966 	*out_instance_end = i_cal_time_clone (end_time);
967 	if (i_cal_time_is_date (*out_instance_end)) {
968 		i_cal_time_set_is_date (*out_instance_end, FALSE);
969 		i_cal_time_set_timezone (*out_instance_end, zone);
970 		i_cal_time_set_is_date (*out_instance_end, TRUE);
971 	} else {
972 		i_cal_time_set_timezone (*out_instance_end, zone);
973 	}
974 
975 	g_clear_object (&start_time);
976 	g_clear_object (&end_time);
977 }
978 
979 time_t
cal_comp_gdate_to_timet(const GDate * date,const ICalTimezone * with_zone)980 cal_comp_gdate_to_timet (const GDate *date,
981 			 const ICalTimezone *with_zone)
982 {
983 	struct tm tm;
984 	ICalTime *tt;
985 	time_t res;
986 
987 	g_return_val_if_fail (date != NULL, (time_t) -1);
988 	g_return_val_if_fail (g_date_valid (date), (time_t) -1);
989 
990 	g_date_to_struct_tm (date, &tm);
991 
992 	tt = e_cal_util_tm_to_icaltime (&tm, TRUE);
993 	if (with_zone)
994 		res = i_cal_time_as_timet_with_zone (tt, (ICalTimezone *) with_zone);
995 	else
996 		res = i_cal_time_as_timet (tt);
997 
998 	g_clear_object (&tt);
999 
1000 	return res;
1001 }
1002 
1003 typedef struct _AsyncContext {
1004 	ECalClient *src_client;
1005 	ICalComponent *icomp_clone;
1006 	gboolean do_copy;
1007 } AsyncContext;
1008 
1009 struct ForeachTzidData
1010 {
1011 	ECalClient *source_client;
1012 	ECalClient *destination_client;
1013 	GCancellable *cancellable;
1014 	GError **error;
1015 	gboolean success;
1016 };
1017 
1018 static void
async_context_free(AsyncContext * async_context)1019 async_context_free (AsyncContext *async_context)
1020 {
1021 	g_clear_object (&async_context->src_client);
1022 	g_clear_object (&async_context->icomp_clone);
1023 	g_slice_free (AsyncContext, async_context);
1024 }
1025 
1026 static void
add_timezone_to_cal_cb(ICalParameter * param,gpointer data)1027 add_timezone_to_cal_cb (ICalParameter *param,
1028                         gpointer data)
1029 {
1030 	struct ForeachTzidData *ftd = data;
1031 	ICalTimezone *tz = NULL;
1032 	const gchar *tzid;
1033 
1034 	g_return_if_fail (ftd != NULL);
1035 	g_return_if_fail (ftd->source_client != NULL);
1036 	g_return_if_fail (ftd->destination_client != NULL);
1037 
1038 	if (!ftd->success)
1039 		return;
1040 
1041 	if (ftd->cancellable && g_cancellable_is_cancelled (ftd->cancellable)) {
1042 		ftd->success = FALSE;
1043 		return;
1044 	}
1045 
1046 	tzid = i_cal_parameter_get_tzid (param);
1047 	if (!tzid || !*tzid)
1048 		return;
1049 
1050 	if (e_cal_client_get_timezone_sync (ftd->source_client, tzid, &tz, ftd->cancellable, NULL) && tz)
1051 		ftd->success = e_cal_client_add_timezone_sync (
1052 				ftd->destination_client, tz, ftd->cancellable, ftd->error);
1053 }
1054 
1055 /* Helper for cal_comp_transfer_item_to() */
1056 static void
cal_comp_transfer_item_to_thread(GSimpleAsyncResult * simple,GObject * source_object,GCancellable * cancellable)1057 cal_comp_transfer_item_to_thread (GSimpleAsyncResult *simple,
1058                                   GObject *source_object,
1059                                   GCancellable *cancellable)
1060 {
1061 	AsyncContext *async_context;
1062 	GError *local_error = NULL;
1063 
1064 	async_context = g_simple_async_result_get_op_res_gpointer (simple);
1065 
1066 	cal_comp_transfer_item_to_sync (
1067 		async_context->src_client,
1068 		E_CAL_CLIENT (source_object),
1069 		async_context->icomp_clone,
1070 		async_context->do_copy,
1071 		cancellable, &local_error);
1072 
1073 	if (local_error != NULL)
1074 		g_simple_async_result_take_error (simple, local_error);
1075 }
1076 
1077 void
cal_comp_transfer_item_to(ECalClient * src_client,ECalClient * dest_client,ICalComponent * icomp_vcal,gboolean do_copy,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1078 cal_comp_transfer_item_to (ECalClient *src_client,
1079 			   ECalClient *dest_client,
1080 			   ICalComponent *icomp_vcal,
1081 			   gboolean do_copy,
1082 			   GCancellable *cancellable,
1083 			   GAsyncReadyCallback callback,
1084 			   gpointer user_data)
1085 {
1086 	GSimpleAsyncResult *simple;
1087 	AsyncContext *async_context;
1088 
1089 	g_return_if_fail (E_IS_CAL_CLIENT (src_client));
1090 	g_return_if_fail (E_IS_CAL_CLIENT (dest_client));
1091 	g_return_if_fail (icomp_vcal != NULL);
1092 
1093 	async_context = g_slice_new0 (AsyncContext);
1094 	async_context->src_client = g_object_ref (src_client);
1095 	async_context->icomp_clone = i_cal_component_clone (icomp_vcal);
1096 	async_context->do_copy = do_copy;
1097 
1098 	simple = g_simple_async_result_new (
1099 		G_OBJECT (dest_client), callback, user_data,
1100 		cal_comp_transfer_item_to);
1101 
1102 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1103 
1104 	g_simple_async_result_set_op_res_gpointer (
1105 		simple, async_context, (GDestroyNotify) async_context_free);
1106 
1107 	g_simple_async_result_run_in_thread (
1108 		simple, cal_comp_transfer_item_to_thread,
1109 		G_PRIORITY_DEFAULT, cancellable);
1110 
1111 	g_object_unref (simple);
1112 }
1113 
1114 gboolean
cal_comp_transfer_item_to_finish(ECalClient * client,GAsyncResult * result,GError ** error)1115 cal_comp_transfer_item_to_finish (ECalClient *client,
1116                                   GAsyncResult *result,
1117                                   GError **error)
1118 {
1119 	GSimpleAsyncResult *simple;
1120 
1121 	g_return_val_if_fail (
1122 		g_simple_async_result_is_valid (result, G_OBJECT (client), cal_comp_transfer_item_to),
1123 		FALSE);
1124 
1125 	simple = G_SIMPLE_ASYNC_RESULT (result);
1126 
1127 	if (g_simple_async_result_propagate_error (simple, error))
1128 		return FALSE;
1129 
1130 	return TRUE;
1131 }
1132 
1133 gboolean
cal_comp_transfer_item_to_sync(ECalClient * src_client,ECalClient * dest_client,ICalComponent * icomp_vcal,gboolean do_copy,GCancellable * cancellable,GError ** error)1134 cal_comp_transfer_item_to_sync (ECalClient *src_client,
1135 				ECalClient *dest_client,
1136 				ICalComponent *icomp_vcal,
1137 				gboolean do_copy,
1138 				GCancellable *cancellable,
1139 				GError **error)
1140 {
1141 	ICalComponent *icomp;
1142 	ICalComponent *icomp_event, *subcomp;
1143 	ICalComponentKind icomp_kind;
1144 	const gchar *uid;
1145 	gchar *new_uid = NULL;
1146 	struct ForeachTzidData ftd;
1147 	ECalClientSourceType source_type;
1148 	GHashTable *processed_uids;
1149 	gboolean same_client;
1150 	gboolean success = FALSE;
1151 
1152 	g_return_val_if_fail (E_IS_CAL_CLIENT (src_client), FALSE);
1153 	g_return_val_if_fail (E_IS_CAL_CLIENT (dest_client), FALSE);
1154 	g_return_val_if_fail (icomp_vcal != NULL, FALSE);
1155 
1156 	icomp_event = i_cal_component_get_inner (icomp_vcal);
1157 	g_return_val_if_fail (icomp_event != NULL, FALSE);
1158 
1159 	source_type = e_cal_client_get_source_type (src_client);
1160 	switch (source_type) {
1161 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1162 			icomp_kind = I_CAL_VEVENT_COMPONENT;
1163 			break;
1164 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1165 			icomp_kind = I_CAL_VTODO_COMPONENT;
1166 			break;
1167 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1168 			icomp_kind = I_CAL_VJOURNAL_COMPONENT;
1169 			break;
1170 		default:
1171 			g_return_val_if_reached (FALSE);
1172 	}
1173 
1174 	same_client = src_client == dest_client || e_source_equal (
1175 		e_client_get_source (E_CLIENT (src_client)), e_client_get_source (E_CLIENT (dest_client)));
1176 	processed_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1177 
1178 	icomp_event = i_cal_component_get_first_component (icomp_vcal, icomp_kind);
1179 	/*
1180 	 * This check should be removed in the near future.
1181 	 * We should be able to work properly with multiselection, which means that we always
1182 	 * will receive a component with subcomponents.
1183 	 */
1184 	if (icomp_event == NULL)
1185 		icomp_event = icomp_vcal;
1186 	for (;
1187 	     icomp_event;
1188 	     g_object_unref (icomp_event), icomp_event = i_cal_component_get_next_component (icomp_vcal, icomp_kind)) {
1189 		GError *local_error = NULL;
1190 
1191 		uid = i_cal_component_get_uid (icomp_event);
1192 
1193 		if (g_hash_table_lookup (processed_uids, uid))
1194 			continue;
1195 
1196 		if (do_copy && same_client)
1197 			success = FALSE;
1198 		else
1199 			success = e_cal_client_get_object_sync (dest_client, uid, NULL, &icomp, cancellable, &local_error);
1200 		if (success) {
1201 			success = e_cal_client_modify_object_sync (
1202 				dest_client, icomp_event, E_CAL_OBJ_MOD_ALL, E_CAL_OPERATION_FLAG_DISABLE_ITIP_MESSAGE, cancellable, error);
1203 
1204 			g_clear_object (&icomp);
1205 			if (!success)
1206 				goto exit;
1207 
1208 			if (!do_copy) {
1209 				ECalObjModType mod_type = E_CAL_OBJ_MOD_THIS;
1210 
1211 				/* Remove the item from the source calendar. */
1212 				if (e_cal_util_component_is_instance (icomp_event) ||
1213 				    e_cal_util_component_has_recurrences (icomp_event))
1214 					mod_type = E_CAL_OBJ_MOD_ALL;
1215 
1216 				success = e_cal_client_remove_object_sync (
1217 						src_client, uid, NULL, mod_type, E_CAL_OPERATION_FLAG_DISABLE_ITIP_MESSAGE, cancellable, error);
1218 				if (!success)
1219 					goto exit;
1220 			}
1221 
1222 			continue;
1223 		} else if (local_error != NULL && !g_error_matches (
1224 					local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND)) {
1225 			g_propagate_error (error, local_error);
1226 			goto exit;
1227 		} else {
1228 			g_clear_error (&local_error);
1229 		}
1230 
1231 		if (e_cal_util_component_is_instance (icomp_event)) {
1232 			GSList *ecalcomps = NULL, *eiter;
1233 			ECalComponent *comp;
1234 
1235 			success = e_cal_client_get_objects_for_uid_sync (src_client, uid, &ecalcomps, cancellable, error);
1236 			if (!success)
1237 				goto exit;
1238 
1239 			if (ecalcomps && !ecalcomps->next) {
1240 				/* only one component, no need for a vCalendar list */
1241 				comp = ecalcomps->data;
1242 				icomp = i_cal_component_clone (e_cal_component_get_icalcomponent (comp));
1243 			} else {
1244 				icomp = i_cal_component_new (I_CAL_VCALENDAR_COMPONENT);
1245 				for (eiter = ecalcomps; eiter; eiter = g_slist_next (eiter)) {
1246 					comp = eiter->data;
1247 
1248 					i_cal_component_take_component (
1249 						icomp,
1250 						i_cal_component_clone (e_cal_component_get_icalcomponent (comp)));
1251 				}
1252 			}
1253 
1254 			e_util_free_nullable_object_slist (ecalcomps);
1255 		} else {
1256 			icomp = i_cal_component_clone (icomp_event);
1257 		}
1258 
1259 		if (do_copy) {
1260 			/* Change the UID to avoid problems with duplicated UID */
1261 			new_uid = e_util_generate_uid ();
1262 			if (i_cal_component_isa (icomp) == I_CAL_VCALENDAR_COMPONENT) {
1263 				/* in case of a vCalendar, the component might have detached instances,
1264 				 * thus change the UID on all of the subcomponents of it */
1265 				for (subcomp = i_cal_component_get_first_component (icomp, icomp_kind);
1266 				     subcomp;
1267 				     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (icomp, icomp_kind)) {
1268 					i_cal_component_set_uid (subcomp, new_uid);
1269 				}
1270 			} else {
1271 				i_cal_component_set_uid (icomp, new_uid);
1272 			}
1273 			g_free (new_uid);
1274 			new_uid = NULL;
1275 		}
1276 
1277 		ftd.source_client = src_client;
1278 		ftd.destination_client = dest_client;
1279 		ftd.cancellable = cancellable;
1280 		ftd.error = error;
1281 		ftd.success = TRUE;
1282 
1283 		if (i_cal_component_isa (icomp) == I_CAL_VCALENDAR_COMPONENT) {
1284 			/* in case of a vCalendar, the component might have detached instances,
1285 			 * thus check timezones on all of the subcomponents of it */
1286 			for (subcomp = i_cal_component_get_first_component (icomp, icomp_kind);
1287 			     subcomp && ftd.success;
1288 			     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (icomp, icomp_kind)) {
1289 				i_cal_component_foreach_tzid (subcomp, add_timezone_to_cal_cb, &ftd);
1290 			}
1291 
1292 			g_clear_object (&subcomp);
1293 		} else {
1294 			i_cal_component_foreach_tzid (icomp, add_timezone_to_cal_cb, &ftd);
1295 		}
1296 
1297 		if (!ftd.success) {
1298 			success = FALSE;
1299 			goto exit;
1300 		}
1301 
1302 		if (i_cal_component_isa (icomp) == I_CAL_VCALENDAR_COMPONENT) {
1303 			gboolean did_add = FALSE;
1304 
1305 			/* in case of a vCalendar, the component might have detached instances,
1306 			 * thus add the master object first, and then all of the subcomponents of it */
1307 			for (subcomp = i_cal_component_get_first_component (icomp, icomp_kind);
1308 			     subcomp && !did_add;
1309 			     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (icomp, icomp_kind)) {
1310 				if (!e_cal_util_component_has_property (subcomp, I_CAL_RECURRENCEID_PROPERTY)) {
1311 					did_add = TRUE;
1312 					success = e_cal_client_create_object_sync (
1313 						dest_client, subcomp, E_CAL_OPERATION_FLAG_DISABLE_ITIP_MESSAGE,
1314 						&new_uid, cancellable, error);
1315 					g_free (new_uid);
1316 				}
1317 			}
1318 
1319 			g_clear_object (&subcomp);
1320 
1321 			if (!success) {
1322 				g_clear_object (&icomp);
1323 				goto exit;
1324 			}
1325 
1326 			/* deal with detached instances */
1327 			for (subcomp = i_cal_component_get_first_component (icomp, icomp_kind);
1328 			     subcomp && success;
1329 			     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (icomp, icomp_kind)) {
1330 				if (e_cal_util_component_has_property (subcomp, I_CAL_RECURRENCEID_PROPERTY)) {
1331 					if (did_add) {
1332 						success = e_cal_client_modify_object_sync (
1333 							dest_client, subcomp,
1334 							E_CAL_OBJ_MOD_THIS, E_CAL_OPERATION_FLAG_DISABLE_ITIP_MESSAGE, cancellable, error);
1335 					} else {
1336 						/* just in case there are only detached instances and no master object */
1337 						did_add = TRUE;
1338 						success = e_cal_client_create_object_sync (
1339 							dest_client, subcomp, E_CAL_OPERATION_FLAG_DISABLE_ITIP_MESSAGE,
1340 							&new_uid, cancellable, error);
1341 						g_free (new_uid);
1342 					}
1343 				}
1344 			}
1345 
1346 			g_clear_object (&subcomp);
1347 		} else {
1348 			success = e_cal_client_create_object_sync (dest_client, icomp, E_CAL_OPERATION_FLAG_DISABLE_ITIP_MESSAGE, &new_uid, cancellable, error);
1349 			g_free (new_uid);
1350 		}
1351 
1352 		g_clear_object (&icomp);
1353 		if (!success)
1354 			goto exit;
1355 
1356 		if (!do_copy) {
1357 			ECalObjModType mod_type = E_CAL_OBJ_MOD_THIS;
1358 
1359 			/* Remove the item from the source calendar. */
1360 			if (e_cal_util_component_is_instance (icomp_event) ||
1361 			    e_cal_util_component_has_recurrences (icomp_event))
1362 				mod_type = E_CAL_OBJ_MOD_ALL;
1363 
1364 			success = e_cal_client_remove_object_sync (src_client, uid, NULL, mod_type, E_CAL_OPERATION_FLAG_DISABLE_ITIP_MESSAGE, cancellable, error);
1365 			if (!success)
1366 				goto exit;
1367 		}
1368 
1369 		g_hash_table_insert (processed_uids, g_strdup (uid), GINT_TO_POINTER (1));
1370 	}
1371 
1372  exit:
1373 	g_hash_table_destroy (processed_uids);
1374 
1375 	return success;
1376 }
1377 
1378 void
cal_comp_util_update_tzid_parameter(ICalProperty * prop,const ICalTime * tt)1379 cal_comp_util_update_tzid_parameter (ICalProperty *prop,
1380 				     const ICalTime *tt)
1381 {
1382 	ICalParameter *param;
1383 	const gchar *tzid = NULL;
1384 
1385 	g_return_if_fail (prop != NULL);
1386 
1387 	if (!tt || !i_cal_time_is_valid_time ((ICalTime *) tt) ||
1388 	    i_cal_time_is_null_time ((ICalTime *) tt))
1389 		return;
1390 
1391 	param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
1392 	if (i_cal_time_get_timezone ((ICalTime *) tt))
1393 		tzid = i_cal_timezone_get_tzid (i_cal_time_get_timezone ((ICalTime *) tt));
1394 
1395 	if (i_cal_time_get_timezone ((ICalTime *) tt) && tzid && *tzid &&
1396 	    !i_cal_time_is_utc ((ICalTime *) tt) &&
1397 	    !i_cal_time_is_date ((ICalTime *) tt)) {
1398 		if (param) {
1399 			i_cal_parameter_set_tzid (param, (gchar *) tzid);
1400 			g_object_unref (param);
1401 		} else {
1402 			param = i_cal_parameter_new_tzid ((gchar *) tzid);
1403 			i_cal_property_take_parameter (prop, param);
1404 		}
1405 	} else if (param) {
1406 		i_cal_property_remove_parameter_by_kind (prop, I_CAL_TZID_PARAMETER);
1407 		g_object_unref (param);
1408 	}
1409 }
1410 
1411 /* Returns <0 for time before today, 0 for today, >0 for after today (future) */
1412 gint
cal_comp_util_compare_time_with_today(const ICalTime * time_tt)1413 cal_comp_util_compare_time_with_today (const ICalTime *time_tt)
1414 {
1415 	ICalTime *now_tt, *tt = (ICalTime *) time_tt;
1416 	gint res;
1417 
1418 	if (!tt || i_cal_time_is_null_time (tt))
1419 		return 0;
1420 
1421 	if (i_cal_time_is_date (tt)) {
1422 		time_t now;
1423 
1424 		/* Compare with localtime, not with UTC */
1425 		now = time (NULL);
1426 		now_tt = e_cal_util_tm_to_icaltime (localtime (&now), TRUE);
1427 
1428 		res = i_cal_time_compare_date_only (tt, now_tt);
1429 	} else {
1430 		now_tt = i_cal_time_new_current_with_zone (i_cal_time_get_timezone (tt));
1431 		i_cal_time_set_timezone (now_tt, i_cal_time_get_timezone (tt));
1432 		if (!i_cal_time_get_second (time_tt))
1433 			i_cal_time_set_second (now_tt, 0);
1434 		res = i_cal_time_compare (tt, now_tt);
1435 	}
1436 
1437 	g_clear_object (&now_tt);
1438 
1439 	return res;
1440 }
1441 
1442 gboolean
cal_comp_util_have_in_new_attendees(const GSList * new_attendees_mails,const gchar * eml)1443 cal_comp_util_have_in_new_attendees (const GSList *new_attendees_mails,
1444 				     const gchar *eml)
1445 {
1446 	const GSList *link;
1447 
1448 	if (!eml)
1449 		return FALSE;
1450 
1451 	for (link = new_attendees_mails; link; link = g_slist_next (link)) {
1452 		if (link->data && g_ascii_strcasecmp (eml, link->data) == 0)
1453 			return TRUE;
1454 	}
1455 
1456 	return FALSE;
1457 }
1458 
1459 static void
free_slist_strs(gpointer data)1460 free_slist_strs (gpointer data)
1461 {
1462 	GSList *lst = data;
1463 
1464 	if (lst) {
1465 		g_slist_foreach (lst, (GFunc) g_free, NULL);
1466 		g_slist_free (lst);
1467 	}
1468 }
1469 
1470 /**
1471  * cal_comp_util_copy_new_attendees:
1472  * @des: Component, to copy to.
1473  * @src: Component, to copy from.
1474  *
1475  * Copies "new-attendees" information from @src to @des component.
1476  **/
1477 void
cal_comp_util_copy_new_attendees(ECalComponent * des,ECalComponent * src)1478 cal_comp_util_copy_new_attendees (ECalComponent *des,
1479 				  ECalComponent *src)
1480 {
1481 	GSList *copy = NULL, *l;
1482 
1483 	g_return_if_fail (src != NULL);
1484 	g_return_if_fail (des != NULL);
1485 
1486 	for (l = g_object_get_data (G_OBJECT (src), "new-attendees"); l; l = l->next) {
1487 		copy = g_slist_append (copy, g_strdup (l->data));
1488 	}
1489 
1490 	g_object_set_data_full (G_OBJECT (des), "new-attendees", copy, free_slist_strs);
1491 }
1492 
1493 /* Takes ownership of the 'emails' */
1494 void
cal_comp_util_set_added_attendees_mails(ECalComponent * comp,GSList * emails)1495 cal_comp_util_set_added_attendees_mails (ECalComponent *comp,
1496 					 GSList *emails)
1497 {
1498 	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
1499 
1500 	g_object_set_data_full (G_OBJECT (comp), "new-attendees", emails, free_slist_strs);
1501 }
1502 
1503 gchar *
cal_comp_util_dup_parameter_xvalue(ICalProperty * prop,const gchar * name)1504 cal_comp_util_dup_parameter_xvalue (ICalProperty *prop,
1505 				    const gchar *name)
1506 {
1507 	ICalParameter *param;
1508 
1509 	if (!prop || !name || !*name)
1510 		return NULL;
1511 
1512 	for (param = i_cal_property_get_first_parameter (prop, I_CAL_X_PARAMETER);
1513 	     param;
1514 	     g_object_unref (param), param = i_cal_property_get_next_parameter (prop, I_CAL_X_PARAMETER)) {
1515 		const gchar *xname = i_cal_parameter_get_xname (param);
1516 
1517 		if (xname && g_ascii_strcasecmp (xname, name) == 0) {
1518 			gchar *value;
1519 
1520 			value = g_strdup (i_cal_parameter_get_xvalue (param));
1521 			g_object_unref (param);
1522 
1523 			return value;
1524 		}
1525 	}
1526 
1527 	return NULL;
1528 }
1529 
1530 gchar *
cal_comp_util_get_attendee_comments(ICalComponent * icomp)1531 cal_comp_util_get_attendee_comments (ICalComponent *icomp)
1532 {
1533 	GString *comments = NULL;
1534 	ICalProperty *prop;
1535 
1536 	g_return_val_if_fail (icomp != NULL, NULL);
1537 
1538 	for (prop = i_cal_component_get_first_property (icomp, I_CAL_ATTENDEE_PROPERTY);
1539 	     prop;
1540 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ATTENDEE_PROPERTY)) {
1541 		gchar *guests_str = NULL;
1542 		guint32 num_guests = 0;
1543 		gchar *value;
1544 
1545 		value = cal_comp_util_dup_parameter_xvalue (prop, "X-NUM-GUESTS");
1546 		if (value && *value)
1547 			num_guests = atoi (value);
1548 		g_free (value);
1549 
1550 		value = cal_comp_util_dup_parameter_xvalue (prop, "X-RESPONSE-COMMENT");
1551 
1552 		if (num_guests)
1553 			guests_str = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "with one guest", "with %d guests", num_guests), num_guests);
1554 
1555 		if (guests_str || (value && *value)) {
1556 			const gchar *email = i_cal_property_get_attendee (prop);
1557 			const gchar *cn = NULL;
1558 			ICalParameter *cnparam;
1559 
1560 			cnparam = i_cal_property_get_first_parameter (prop, I_CAL_CN_PARAMETER);
1561 			if (cnparam) {
1562 				cn = i_cal_parameter_get_cn (cnparam);
1563 				if (!cn || !*cn)
1564 					cn = NULL;
1565 			}
1566 
1567 			email = itip_strip_mailto (email);
1568 
1569 			if ((email && *email) || (cn && *cn)) {
1570 				if (!comments)
1571 					comments = g_string_new ("");
1572 				else
1573 					g_string_append (comments, "\n    ");
1574 
1575 				if (cn && *cn) {
1576 					g_string_append (comments, cn);
1577 
1578 					if (g_strcmp0 (email, cn) == 0)
1579 						email = NULL;
1580 				}
1581 
1582 				if (email && *email) {
1583 					if (cn && *cn)
1584 						g_string_append_printf (comments, " <%s>", email);
1585 					else
1586 						g_string_append (comments, email);
1587 				}
1588 
1589 				g_string_append (comments, ": ");
1590 
1591 				if (guests_str) {
1592 					g_string_append (comments, guests_str);
1593 
1594 					if (value && *value)
1595 						g_string_append (comments, "; ");
1596 				}
1597 
1598 				if (value && *value)
1599 					g_string_append (comments, value);
1600 			}
1601 
1602 			g_clear_object (&cnparam);
1603 		}
1604 
1605 		g_free (guests_str);
1606 		g_free (value);
1607 	}
1608 
1609 	if (comments) {
1610 		gchar *str;
1611 
1612 		str = g_strdup_printf (_("Comments: %s"), comments->str);
1613 		g_string_free (comments, TRUE);
1614 
1615 		return str;
1616 	}
1617 
1618 	return NULL;
1619 }
1620 
1621 static struct _status_values {
1622 	ICalComponentKind kind;
1623 	ICalPropertyStatus status;
1624 	const gchar *text;
1625 } status_values[] = {
1626 	{ I_CAL_VEVENT_COMPONENT,   I_CAL_STATUS_NONE,        NC_("iCalendarStatus", "None") },
1627 	{ I_CAL_VEVENT_COMPONENT,   I_CAL_STATUS_TENTATIVE,   NC_("iCalendarStatus", "Tentative") },
1628 	{ I_CAL_VEVENT_COMPONENT,   I_CAL_STATUS_CONFIRMED,   NC_("iCalendarStatus", "Confirmed") },
1629 	{ I_CAL_VJOURNAL_COMPONENT, I_CAL_STATUS_NONE,        NC_("iCalendarStatus", "None") },
1630 	{ I_CAL_VJOURNAL_COMPONENT, I_CAL_STATUS_DRAFT,       NC_("iCalendarStatus", "Draft") },
1631 	{ I_CAL_VJOURNAL_COMPONENT, I_CAL_STATUS_FINAL,       NC_("iCalendarStatus", "Final") },
1632 	{ I_CAL_VTODO_COMPONENT,    I_CAL_STATUS_NONE,        NC_("iCalendarStatus", "Not Started") },
1633 	{ I_CAL_VTODO_COMPONENT,    I_CAL_STATUS_NEEDSACTION, NC_("iCalendarStatus", "Needs Action") },
1634 	{ I_CAL_VTODO_COMPONENT,    I_CAL_STATUS_INPROCESS,   NC_("iCalendarStatus", "In Progress") },
1635 	{ I_CAL_VTODO_COMPONENT,    I_CAL_STATUS_COMPLETED,   NC_("iCalendarStatus", "Completed") },
1636 	{ I_CAL_ANY_COMPONENT,      I_CAL_STATUS_CANCELLED,   NC_("iCalendarStatus", "Cancelled") }
1637 };
1638 
1639 /**
1640  * cal_comp_util_status_to_localized_string:
1641  * @kind: an #ICalComponentKind of a component the @status belongs to
1642  * @status: an #ICalPropertyStatus
1643  *
1644  * Returns localized text, suitable for user-visible strings,
1645  * corresponding to the @status value. The @kind is used to
1646  * distinguish how to localize certain values.
1647  *
1648  * To transform the returned string back to the enum value
1649  * use cal_comp_util_localized_string_to_status().
1650  *
1651  * Returns: (nullable): the @status as a localized string, or %NULL,
1652  *    when such @status could not be found for the given @kind
1653  *
1654  * Since: 3.34
1655  **/
1656 const gchar *
cal_comp_util_status_to_localized_string(ICalComponentKind kind,ICalPropertyStatus status)1657 cal_comp_util_status_to_localized_string (ICalComponentKind kind,
1658 					  ICalPropertyStatus status)
1659 {
1660 	gint ii;
1661 
1662 	for (ii = 0; ii < G_N_ELEMENTS (status_values); ii++) {
1663 		if ((status_values[ii].kind == kind ||
1664 		     status_values[ii].kind == I_CAL_ANY_COMPONENT ||
1665 		     kind == I_CAL_ANY_COMPONENT) &&
1666 		     status_values[ii].status == status)
1667 			return g_dpgettext2 (GETTEXT_PACKAGE, "iCalendarStatus", status_values[ii].text);
1668 	}
1669 
1670 	return NULL;
1671 }
1672 
1673 /**
1674  * cal_comp_util_localized_string_to_status:
1675  * @kind: an #ICalComponentKind of a component the status belongs to
1676  * @localized_string: (nullable): localized text for the status, or %NULL
1677  * @cmp_func: (scope call) (closure user_data) (nullable): optional compare function, can be %NULL
1678  * @user_data: user data for the @cmp_func
1679  *
1680  * Converts @localized_string returned from cal_comp_util_status_to_localized_string()
1681  * back to an #ICalPropertyStatus enum. Returns %I_CAL_STATUS_NONE, when
1682  * the @localized_string cannot be found for the given @kind.
1683  *
1684  * Returns: an #ICalPropertyStatus corresponding to given @kind and @localized_string,
1685  *    or %I_CAL_STATUS_NONE, when the value cannot be found.
1686  *
1687  * Since: 3.34
1688  **/
1689 ICalPropertyStatus
cal_comp_util_localized_string_to_status(ICalComponentKind kind,const gchar * localized_string,GCompareDataFunc cmp_func,gpointer user_data)1690 cal_comp_util_localized_string_to_status (ICalComponentKind kind,
1691 					  const gchar *localized_string,
1692 					  GCompareDataFunc cmp_func,
1693 					  gpointer user_data)
1694 {
1695 	gint ii;
1696 
1697 	if (!localized_string || !*localized_string)
1698 		return I_CAL_STATUS_NONE;
1699 
1700 	if (!cmp_func) {
1701 		cmp_func = (GCompareDataFunc) e_util_utf8_strcasecmp;
1702 		user_data = NULL;
1703 	}
1704 
1705 	for (ii = 0; ii < G_N_ELEMENTS (status_values); ii++) {
1706 		if ((status_values[ii].kind == kind ||
1707 		     status_values[ii].kind == I_CAL_ANY_COMPONENT ||
1708 		     kind == I_CAL_ANY_COMPONENT) &&
1709 		     cmp_func (localized_string, g_dpgettext2 (GETTEXT_PACKAGE, "iCalendarStatus", status_values[ii].text), user_data) == 0)
1710 			return status_values[ii].status;
1711 	}
1712 
1713 	return I_CAL_STATUS_NONE;
1714 }
1715 
1716 /**
1717  * cal_comp_util_get_status_list_for_kind:
1718  * @kind: an #ICalComponentKind
1719  *
1720  * Returns: (element-type utf8) (transfer container): a #GList of localized strings
1721  *    corresponding to #ICalPropertyStatus usable for the @kind. The caller owns
1722  *    the returned #GList, but not the items of it. In other words, free the returned
1723  *    #GList with g_list_free(), when no longer needed.
1724  *
1725  * Since: 3.34
1726  **/
1727 GList *
cal_comp_util_get_status_list_for_kind(ICalComponentKind kind)1728 cal_comp_util_get_status_list_for_kind (ICalComponentKind kind)
1729 {
1730 	GList *items = NULL;
1731 	gint ii;
1732 
1733 	for (ii = 0; ii < G_N_ELEMENTS (status_values); ii++) {
1734 		if ((status_values[ii].kind == kind ||
1735 		     status_values[ii].kind == I_CAL_ANY_COMPONENT ||
1736 		     kind == I_CAL_ANY_COMPONENT))
1737 			items = g_list_prepend (items, (gpointer) g_dpgettext2 (GETTEXT_PACKAGE, "iCalendarStatus", status_values[ii].text));
1738 	}
1739 
1740 	return g_list_reverse (items);
1741 }
1742 
1743 /**
1744  * cal_comp_util_ensure_allday_timezone:
1745  * @itime: an #ICalTime to update
1746  * @zone: (nullable): an #ICalTimezone to set, or %NULL for UTC
1747  *
1748  * Changes DATE value of @itime to DATETIME using the @zone.
1749  * The @itime is not converted to the @zone, the @zone is
1750  * assigned to it.
1751  *
1752  * Returns: whether made any change
1753  *
1754  * Since: 3.38
1755  **/
1756 gboolean
cal_comp_util_ensure_allday_timezone(ICalTime * itime,ICalTimezone * zone)1757 cal_comp_util_ensure_allday_timezone (ICalTime *itime,
1758 				      ICalTimezone *zone)
1759 {
1760 	g_return_val_if_fail (I_CAL_IS_TIME (itime), FALSE);
1761 
1762 	if (i_cal_time_is_date (itime)) {
1763 		if (!zone)
1764 			zone = i_cal_timezone_get_utc_timezone ();
1765 
1766 		i_cal_time_set_is_date (itime, FALSE);
1767 		i_cal_time_set_time (itime, 0, 0, 0);
1768 		i_cal_time_set_timezone (itime, zone);
1769 
1770 		return TRUE;
1771 	}
1772 
1773 	return FALSE;
1774 }
1775 
1776 static void
ensure_allday_timezone_property(ICalComponent * icomp,ICalTimezone * zone,ICalPropertyKind prop_kind,ICalTime * (* get_func)(ICalComponent * icomp),void (* set_func)(ICalComponent * icomp,ICalTime * dtvalue))1777 ensure_allday_timezone_property (ICalComponent *icomp,
1778 				 ICalTimezone *zone,
1779 				 ICalPropertyKind prop_kind,
1780 				 ICalTime *(* get_func) (ICalComponent *icomp),
1781 				 void (* set_func) (ICalComponent *icomp,
1782 						    ICalTime *dtvalue))
1783 {
1784 	ICalProperty *prop;
1785 
1786 	g_return_if_fail (I_CAL_IS_COMPONENT (icomp));
1787 	g_return_if_fail (get_func != NULL);
1788 	g_return_if_fail (set_func != NULL);
1789 
1790 	prop = i_cal_component_get_first_property (icomp, prop_kind);
1791 
1792 	if (prop) {
1793 		ICalTime *dtvalue;
1794 
1795 		dtvalue = get_func (icomp);
1796 
1797 		if (dtvalue && cal_comp_util_ensure_allday_timezone (dtvalue, zone)) {
1798 			/* Remove the VALUE parameter, to correspond to the actual value being set */
1799 			i_cal_property_remove_parameter_by_kind (prop, I_CAL_VALUE_PARAMETER);
1800 		}
1801 
1802 		set_func (icomp, dtvalue);
1803 		cal_comp_util_update_tzid_parameter (prop, dtvalue);
1804 
1805 		g_clear_object (&dtvalue);
1806 		g_clear_object (&prop);
1807 	}
1808 }
1809 
1810 /**
1811  * cal_comp_util_maybe_ensure_allday_timezone_properties:
1812  * @client: (nullable): an #ECalClient, or NULL, to change it always
1813  * @icomp: an #ICalComponent to update
1814  * @zone: (nullable): an #ICalTimezone to eventually set, or %NULL for floating time
1815  *
1816  * When the @client is not specified, or when it has set %E_CAL_STATIC_CAPABILITY_ALL_DAY_EVENT_AS_TIME,
1817  * calls cal_comp_util_ensure_allday_timezone() for DTSTART
1818  * and DTEND properties, if such exist in the @icomp, and updates
1819  * those accordingly.
1820  *
1821  * Since: 3.38
1822  **/
1823 void
cal_comp_util_maybe_ensure_allday_timezone_properties(ECalClient * client,ICalComponent * icomp,ICalTimezone * zone)1824 cal_comp_util_maybe_ensure_allday_timezone_properties (ECalClient *client,
1825 						       ICalComponent *icomp,
1826 						       ICalTimezone *zone)
1827 {
1828 	if (client)
1829 		g_return_if_fail (E_IS_CAL_CLIENT (client));
1830 
1831 	g_return_if_fail (I_CAL_IS_COMPONENT (icomp));
1832 
1833 	if (client && !e_client_check_capability (E_CLIENT (client), E_CAL_STATIC_CAPABILITY_ALL_DAY_EVENT_AS_TIME))
1834 		return;
1835 
1836 	ensure_allday_timezone_property (icomp, zone, I_CAL_DTSTART_PROPERTY, i_cal_component_get_dtstart, i_cal_component_set_dtstart);
1837 	ensure_allday_timezone_property (icomp, zone, I_CAL_DTEND_PROPERTY, i_cal_component_get_dtend, i_cal_component_set_dtend);
1838 }
1839 
1840 void
cal_comp_util_format_itt(ICalTime * itt,gchar * buffer,gint buffer_size)1841 cal_comp_util_format_itt (ICalTime *itt,
1842 			  gchar *buffer,
1843 			  gint buffer_size)
1844 {
1845 	struct tm tm;
1846 
1847 	g_return_if_fail (itt != NULL);
1848 	g_return_if_fail (buffer != NULL);
1849 	g_return_if_fail (buffer_size > 0);
1850 
1851 	buffer[0] = '\0';
1852 
1853 	tm = e_cal_util_icaltime_to_tm (itt);
1854 	e_datetime_format_format_tm_inline ("calendar", "table", i_cal_time_is_date (itt) ? DTFormatKindDate : DTFormatKindDateTime, &tm, buffer, buffer_size);
1855 }
1856