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