1 /* Evolution calendar - iCalendar file backend
2 *
3 * Copyright (C) 1993 Free Software Foundation, Inc.
4 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5 *
6 * This library is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation.
9 *
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authors: Federico Mena-Quintero <federico@ximian.com>
19 * Rodrigo Moya <rodrigo@ximian.com>
20 * Jan Brittenson <bson@gnu.ai.mit.edu>
21 */
22
23 #include "evolution-data-server-config.h"
24
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <glib/gstdio.h>
31 #include <glib/gi18n-lib.h>
32
33 #include <libedataserver/libedataserver.h>
34
35 #include "e-cal-backend-file-events.h"
36
37 #ifndef O_BINARY
38 #define O_BINARY 0
39 #endif
40
41 #define EC_ERROR(_code) e_client_error_create (_code, NULL)
42 #define EC_ERROR_EX(_code, _msg) e_client_error_create (_code, _msg)
43 #define EC_ERROR_NO_URI() e_client_error_create (E_CLIENT_ERROR_OTHER_ERROR, _("Cannot get URI"))
44 #define ECC_ERROR(_code) e_cal_client_error_create (_code, NULL)
45
46 #define ECAL_REVISION_X_PROP "X-EVOLUTION-DATA-REVISION"
47
48 /* Placeholder for each component and its recurrences */
49 typedef struct {
50 ECalComponent *full_object;
51 GHashTable *recurrences;
52 GList *recurrences_list;
53 } ECalBackendFileObject;
54
55 /* Private part of the ECalBackendFile structure */
56 struct _ECalBackendFilePrivate {
57 /* path where the calendar data is stored */
58 gchar *path;
59
60 /* Filename in the dir */
61 gchar *file_name;
62 gboolean is_dirty;
63 guint dirty_idle_id;
64
65 /* locked in high-level functions to ensure data is consistent
66 * in idle and CORBA thread(s?); because high-level functions
67 * may call other high-level functions the mutex must allow
68 * recursive locking
69 */
70 GRecMutex idle_save_rmutex;
71
72 /* Toplevel VCALENDAR component */
73 ICalComponent *vcalendar;
74
75 /* All the objects in the calendar, hashed by UID. The
76 * hash key *is* the uid returned by cal_component_get_uid(); it is not
77 * copied, so don't free it when you remove an object from the hash
78 * table. Each item in the hash table is a ECalBackendFileObject.
79 */
80 GHashTable *comp_uid_hash;
81
82 EIntervalTree *interval_tree;
83
84 GList *comp;
85
86 /* guards refresh members */
87 GMutex refresh_lock;
88 /* set to TRUE to indicate thread should stop */
89 gboolean refresh_thread_stop;
90 /* set to TRUE when the refresh thread is running;
91 it can happen that the refresh_cond is set, but the thread is gone */
92 gboolean refresh_thread_running;
93 /* condition for refreshing, not NULL when thread exists */
94 GCond *refresh_cond;
95 /* cond to know the refresh thread gone */
96 GCond *refresh_gone_cond;
97 /* increased when backend saves the file */
98 guint refresh_skip;
99
100 GFileMonitor *refresh_monitor;
101
102 /* Just an incremental number to ensure uniqueness across revisions */
103 guint revision_counter;
104
105 /* Only for ETimezoneCache::get_timezone() call */
106 GHashTable *cached_timezones; /* gchar *tzid ~> ICalTimezone * */
107 };
108
109 #define d(x)
110
111 static void free_refresh_data (ECalBackendFile *cbfile);
112
113 static void bump_revision (ECalBackendFile *cbfile);
114
115 static void e_cal_backend_file_timezone_cache_init
116 (ETimezoneCacheInterface *iface);
117
118 static ETimezoneCacheInterface *parent_timezone_cache_interface;
119
G_DEFINE_TYPE_WITH_CODE(ECalBackendFile,e_cal_backend_file,E_TYPE_CAL_BACKEND_SYNC,G_ADD_PRIVATE (ECalBackendFile)G_IMPLEMENT_INTERFACE (E_TYPE_TIMEZONE_CACHE,e_cal_backend_file_timezone_cache_init))120 G_DEFINE_TYPE_WITH_CODE (
121 ECalBackendFile,
122 e_cal_backend_file,
123 E_TYPE_CAL_BACKEND_SYNC,
124 G_ADD_PRIVATE (ECalBackendFile)
125 G_IMPLEMENT_INTERFACE (
126 E_TYPE_TIMEZONE_CACHE,
127 e_cal_backend_file_timezone_cache_init))
128
129 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
130 static void
131 free_object_data (gpointer data)
132 {
133 ECalBackendFileObject *obj_data = data;
134
135 if (obj_data->full_object)
136 g_object_unref (obj_data->full_object);
137 g_hash_table_destroy (obj_data->recurrences);
138 g_list_free (obj_data->recurrences_list);
139
140 g_free (obj_data);
141 }
142
143 /* Saves the calendar data */
144 static gboolean
save_file_when_idle(gpointer user_data)145 save_file_when_idle (gpointer user_data)
146 {
147 ECalBackendFilePrivate *priv;
148 GError *e = NULL;
149 GFile *file, *backup_file;
150 GFileOutputStream *stream;
151 gboolean succeeded;
152 gchar *tmp, *backup_uristr;
153 gchar *buf;
154 ECalBackendFile *cbfile = user_data;
155 gboolean writable;
156
157 priv = cbfile->priv;
158 g_return_val_if_fail (priv->path != NULL, FALSE);
159 g_return_val_if_fail (priv->vcalendar != NULL, FALSE);
160
161 writable = e_cal_backend_get_writable (E_CAL_BACKEND (cbfile));
162
163 g_rec_mutex_lock (&priv->idle_save_rmutex);
164 if (!priv->is_dirty || !writable) {
165 priv->dirty_idle_id = 0;
166 priv->is_dirty = FALSE;
167 g_rec_mutex_unlock (&priv->idle_save_rmutex);
168 return FALSE;
169 }
170
171 file = g_file_new_for_path (priv->path);
172 if (!file)
173 goto error_malformed_uri;
174
175 /* save calendar to backup file */
176 tmp = g_file_get_uri (file);
177 if (!tmp) {
178 g_object_unref (file);
179 goto error_malformed_uri;
180 }
181
182 backup_uristr = g_strconcat (tmp, "~", NULL);
183 backup_file = g_file_new_for_uri (backup_uristr);
184
185 g_free (tmp);
186 g_free (backup_uristr);
187
188 if (!backup_file) {
189 g_object_unref (file);
190 goto error_malformed_uri;
191 }
192
193 priv->refresh_skip++;
194 stream = g_file_replace (backup_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &e);
195 if (!stream || e) {
196 if (stream)
197 g_object_unref (stream);
198
199 g_object_unref (file);
200 g_object_unref (backup_file);
201 priv->refresh_skip--;
202 goto error;
203 }
204
205 buf = i_cal_component_as_ical_string (priv->vcalendar);
206 succeeded = g_output_stream_write_all (G_OUTPUT_STREAM (stream), buf, strlen (buf) * sizeof (gchar), NULL, NULL, &e);
207 g_free (buf);
208
209 if (!succeeded || e) {
210 g_object_unref (stream);
211 g_object_unref (file);
212 g_object_unref (backup_file);
213 goto error;
214 }
215
216 succeeded = g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &e);
217 g_object_unref (stream);
218
219 if (!succeeded || e) {
220 g_object_unref (file);
221 g_object_unref (backup_file);
222 goto error;
223 }
224
225 /* now copy the temporary file to the real file */
226 g_file_move (backup_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &e);
227
228 g_object_unref (file);
229 g_object_unref (backup_file);
230 if (e)
231 goto error;
232
233 priv->is_dirty = FALSE;
234 priv->dirty_idle_id = 0;
235
236 g_rec_mutex_unlock (&priv->idle_save_rmutex);
237
238 return FALSE;
239
240 error_malformed_uri:
241 g_rec_mutex_unlock (&priv->idle_save_rmutex);
242 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
243 _("Cannot save calendar data: Malformed URI."));
244 return FALSE;
245
246 error:
247 g_rec_mutex_unlock (&priv->idle_save_rmutex);
248
249 if (e) {
250 gchar *msg = g_strdup_printf ("%s: %s", _("Cannot save calendar data"), e->message);
251
252 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), msg);
253 g_free (msg);
254 g_error_free (e);
255 } else
256 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), _("Cannot save calendar data"));
257
258 return FALSE;
259 }
260
261 static void
save(ECalBackendFile * cbfile,gboolean do_bump_revision)262 save (ECalBackendFile *cbfile,
263 gboolean do_bump_revision)
264 {
265 ECalBackendFilePrivate *priv;
266
267 if (do_bump_revision)
268 bump_revision (cbfile);
269
270 priv = cbfile->priv;
271
272 g_rec_mutex_lock (&priv->idle_save_rmutex);
273 priv->is_dirty = TRUE;
274
275 if (!priv->dirty_idle_id)
276 priv->dirty_idle_id = g_idle_add ((GSourceFunc) save_file_when_idle, cbfile);
277
278 g_rec_mutex_unlock (&priv->idle_save_rmutex);
279 }
280
281 static void
free_calendar_components(GHashTable * comp_uid_hash,ICalComponent * top_icomp)282 free_calendar_components (GHashTable *comp_uid_hash,
283 ICalComponent *top_icomp)
284 {
285 if (comp_uid_hash)
286 g_hash_table_destroy (comp_uid_hash);
287
288 if (top_icomp)
289 g_object_unref (top_icomp);
290 }
291
292 static void
free_calendar_data(ECalBackendFile * cbfile)293 free_calendar_data (ECalBackendFile *cbfile)
294 {
295 ECalBackendFilePrivate *priv;
296
297 priv = cbfile->priv;
298
299 g_rec_mutex_lock (&priv->idle_save_rmutex);
300
301 if (priv->interval_tree)
302 e_intervaltree_destroy (priv->interval_tree);
303 priv->interval_tree = NULL;
304
305 free_calendar_components (priv->comp_uid_hash, priv->vcalendar);
306 priv->comp_uid_hash = NULL;
307 priv->vcalendar = NULL;
308
309 g_list_free (priv->comp);
310 priv->comp = NULL;
311
312 g_rec_mutex_unlock (&priv->idle_save_rmutex);
313 }
314
315 /* Dispose handler for the file backend */
316 static void
e_cal_backend_file_dispose(GObject * object)317 e_cal_backend_file_dispose (GObject *object)
318 {
319 ECalBackendFile *cbfile;
320 ECalBackendFilePrivate *priv;
321 ESource *source;
322
323 cbfile = E_CAL_BACKEND_FILE (object);
324 priv = cbfile->priv;
325
326 free_refresh_data (E_CAL_BACKEND_FILE (object));
327
328 /* Save if necessary */
329 if (priv->is_dirty)
330 save_file_when_idle (cbfile);
331
332 free_calendar_data (cbfile);
333
334 source = e_backend_get_source (E_BACKEND (cbfile));
335 if (source)
336 g_signal_handlers_disconnect_matched (source, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, cbfile);
337
338 /* Chain up to parent's dispose() method. */
339 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->dispose (object);
340 }
341
342 /* Finalize handler for the file backend */
343 static void
e_cal_backend_file_finalize(GObject * object)344 e_cal_backend_file_finalize (GObject *object)
345 {
346 ECalBackendFilePrivate *priv;
347
348 priv = E_CAL_BACKEND_FILE (object)->priv;
349
350 /* Clean up */
351
352 if (priv->dirty_idle_id)
353 g_source_remove (priv->dirty_idle_id);
354
355 g_mutex_clear (&priv->refresh_lock);
356
357 g_rec_mutex_clear (&priv->idle_save_rmutex);
358 g_hash_table_destroy (priv->cached_timezones);
359
360 g_free (priv->path);
361 g_free (priv->file_name);
362
363 /* Chain up to parent's finalize() method. */
364 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->finalize (object);
365 }
366
367 /* Looks up a component by its UID on the backend's component hash table
368 * and returns TRUE if any event (regardless whether it is the master or a child)
369 * with that UID exists */
370 static gboolean
uid_in_use(ECalBackendFile * cbfile,const gchar * uid)371 uid_in_use (ECalBackendFile *cbfile,
372 const gchar *uid)
373 {
374 ECalBackendFilePrivate *priv;
375 ECalBackendFileObject *obj_data;
376
377 priv = cbfile->priv;
378
379 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
380 return obj_data != NULL;
381 }
382
383 static ICalProperty *
get_revision_property(ECalBackendFile * cbfile)384 get_revision_property (ECalBackendFile *cbfile)
385 {
386 if (!cbfile->priv->vcalendar)
387 return NULL;
388
389 return e_cal_util_component_find_x_property (cbfile->priv->vcalendar, ECAL_REVISION_X_PROP);
390 }
391
392 static gchar *
make_revision_string(ECalBackendFile * cbfile)393 make_revision_string (ECalBackendFile *cbfile)
394 {
395 GTimeVal timeval;
396 gchar *datestr;
397 gchar *revision;
398
399 g_get_current_time (&timeval);
400
401 datestr = g_time_val_to_iso8601 (&timeval);
402 revision = g_strdup_printf ("%s(%d)", datestr, cbfile->priv->revision_counter++);
403
404 g_free (datestr);
405 return revision;
406 }
407
408 static ICalProperty *
ensure_revision(ECalBackendFile * cbfile)409 ensure_revision (ECalBackendFile *cbfile)
410 {
411 ICalProperty *prop;
412
413 if (cbfile->priv->vcalendar == NULL)
414 return NULL;
415
416 prop = get_revision_property (cbfile);
417
418 if (!prop) {
419 gchar *revision = make_revision_string (cbfile);
420
421 e_cal_util_component_set_x_property (cbfile->priv->vcalendar, ECAL_REVISION_X_PROP, revision);
422
423 g_free (revision);
424
425 prop = get_revision_property (cbfile);
426 g_warn_if_fail (prop != NULL);
427 }
428
429 return prop;
430 }
431
432 static void
bump_revision(ECalBackendFile * cbfile)433 bump_revision (ECalBackendFile *cbfile)
434 {
435 /* Update the revision string */
436 ICalProperty *prop = ensure_revision (cbfile);
437 gchar *revision = make_revision_string (cbfile);
438
439 i_cal_property_set_x (prop, revision);
440
441 e_cal_backend_notify_property_changed (E_CAL_BACKEND (cbfile),
442 E_CAL_BACKEND_PROPERTY_REVISION,
443 revision);
444
445 g_object_unref (prop);
446 g_free (revision);
447 }
448
449 /* Calendar backend methods */
450
451 /* Get_email_address handler for the file backend */
452 static gchar *
e_cal_backend_file_get_backend_property(ECalBackend * backend,const gchar * prop_name)453 e_cal_backend_file_get_backend_property (ECalBackend *backend,
454 const gchar *prop_name)
455 {
456 g_return_val_if_fail (prop_name != NULL, FALSE);
457
458 if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
459 return g_strjoin (
460 ",",
461 E_CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS,
462 E_CAL_STATIC_CAPABILITY_NO_THISANDPRIOR,
463 E_CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED,
464 E_CAL_STATIC_CAPABILITY_REMOVE_ONLY_THIS,
465 E_CAL_STATIC_CAPABILITY_BULK_ADDS,
466 E_CAL_STATIC_CAPABILITY_BULK_MODIFIES,
467 E_CAL_STATIC_CAPABILITY_BULK_REMOVES,
468 E_CAL_STATIC_CAPABILITY_ALARM_DESCRIPTION,
469 E_CAL_STATIC_CAPABILITY_TASK_CAN_RECUR,
470 E_CAL_STATIC_CAPABILITY_COMPONENT_COLOR,
471 NULL);
472
473 } else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
474 g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
475 ESourceLocal *local_extension;
476
477 local_extension = e_source_get_extension (e_backend_get_source (E_BACKEND (backend)), E_SOURCE_EXTENSION_LOCAL_BACKEND);
478
479 return e_source_local_dup_email_address (local_extension);
480
481 } else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
482 ECalComponent *comp;
483 gchar *prop_value;
484
485 comp = e_cal_component_new ();
486
487 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
488 case I_CAL_VEVENT_COMPONENT:
489 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
490 break;
491 case I_CAL_VTODO_COMPONENT:
492 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
493 break;
494 case I_CAL_VJOURNAL_COMPONENT:
495 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
496 break;
497 default:
498 g_object_unref (comp);
499 return NULL;
500 }
501
502 prop_value = e_cal_component_get_as_string (comp);
503
504 g_object_unref (comp);
505
506 return prop_value;
507
508 } else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_REVISION)) {
509 ICalProperty *prop;
510 gchar *revision = NULL;
511
512 /* This returns NULL if backend lacks a vcalendar. */
513 prop = ensure_revision (E_CAL_BACKEND_FILE (backend));
514 if (prop) {
515 revision = g_strdup (i_cal_property_get_x (prop));
516 g_object_unref (prop);
517 }
518
519 return revision;
520 }
521
522 /* Chain up to parent's method. */
523 return E_CAL_BACKEND_CLASS (e_cal_backend_file_parent_class)->impl_get_backend_property (backend, prop_name);
524 }
525
526 typedef struct _ResolveTzidData {
527 ICalComponent *vcalendar;
528 GHashTable *zones; /* gchar *tzid ~> ICalTimezone * */
529 } ResolveTzidData;
530
531 static void
resolve_tzid_data_init(ResolveTzidData * rtd,ICalComponent * vcalendar)532 resolve_tzid_data_init (ResolveTzidData *rtd,
533 ICalComponent *vcalendar)
534 {
535 if (rtd) {
536 rtd->vcalendar = vcalendar;
537 rtd->zones = NULL;
538 }
539 }
540
541 /* Clears the content, not the structure */
542 static void
resolve_tzid_data_clear(ResolveTzidData * rtd)543 resolve_tzid_data_clear (ResolveTzidData *rtd)
544 {
545 if (rtd && rtd->zones)
546 g_hash_table_destroy (rtd->zones);
547 }
548
549 /* function to resolve timezones */
550 static ICalTimezone *
resolve_tzid_cb(const gchar * tzid,gpointer user_data,GCancellable * cancellable,GError ** error)551 resolve_tzid_cb (const gchar *tzid,
552 gpointer user_data,
553 GCancellable *cancellable,
554 GError **error)
555 {
556 ResolveTzidData *rtd = user_data;
557 ICalTimezone *zone;
558
559 if (!tzid || !tzid[0])
560 return NULL;
561 else if (!strcmp (tzid, "UTC"))
562 return i_cal_timezone_get_utc_timezone ();
563
564 if (rtd->zones) {
565 zone = g_hash_table_lookup (rtd->zones, tzid);
566 if (zone)
567 return zone;
568 }
569
570 zone = i_cal_timezone_get_builtin_timezone_from_tzid (tzid);
571 if (zone)
572 g_object_ref (zone);
573 else if (rtd->vcalendar)
574 zone = i_cal_component_get_timezone (rtd->vcalendar, tzid);
575
576 if (zone) {
577 if (!rtd->zones)
578 rtd->zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
579
580 g_hash_table_insert (rtd->zones, g_strdup (tzid), zone);
581 }
582
583 return zone;
584 }
585
586 /* Checks if the specified component has a duplicated UID and if so changes it.
587 * UIDs may be shared between components if there is at most one component
588 * without RECURRENCE-ID (master) and all others have different RECURRENCE-ID
589 * values.
590 */
591 static void
check_dup_uid(ECalBackendFile * cbfile,ECalComponent * comp)592 check_dup_uid (ECalBackendFile *cbfile,
593 ECalComponent *comp)
594 {
595 ECalBackendFilePrivate *priv;
596 ECalBackendFileObject *obj_data;
597 const gchar *uid;
598 gchar *new_uid = NULL;
599 gchar *rid = NULL;
600
601 priv = cbfile->priv;
602
603 uid = e_cal_component_get_uid (comp);
604
605 if (!uid) {
606 g_warning ("Checking for duplicate uid, the component does not have a valid UID skipping it\n");
607 return;
608 }
609
610 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
611 if (!obj_data)
612 return; /* Everything is fine */
613
614 rid = e_cal_component_get_recurid_as_string (comp);
615 if (rid && *rid) {
616 /* new component has rid, must not be the same as in other detached recurrence */
617 if (!g_hash_table_lookup (obj_data->recurrences, rid))
618 goto done;
619 } else {
620 /* new component has no rid, must not clash with existing master */
621 if (!obj_data->full_object)
622 goto done;
623 }
624
625 d (
626 g_message (G_STRLOC ": Got object with duplicated UID `%s' and rid `%s', changing it...",
627 uid,
628 rid ? rid : ""));
629
630 new_uid = e_util_generate_uid ();
631 e_cal_component_set_uid (comp, new_uid);
632
633 /* FIXME: I think we need to reset the SEQUENCE property and reset the
634 * CREATED/DTSTAMP/LAST-MODIFIED.
635 */
636
637 save (cbfile, FALSE);
638
639 done:
640 g_free (rid);
641 g_free (new_uid);
642 }
643
644 static time_t
get_rid_as_time_t(ECalComponent * comp)645 get_rid_as_time_t (ECalComponent *comp)
646 {
647 ECalComponentRange *range;
648 ECalComponentDateTime *dt;
649 time_t tmt = (time_t) -1;
650
651 range = e_cal_component_get_recurid (comp);
652 if (!range)
653 return tmt;
654
655 dt = e_cal_component_range_get_datetime (range);
656 if (!dt) {
657 e_cal_component_range_free (range);
658 return tmt;
659 }
660
661 tmt = i_cal_time_as_timet (e_cal_component_datetime_get_value (dt));
662
663 e_cal_component_range_free (range);
664
665 return tmt;
666 }
667
668 /* Adds component to the interval tree
669 */
670 static void
add_component_to_intervaltree(ECalBackendFile * cbfile,ECalComponent * comp)671 add_component_to_intervaltree (ECalBackendFile *cbfile,
672 ECalComponent *comp)
673 {
674 time_t time_start = -1, time_end = -1;
675 ECalBackendFilePrivate *priv;
676 ResolveTzidData rtd;
677
678 g_return_if_fail (cbfile != NULL);
679 g_return_if_fail (comp != NULL);
680
681 priv = cbfile->priv;
682
683 resolve_tzid_data_init (&rtd, cbfile->priv->vcalendar);
684
685 e_cal_util_get_component_occur_times (
686 comp, &time_start, &time_end,
687 resolve_tzid_cb, &rtd, i_cal_timezone_get_utc_timezone (),
688 e_cal_backend_get_kind (E_CAL_BACKEND (cbfile)));
689
690 resolve_tzid_data_clear (&rtd);
691
692 if (time_end != -1 && time_start > time_end) {
693 gchar *str = e_cal_component_get_as_string (comp);
694 g_print ("Bogus component %s\n", str);
695 g_free (str);
696 } else {
697 g_rec_mutex_lock (&priv->idle_save_rmutex);
698 e_intervaltree_insert (priv->interval_tree, time_start, time_end, comp);
699 g_rec_mutex_unlock (&priv->idle_save_rmutex);
700 }
701 }
702
703 static gboolean
remove_component_from_intervaltree(ECalBackendFile * cbfile,ECalComponent * comp)704 remove_component_from_intervaltree (ECalBackendFile *cbfile,
705 ECalComponent *comp)
706 {
707 const gchar *uid;
708 gchar *rid;
709 gboolean res;
710 ECalBackendFilePrivate *priv;
711
712 g_return_val_if_fail (cbfile != NULL, FALSE);
713 g_return_val_if_fail (comp != NULL, FALSE);
714
715 priv = cbfile->priv;
716
717 uid = e_cal_component_get_uid (comp);
718 rid = e_cal_component_get_recurid_as_string (comp);
719
720 g_rec_mutex_lock (&priv->idle_save_rmutex);
721 res = e_intervaltree_remove (priv->interval_tree, uid, rid);
722 g_rec_mutex_unlock (&priv->idle_save_rmutex);
723
724 g_free (rid);
725
726 return res;
727 }
728
729 /* Tries to add an ICalComponent to the file backend. We only store the objects
730 * of the types we support; all others just remain in the toplevel component so
731 * that we don't lose them.
732 *
733 * The caller is responsible for ensuring that the component has a UID and that
734 * the UID is not in use already.
735 */
736 static void
add_component(ECalBackendFile * cbfile,ECalComponent * comp,gboolean add_to_toplevel)737 add_component (ECalBackendFile *cbfile,
738 ECalComponent *comp,
739 gboolean add_to_toplevel)
740 {
741 ECalBackendFilePrivate *priv;
742 ECalBackendFileObject *obj_data;
743 const gchar *uid;
744
745 priv = cbfile->priv;
746
747 uid = e_cal_component_get_uid (comp);
748
749 if (!uid) {
750 g_warning ("The component does not have a valid UID skipping it\n");
751 return;
752 }
753
754 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
755 if (e_cal_component_is_instance (comp)) {
756 gchar *rid;
757
758 rid = e_cal_component_get_recurid_as_string (comp);
759 if (obj_data) {
760 if (g_hash_table_lookup (obj_data->recurrences, rid)) {
761 g_warning (G_STRLOC ": Tried to add an already existing recurrence");
762 g_free (rid);
763 return;
764 }
765 } else {
766 obj_data = g_new0 (ECalBackendFileObject, 1);
767 obj_data->full_object = NULL;
768 obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
769 g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
770 }
771
772 g_hash_table_insert (obj_data->recurrences, rid, comp);
773 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
774 } else {
775 if (obj_data) {
776 if (obj_data->full_object) {
777 g_warning (G_STRLOC ": Tried to add an already existing object");
778 return;
779 }
780
781 obj_data->full_object = comp;
782 } else {
783 obj_data = g_new0 (ECalBackendFileObject, 1);
784 obj_data->full_object = comp;
785 obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
786
787 g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
788 }
789 }
790
791 add_component_to_intervaltree (cbfile, comp);
792
793 priv->comp = g_list_prepend (priv->comp, comp);
794
795 /* Put the object in the toplevel component if required */
796
797 if (add_to_toplevel) {
798 ICalComponent *icomp;
799
800 icomp = e_cal_component_get_icalcomponent (comp);
801 g_return_if_fail (icomp != NULL);
802
803 i_cal_component_add_component (priv->vcalendar, icomp);
804 }
805 }
806
807 /* g_hash_table_foreach_remove() callback to remove recurrences from the calendar */
808 static gboolean
remove_recurrence_cb(gpointer key,gpointer value,gpointer data)809 remove_recurrence_cb (gpointer key,
810 gpointer value,
811 gpointer data)
812 {
813 ICalComponent *icomp;
814 ECalBackendFilePrivate *priv;
815 ECalComponent *comp = value;
816 ECalBackendFile *cbfile = data;
817
818 priv = cbfile->priv;
819
820 /* remove the recurrence from the top-level calendar */
821 icomp = e_cal_component_get_icalcomponent (comp);
822 g_return_val_if_fail (icomp != NULL, FALSE);
823
824 icomp = g_object_ref (icomp);
825
826 if (!remove_component_from_intervaltree (cbfile, comp)) {
827 g_message (G_STRLOC " Could not remove component from interval tree!");
828 }
829 i_cal_component_remove_component (priv->vcalendar, icomp);
830
831 g_object_unref (icomp);
832
833 /* remove it from our mapping */
834 priv->comp = g_list_remove (priv->comp, comp);
835
836 return TRUE;
837 }
838
839 /* Removes a component from the backend's hash and lists. Does not perform
840 * notification on the clients. Also removes the component from the toplevel
841 * ICalComponent.
842 */
843 static void
remove_component(ECalBackendFile * cbfile,const gchar * uid,ECalBackendFileObject * obj_data)844 remove_component (ECalBackendFile *cbfile,
845 const gchar *uid,
846 ECalBackendFileObject *obj_data)
847 {
848 ECalBackendFilePrivate *priv;
849 ICalComponent *icomp;
850 GList *l;
851
852 priv = cbfile->priv;
853
854 /* Remove the ICalComponent from the toplevel */
855 if (obj_data->full_object) {
856 icomp = e_cal_component_get_icalcomponent (obj_data->full_object);
857 g_return_if_fail (icomp != NULL);
858
859 i_cal_component_remove_component (priv->vcalendar, icomp);
860
861 /* Remove it from our mapping */
862 l = g_list_find (priv->comp, obj_data->full_object);
863 g_return_if_fail (l != NULL);
864 priv->comp = g_list_delete_link (priv->comp, l);
865
866 if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) {
867 g_message (G_STRLOC " Could not remove component from interval tree!");
868 }
869 }
870
871 /* remove the recurrences also */
872 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_recurrence_cb, cbfile);
873
874 g_hash_table_remove (priv->comp_uid_hash, uid);
875
876 save (cbfile, TRUE);
877 }
878
879 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
880 static void
scan_vcalendar(ECalBackendFile * cbfile)881 scan_vcalendar (ECalBackendFile *cbfile)
882 {
883 ECalBackendFilePrivate *priv;
884 ICalCompIter *iter;
885 ICalComponent *icomp;
886
887 priv = cbfile->priv;
888 g_return_if_fail (priv->vcalendar != NULL);
889 g_return_if_fail (priv->comp_uid_hash != NULL);
890
891 iter = i_cal_component_begin_component (priv->vcalendar, I_CAL_ANY_COMPONENT);
892 icomp = iter ? i_cal_comp_iter_deref (iter) : NULL;
893 while (icomp) {
894 ICalComponentKind kind;
895 ECalComponent *comp;
896
897 kind = i_cal_component_isa (icomp);
898
899 if (kind == I_CAL_VEVENT_COMPONENT ||
900 kind == I_CAL_VTODO_COMPONENT ||
901 kind == I_CAL_VJOURNAL_COMPONENT) {
902 comp = e_cal_component_new ();
903
904 if (e_cal_component_set_icalcomponent (comp, icomp)) {
905 /* Thus it's not freed while being used in the 'comp' */
906 g_object_ref (icomp);
907
908 check_dup_uid (cbfile, comp);
909
910 add_component (cbfile, comp, FALSE);
911 } else {
912 g_object_unref (comp);
913 }
914 }
915
916 g_object_unref (icomp);
917 icomp = i_cal_comp_iter_next (iter);
918 }
919
920 g_clear_object (&iter);
921 }
922
923 static gchar *
uri_to_path(ECalBackend * backend)924 uri_to_path (ECalBackend *backend)
925 {
926 ECalBackendFile *cbfile;
927 ECalBackendFilePrivate *priv;
928 ESource *source;
929 ESourceLocal *local_extension;
930 GFile *custom_file;
931 const gchar *extension_name;
932 const gchar *cache_dir;
933 gchar *filename = NULL;
934
935 cbfile = E_CAL_BACKEND_FILE (backend);
936 priv = cbfile->priv;
937
938 cache_dir = e_cal_backend_get_cache_dir (backend);
939
940 source = e_backend_get_source (E_BACKEND (backend));
941
942 extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
943 local_extension = e_source_get_extension (source, extension_name);
944
945 custom_file = e_source_local_dup_custom_file (local_extension);
946 if (custom_file != NULL) {
947 filename = g_file_get_path (custom_file);
948 g_object_unref (custom_file);
949 }
950
951 if (filename == NULL)
952 filename = g_build_filename (cache_dir, priv->file_name, NULL);
953
954 if (filename != NULL && *filename == '\0') {
955 g_free (filename);
956 filename = NULL;
957 }
958
959 return filename;
960 }
961
962 static gpointer
refresh_thread_func(gpointer data)963 refresh_thread_func (gpointer data)
964 {
965 ECalBackendFile *cbfile = data;
966 ECalBackendFilePrivate *priv;
967 ESource *source;
968 ESourceLocal *extension;
969 GFileInfo *info;
970 GFile *file;
971 const gchar *extension_name;
972 guint64 last_modified, modified;
973
974 g_return_val_if_fail (cbfile != NULL, NULL);
975 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
976
977 priv = cbfile->priv;
978
979 extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
980 source = e_backend_get_source (E_BACKEND (cbfile));
981 extension = e_source_get_extension (source, extension_name);
982
983 /* This returns a newly-created GFile. */
984 file = e_source_local_dup_custom_file (extension);
985 if (!file) {
986 g_mutex_lock (&priv->refresh_lock);
987 priv->refresh_thread_running = FALSE;
988 g_cond_signal (priv->refresh_gone_cond);
989 g_mutex_unlock (&priv->refresh_lock);
990
991 return NULL;
992 }
993
994 info = g_file_query_info (
995 file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
996 G_FILE_QUERY_INFO_NONE, NULL, NULL);
997 if (info) {
998 last_modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
999 g_object_unref (info);
1000 } else {
1001 last_modified = 0;
1002 }
1003
1004 g_mutex_lock (&priv->refresh_lock);
1005 while (!priv->refresh_thread_stop) {
1006 g_cond_wait (priv->refresh_cond, &priv->refresh_lock);
1007
1008 g_rec_mutex_lock (&priv->idle_save_rmutex);
1009
1010 if (priv->refresh_skip > 0) {
1011 priv->refresh_skip--;
1012 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1013 continue;
1014 }
1015
1016 if (priv->is_dirty) {
1017 /* save before reload, if dirty */
1018 if (priv->dirty_idle_id) {
1019 g_source_remove (priv->dirty_idle_id);
1020 priv->dirty_idle_id = 0;
1021 }
1022 save_file_when_idle (cbfile);
1023 priv->refresh_skip = 0;
1024 }
1025
1026 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1027
1028 info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL);
1029 if (!info)
1030 break;
1031
1032 modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1033 g_object_unref (info);
1034
1035 if (modified != last_modified) {
1036 last_modified = modified;
1037 e_cal_backend_file_reload (cbfile, NULL);
1038 }
1039 }
1040
1041 g_object_unref (file);
1042 priv->refresh_thread_running = FALSE;
1043 g_cond_signal (priv->refresh_gone_cond);
1044 g_mutex_unlock (&priv->refresh_lock);
1045
1046 return NULL;
1047 }
1048
1049 static void
custom_file_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,ECalBackendFilePrivate * priv)1050 custom_file_changed (GFileMonitor *monitor,
1051 GFile *file,
1052 GFile *other_file,
1053 GFileMonitorEvent event_type,
1054 ECalBackendFilePrivate *priv)
1055 {
1056 if (priv->refresh_cond)
1057 g_cond_signal (priv->refresh_cond);
1058 }
1059
1060 static void
prepare_refresh_data(ECalBackendFile * cbfile)1061 prepare_refresh_data (ECalBackendFile *cbfile)
1062 {
1063 ECalBackendFilePrivate *priv;
1064 ESource *source;
1065 ESourceLocal *local_extension;
1066 GFile *custom_file;
1067 const gchar *extension_name;
1068
1069 g_return_if_fail (cbfile != NULL);
1070
1071 priv = cbfile->priv;
1072
1073 g_mutex_lock (&priv->refresh_lock);
1074
1075 priv->refresh_thread_stop = FALSE;
1076 priv->refresh_skip = 0;
1077
1078 source = e_backend_get_source (E_BACKEND (cbfile));
1079
1080 extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
1081 local_extension = e_source_get_extension (source, extension_name);
1082
1083 custom_file = e_source_local_dup_custom_file (local_extension);
1084
1085 if (custom_file != NULL) {
1086 GError *error = NULL;
1087
1088 priv->refresh_monitor = g_file_monitor_file (
1089 custom_file, G_FILE_MONITOR_WATCH_MOUNTS, NULL, &error);
1090
1091 if (error == NULL) {
1092 g_signal_connect (
1093 priv->refresh_monitor, "changed",
1094 G_CALLBACK (custom_file_changed), priv);
1095 } else {
1096 g_warning ("%s", error->message);
1097 g_error_free (error);
1098 }
1099
1100 g_object_unref (custom_file);
1101 }
1102
1103 if (priv->refresh_monitor) {
1104 GThread *thread;
1105
1106 priv->refresh_cond = g_new0 (GCond, 1);
1107 priv->refresh_gone_cond = g_new0 (GCond, 1);
1108 priv->refresh_thread_running = TRUE;
1109
1110 thread = g_thread_new (NULL, refresh_thread_func, cbfile);
1111 g_thread_unref (thread);
1112 }
1113
1114 g_mutex_unlock (&priv->refresh_lock);
1115 }
1116
1117 static void
free_refresh_data(ECalBackendFile * cbfile)1118 free_refresh_data (ECalBackendFile *cbfile)
1119 {
1120 ECalBackendFilePrivate *priv;
1121
1122 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
1123
1124 priv = cbfile->priv;
1125
1126 g_mutex_lock (&priv->refresh_lock);
1127
1128 g_clear_object (&priv->refresh_monitor);
1129
1130 if (priv->refresh_cond) {
1131 priv->refresh_thread_stop = TRUE;
1132 g_cond_signal (priv->refresh_cond);
1133
1134 while (priv->refresh_thread_running) {
1135 g_cond_wait (priv->refresh_gone_cond, &priv->refresh_lock);
1136 }
1137
1138 g_cond_clear (priv->refresh_cond);
1139 g_free (priv->refresh_cond);
1140 priv->refresh_cond = NULL;
1141 g_cond_clear (priv->refresh_gone_cond);
1142 g_free (priv->refresh_gone_cond);
1143 priv->refresh_gone_cond = NULL;
1144 }
1145
1146 priv->refresh_skip = 0;
1147
1148 g_mutex_unlock (&priv->refresh_lock);
1149 }
1150
1151 static void
cal_backend_file_take_icomp(ECalBackendFile * cbfile,ICalComponent * icomp)1152 cal_backend_file_take_icomp (ECalBackendFile *cbfile,
1153 ICalComponent *icomp)
1154 {
1155 ICalProperty *prop;
1156
1157 g_warn_if_fail (cbfile->priv->vcalendar == NULL);
1158 cbfile->priv->vcalendar = icomp;
1159
1160 prop = ensure_revision (cbfile);
1161
1162 e_cal_backend_notify_property_changed (
1163 E_CAL_BACKEND (cbfile),
1164 E_CAL_BACKEND_PROPERTY_REVISION,
1165 i_cal_property_get_x (prop));
1166
1167 g_clear_object (&prop);
1168 }
1169
1170 /* Parses an open iCalendar file and loads it into the backend */
1171 static void
open_cal(ECalBackendFile * cbfile,const gchar * uristr,GError ** perror)1172 open_cal (ECalBackendFile *cbfile,
1173 const gchar *uristr,
1174 GError **perror)
1175 {
1176 ECalBackendFilePrivate *priv;
1177 ICalComponent *icomp;
1178
1179 priv = cbfile->priv;
1180
1181 free_refresh_data (cbfile);
1182
1183 icomp = e_cal_util_parse_ics_file (uristr);
1184 if (!icomp) {
1185 g_propagate_error (perror, e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR, _("Cannot parse ISC file “%s”"), uristr));
1186 return;
1187 }
1188
1189 /* FIXME: should we try to demangle XROOT components and
1190 * individual components as well?
1191 */
1192
1193 if (i_cal_component_isa (icomp) != I_CAL_VCALENDAR_COMPONENT) {
1194 g_object_unref (icomp);
1195
1196 g_propagate_error (perror, e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR, _("File “%s” is not a VCALENDAR component"), uristr));
1197 return;
1198 }
1199
1200 g_rec_mutex_lock (&priv->idle_save_rmutex);
1201
1202 cal_backend_file_take_icomp (cbfile, icomp);
1203 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1204
1205 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1206 priv->interval_tree = e_intervaltree_new ();
1207 scan_vcalendar (cbfile);
1208
1209 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1210
1211 prepare_refresh_data (cbfile);
1212 }
1213
1214 typedef struct
1215 {
1216 ECalBackend *backend;
1217 GHashTable *old_uid_hash;
1218 GHashTable *new_uid_hash;
1219 }
1220 BackendDeltaContext;
1221
1222 static void
notify_removals_cb(gpointer key,gpointer value,gpointer data)1223 notify_removals_cb (gpointer key,
1224 gpointer value,
1225 gpointer data)
1226 {
1227 BackendDeltaContext *context = data;
1228 const gchar *uid = key;
1229 ECalBackendFileObject *old_obj_data = value;
1230
1231 if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
1232 ECalComponentId *id;
1233
1234 /* Object was removed */
1235
1236 if (!old_obj_data->full_object)
1237 return;
1238
1239 id = e_cal_component_get_id (old_obj_data->full_object);
1240
1241 e_cal_backend_notify_component_removed (context->backend, id, old_obj_data->full_object, NULL);
1242
1243 e_cal_component_id_free (id);
1244 }
1245 }
1246
1247 static void
notify_adds_modifies_cb(gpointer key,gpointer value,gpointer data)1248 notify_adds_modifies_cb (gpointer key,
1249 gpointer value,
1250 gpointer data)
1251 {
1252 BackendDeltaContext *context = data;
1253 const gchar *uid = key;
1254 ECalBackendFileObject *new_obj_data = value;
1255 ECalBackendFileObject *old_obj_data;
1256
1257 old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
1258
1259 if (!old_obj_data) {
1260 /* Object was added */
1261 if (!new_obj_data->full_object)
1262 return;
1263
1264 e_cal_backend_notify_component_created (context->backend, new_obj_data->full_object);
1265 } else {
1266 gchar *old_obj_str, *new_obj_str;
1267
1268 if (!old_obj_data->full_object || !new_obj_data->full_object)
1269 return;
1270
1271 /* There should be better ways to compare an ICalComponent
1272 * than serializing and comparing the strings...
1273 */
1274 old_obj_str = e_cal_component_get_as_string (old_obj_data->full_object);
1275 new_obj_str = e_cal_component_get_as_string (new_obj_data->full_object);
1276 if (old_obj_str && new_obj_str && strcmp (old_obj_str, new_obj_str) != 0) {
1277 /* Object was modified */
1278 e_cal_backend_notify_component_modified (context->backend, old_obj_data->full_object, new_obj_data->full_object);
1279 }
1280
1281 g_free (old_obj_str);
1282 g_free (new_obj_str);
1283 }
1284 }
1285
1286 static void
notify_changes(ECalBackendFile * cbfile,GHashTable * old_uid_hash,GHashTable * new_uid_hash)1287 notify_changes (ECalBackendFile *cbfile,
1288 GHashTable *old_uid_hash,
1289 GHashTable *new_uid_hash)
1290 {
1291 BackendDeltaContext context;
1292
1293 context.backend = E_CAL_BACKEND (cbfile);
1294 context.old_uid_hash = old_uid_hash;
1295 context.new_uid_hash = new_uid_hash;
1296
1297 g_hash_table_foreach (old_uid_hash, (GHFunc) notify_removals_cb, &context);
1298 g_hash_table_foreach (new_uid_hash, (GHFunc) notify_adds_modifies_cb, &context);
1299 }
1300
1301 static void
reload_cal(ECalBackendFile * cbfile,const gchar * uristr,GError ** perror)1302 reload_cal (ECalBackendFile *cbfile,
1303 const gchar *uristr,
1304 GError **perror)
1305 {
1306 ECalBackendFilePrivate *priv;
1307 ICalComponent *icomp, *icomp_old;
1308 GHashTable *comp_uid_hash_old;
1309
1310 priv = cbfile->priv;
1311
1312 icomp = e_cal_util_parse_ics_file (uristr);
1313 if (!icomp) {
1314 g_propagate_error (perror, e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR, _("Cannot parse ISC file “%s”"), uristr));
1315 return;
1316 }
1317
1318 /* FIXME: should we try to demangle XROOT components and
1319 * individual components as well?
1320 */
1321
1322 if (i_cal_component_isa (icomp) != I_CAL_VCALENDAR_COMPONENT) {
1323 g_object_unref (icomp);
1324
1325 g_propagate_error (perror, e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR, _("File “%s” is not a VCALENDAR component"), uristr));
1326 return;
1327 }
1328
1329 /* Keep old data for comparison - free later */
1330
1331 g_rec_mutex_lock (&priv->idle_save_rmutex);
1332
1333 icomp_old = priv->vcalendar;
1334 priv->vcalendar = NULL;
1335
1336 comp_uid_hash_old = priv->comp_uid_hash;
1337 priv->comp_uid_hash = NULL;
1338
1339 /* Load new calendar */
1340
1341 free_calendar_data (cbfile);
1342
1343 cal_backend_file_take_icomp (cbfile, icomp);
1344
1345 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1346 priv->interval_tree = e_intervaltree_new ();
1347 scan_vcalendar (cbfile);
1348
1349 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1350
1351 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1352
1353 /* Compare old and new versions of calendar */
1354
1355 notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
1356
1357 /* Free old data */
1358
1359 free_calendar_components (comp_uid_hash_old, icomp_old);
1360 }
1361
1362 static void
create_cal(ECalBackendFile * cbfile,const gchar * uristr,GError ** perror)1363 create_cal (ECalBackendFile *cbfile,
1364 const gchar *uristr,
1365 GError **perror)
1366 {
1367 gchar *dirname;
1368 ECalBackendFilePrivate *priv;
1369 ICalComponent *icomp;
1370
1371 free_refresh_data (cbfile);
1372
1373 priv = cbfile->priv;
1374
1375 /* Create the directory to contain the file */
1376 dirname = g_path_get_dirname (uristr);
1377 if (g_mkdir_with_parents (dirname, 0700) != 0) {
1378 g_free (dirname);
1379 g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
1380 return;
1381 }
1382
1383 g_free (dirname);
1384
1385 g_rec_mutex_lock (&priv->idle_save_rmutex);
1386
1387 /* Create the new calendar information */
1388 icomp = e_cal_util_new_top_level ();
1389 cal_backend_file_take_icomp (cbfile, icomp);
1390
1391 /* Create our internal data */
1392 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1393 priv->interval_tree = e_intervaltree_new ();
1394
1395 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1396
1397 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1398
1399 save (cbfile, TRUE);
1400
1401 prepare_refresh_data (cbfile);
1402 }
1403
1404 static gchar *
get_uri_string(ECalBackend * backend)1405 get_uri_string (ECalBackend *backend)
1406 {
1407 gchar *str_uri, *full_uri;
1408
1409 str_uri = uri_to_path (backend);
1410 full_uri = g_uri_unescape_string (str_uri, "");
1411 g_free (str_uri);
1412
1413 return full_uri;
1414 }
1415
1416 static gboolean
get_source_writable(EBackend * backend)1417 get_source_writable (EBackend *backend)
1418 {
1419 ESource *source;
1420 ESourceLocal *extension;
1421
1422 source = e_backend_get_source (backend);
1423
1424 if (!e_source_get_writable (source))
1425 return FALSE;
1426
1427 if (!e_source_has_extension (source, E_SOURCE_EXTENSION_LOCAL_BACKEND))
1428 return TRUE;
1429
1430 extension = e_source_get_extension (source, E_SOURCE_EXTENSION_LOCAL_BACKEND);
1431
1432 return !e_source_local_get_custom_file (extension) ||
1433 e_source_local_get_writable (extension);
1434 }
1435
1436 static void
source_changed_cb(ESource * source,ECalBackend * backend)1437 source_changed_cb (ESource *source,
1438 ECalBackend *backend)
1439 {
1440 ESourceLocal *extension;
1441 const gchar *extension_name;
1442 gboolean backend_writable;
1443 gboolean source_writable;
1444
1445 g_return_if_fail (source != NULL);
1446 g_return_if_fail (E_IS_CAL_BACKEND (backend));
1447
1448 extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
1449 extension = e_source_get_extension (source, extension_name);
1450
1451 if (e_source_local_get_custom_file (extension) == NULL)
1452 return;
1453
1454 source_writable = get_source_writable (E_BACKEND (backend));
1455 backend_writable = e_cal_backend_get_writable (backend);
1456
1457 if (source_writable != backend_writable) {
1458 backend_writable = source_writable;
1459
1460 if (source_writable) {
1461 gchar *str_uri = get_uri_string (backend);
1462
1463 g_return_if_fail (str_uri != NULL);
1464
1465 backend_writable = (g_access (str_uri, W_OK) == 0);
1466
1467 g_free (str_uri);
1468 }
1469
1470 e_cal_backend_set_writable (backend, backend_writable);
1471 }
1472 }
1473
1474 /* Open handler for the file backend */
1475 static void
e_cal_backend_file_open(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,GError ** perror)1476 e_cal_backend_file_open (ECalBackendSync *backend,
1477 EDataCal *cal,
1478 GCancellable *cancellable,
1479 GError **perror)
1480 {
1481 ECalBackendFile *cbfile;
1482 ECalBackendFilePrivate *priv;
1483 gchar *str_uri;
1484 gboolean writable = FALSE;
1485 GError *err = NULL;
1486
1487 cbfile = E_CAL_BACKEND_FILE (backend);
1488 priv = cbfile->priv;
1489 g_rec_mutex_lock (&priv->idle_save_rmutex);
1490
1491 /* Local source is always connected. */
1492 e_source_set_connection_status (e_backend_get_source (E_BACKEND (backend)),
1493 E_SOURCE_CONNECTION_STATUS_CONNECTED);
1494
1495 /* Claim a succesful open if we are already open */
1496 if (priv->path && priv->comp_uid_hash) {
1497 /* Success */
1498 goto done;
1499 }
1500
1501 str_uri = get_uri_string (E_CAL_BACKEND (backend));
1502 if (!str_uri) {
1503 err = EC_ERROR_NO_URI ();
1504 goto done;
1505 }
1506
1507 writable = TRUE;
1508 if (g_access (str_uri, R_OK) == 0) {
1509 open_cal (cbfile, str_uri, &err);
1510 if (g_access (str_uri, W_OK) != 0)
1511 writable = FALSE;
1512 } else {
1513 create_cal (cbfile, str_uri, &err);
1514 }
1515
1516 if (!err) {
1517 if (writable) {
1518 ESource *source;
1519
1520 source = e_backend_get_source (E_BACKEND (backend));
1521
1522 g_signal_connect (
1523 source, "changed",
1524 G_CALLBACK (source_changed_cb), backend);
1525
1526 if (!get_source_writable (E_BACKEND (backend)))
1527 writable = FALSE;
1528 }
1529 }
1530
1531 g_free (str_uri);
1532
1533 done:
1534 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1535 e_cal_backend_set_writable (E_CAL_BACKEND (backend), writable);
1536 e_backend_set_online (E_BACKEND (backend), TRUE);
1537
1538 if (err)
1539 g_propagate_error (perror, g_error_copy (err));
1540 }
1541
1542 static void
add_detached_recur_to_vcalendar(gpointer key,gpointer value,gpointer user_data)1543 add_detached_recur_to_vcalendar (gpointer key,
1544 gpointer value,
1545 gpointer user_data)
1546 {
1547 ECalComponent *recurrence = value;
1548 ICalComponent *vcalendar = user_data;
1549
1550 i_cal_component_take_component (
1551 vcalendar,
1552 i_cal_component_clone (e_cal_component_get_icalcomponent (recurrence)));
1553 }
1554
1555 /* Get_object_component handler for the file backend */
1556 static void
e_cal_backend_file_get_object(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * uid,const gchar * rid,gchar ** object,GError ** error)1557 e_cal_backend_file_get_object (ECalBackendSync *backend,
1558 EDataCal *cal,
1559 GCancellable *cancellable,
1560 const gchar *uid,
1561 const gchar *rid,
1562 gchar **object,
1563 GError **error)
1564 {
1565 ECalBackendFile *cbfile;
1566 ECalBackendFilePrivate *priv;
1567 ECalBackendFileObject *obj_data;
1568
1569 cbfile = E_CAL_BACKEND_FILE (backend);
1570 priv = cbfile->priv;
1571
1572 if (priv->vcalendar == NULL) {
1573 g_set_error_literal (
1574 error, E_CAL_CLIENT_ERROR,
1575 E_CAL_CLIENT_ERROR_INVALID_OBJECT,
1576 e_cal_client_error_to_string (
1577 E_CAL_CLIENT_ERROR_INVALID_OBJECT));
1578 return;
1579 }
1580
1581 g_return_if_fail (uid != NULL);
1582 g_return_if_fail (priv->comp_uid_hash != NULL);
1583
1584 g_rec_mutex_lock (&priv->idle_save_rmutex);
1585
1586 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1587 if (!obj_data) {
1588 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1589 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1590 return;
1591 }
1592
1593 if (rid && *rid) {
1594 ECalComponent *comp;
1595
1596 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1597 if (comp) {
1598 *object = e_cal_component_get_as_string (comp);
1599 } else {
1600 ICalComponent *icomp;
1601 ICalTime *itt;
1602
1603 if (!obj_data->full_object) {
1604 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1605 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1606 return;
1607 }
1608
1609 itt = i_cal_time_new_from_string (rid);
1610 icomp = e_cal_util_construct_instance (
1611 e_cal_component_get_icalcomponent (obj_data->full_object),
1612 itt);
1613 g_object_unref (itt);
1614
1615 if (!icomp) {
1616 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1617 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1618 return;
1619 }
1620
1621 *object = i_cal_component_as_ical_string (icomp);
1622
1623 g_object_unref (icomp);
1624 }
1625 } else {
1626 if (g_hash_table_size (obj_data->recurrences) > 0) {
1627 ICalComponent *icomp;
1628
1629 /* if we have detached recurrences, return a VCALENDAR */
1630 icomp = e_cal_util_new_top_level ();
1631
1632 /* detached recurrences don't have full_object */
1633 if (obj_data->full_object)
1634 i_cal_component_take_component (
1635 icomp,
1636 i_cal_component_clone (e_cal_component_get_icalcomponent (obj_data->full_object)));
1637
1638 /* add all detached recurrences */
1639 g_hash_table_foreach (obj_data->recurrences, (GHFunc) add_detached_recur_to_vcalendar, icomp);
1640
1641 *object = i_cal_component_as_ical_string (icomp);
1642
1643 g_object_unref (icomp);
1644 } else if (obj_data->full_object)
1645 *object = e_cal_component_get_as_string (obj_data->full_object);
1646 }
1647
1648 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1649 }
1650
1651 /* Add_timezone handler for the file backend */
1652 static void
e_cal_backend_file_add_timezone(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * tzobj,GError ** error)1653 e_cal_backend_file_add_timezone (ECalBackendSync *backend,
1654 EDataCal *cal,
1655 GCancellable *cancellable,
1656 const gchar *tzobj,
1657 GError **error)
1658 {
1659 ETimezoneCache *timezone_cache;
1660 ICalComponent *tz_comp;
1661
1662 timezone_cache = E_TIMEZONE_CACHE (backend);
1663
1664 tz_comp = i_cal_parser_parse_string (tzobj);
1665 if (!tz_comp) {
1666 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
1667 return;
1668 }
1669
1670 if (i_cal_component_isa (tz_comp) == I_CAL_VTIMEZONE_COMPONENT) {
1671 ICalTimezone *zone;
1672
1673 zone = i_cal_timezone_new ();
1674 if (i_cal_timezone_set_component (zone, tz_comp))
1675 e_timezone_cache_add_timezone (timezone_cache, zone);
1676 g_object_unref (zone);
1677 }
1678
1679 g_object_unref (tz_comp);
1680 }
1681
1682 typedef struct {
1683 GSList *comps_list;
1684 gboolean search_needed;
1685 const gchar *query;
1686 ECalBackendSExp *obj_sexp;
1687 ECalBackend *backend;
1688 EDataCalView *view;
1689 gboolean as_string;
1690 } MatchObjectData;
1691
1692 static void
match_object_sexp_to_component(gpointer value,gpointer data)1693 match_object_sexp_to_component (gpointer value,
1694 gpointer data)
1695 {
1696 ECalComponent *comp = value;
1697 MatchObjectData *match_data = data;
1698 ETimezoneCache *timezone_cache;
1699
1700 g_return_if_fail (comp != NULL);
1701 g_return_if_fail (match_data->backend != NULL);
1702
1703 timezone_cache = E_TIMEZONE_CACHE (match_data->backend);
1704
1705 if ((!match_data->search_needed) ||
1706 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, timezone_cache))) {
1707 if (match_data->as_string)
1708 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (comp));
1709 else
1710 match_data->comps_list = g_slist_prepend (match_data->comps_list, comp);
1711 }
1712 }
1713
1714 static void
match_recurrence_sexp(gpointer key,gpointer value,gpointer data)1715 match_recurrence_sexp (gpointer key,
1716 gpointer value,
1717 gpointer data)
1718 {
1719 ECalComponent *comp = value;
1720 MatchObjectData *match_data = data;
1721 ETimezoneCache *timezone_cache;
1722
1723 timezone_cache = E_TIMEZONE_CACHE (match_data->backend);
1724
1725 if ((!match_data->search_needed) ||
1726 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, timezone_cache))) {
1727 if (match_data->as_string)
1728 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (comp));
1729 else
1730 match_data->comps_list = g_slist_prepend (match_data->comps_list, comp);
1731 }
1732 }
1733
1734 static void
match_object_sexp(gpointer key,gpointer value,gpointer data)1735 match_object_sexp (gpointer key,
1736 gpointer value,
1737 gpointer data)
1738 {
1739 ECalBackendFileObject *obj_data = value;
1740 MatchObjectData *match_data = data;
1741 ETimezoneCache *timezone_cache;
1742
1743 timezone_cache = E_TIMEZONE_CACHE (match_data->backend);
1744
1745 if (obj_data->full_object) {
1746 if ((!match_data->search_needed) ||
1747 (e_cal_backend_sexp_match_comp (match_data->obj_sexp,
1748 obj_data->full_object,
1749 timezone_cache))) {
1750 if (match_data->as_string)
1751 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (obj_data->full_object));
1752 else
1753 match_data->comps_list = g_slist_prepend (match_data->comps_list, obj_data->full_object);
1754 }
1755 }
1756
1757 /* match also recurrences */
1758 g_hash_table_foreach (obj_data->recurrences,
1759 (GHFunc) match_recurrence_sexp,
1760 match_data);
1761 }
1762
1763 /* Get_objects_in_range handler for the file backend */
1764 static void
e_cal_backend_file_get_object_list(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * sexp,GSList ** objects,GError ** perror)1765 e_cal_backend_file_get_object_list (ECalBackendSync *backend,
1766 EDataCal *cal,
1767 GCancellable *cancellable,
1768 const gchar *sexp,
1769 GSList **objects,
1770 GError **perror)
1771 {
1772 ECalBackendFile *cbfile;
1773 ECalBackendFilePrivate *priv;
1774 MatchObjectData match_data = { 0, };
1775 time_t occur_start = -1, occur_end = -1;
1776 gboolean prunning_by_time;
1777 GList * objs_occuring_in_tw;
1778 cbfile = E_CAL_BACKEND_FILE (backend);
1779 priv = cbfile->priv;
1780
1781 d (g_message (G_STRLOC ": Getting object list (%s)", sexp));
1782
1783 match_data.search_needed = TRUE;
1784 match_data.query = sexp;
1785 match_data.comps_list = NULL;
1786 match_data.as_string = TRUE;
1787 match_data.backend = E_CAL_BACKEND (backend);
1788
1789 if (sexp && !strcmp (sexp, "#t"))
1790 match_data.search_needed = FALSE;
1791
1792 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1793 if (!match_data.obj_sexp) {
1794 g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY));
1795 return;
1796 }
1797
1798 g_rec_mutex_lock (&priv->idle_save_rmutex);
1799
1800 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (
1801 match_data.obj_sexp,
1802 &occur_start,
1803 &occur_end);
1804
1805 objs_occuring_in_tw = NULL;
1806
1807 if (!prunning_by_time) {
1808 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
1809 &match_data);
1810 } else {
1811 objs_occuring_in_tw = e_intervaltree_search (
1812 priv->interval_tree,
1813 occur_start, occur_end);
1814
1815 g_list_foreach (objs_occuring_in_tw, (GFunc) match_object_sexp_to_component,
1816 &match_data);
1817 }
1818
1819 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1820
1821 *objects = g_slist_reverse (match_data.comps_list);
1822
1823 if (objs_occuring_in_tw) {
1824 g_list_foreach (objs_occuring_in_tw, (GFunc) g_object_unref, NULL);
1825 g_list_free (objs_occuring_in_tw);
1826 }
1827
1828 g_object_unref (match_data.obj_sexp);
1829 }
1830
1831 static void
add_attach_uris(GSList ** attachment_uris,ICalComponent * icomp)1832 add_attach_uris (GSList **attachment_uris,
1833 ICalComponent *icomp)
1834 {
1835 ICalProperty *prop;
1836
1837 g_return_if_fail (attachment_uris != NULL);
1838 g_return_if_fail (icomp != NULL);
1839
1840 for (prop = i_cal_component_get_first_property (icomp, I_CAL_ATTACH_PROPERTY);
1841 prop;
1842 g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ATTACH_PROPERTY)) {
1843 ICalAttach *attach = i_cal_property_get_attach (prop);
1844
1845 if (attach && i_cal_attach_get_is_url (attach)) {
1846 const gchar *url;
1847
1848 url = i_cal_attach_get_url (attach);
1849 if (url) {
1850 gchar *buf;
1851
1852 buf = i_cal_value_decode_ical_string (url);
1853
1854 *attachment_uris = g_slist_prepend (*attachment_uris, g_strdup (buf));
1855
1856 g_free (buf);
1857 }
1858 }
1859
1860 g_clear_object (&attach);
1861 }
1862 }
1863
1864 static void
add_detached_recur_attach_uris(gpointer key,gpointer value,gpointer user_data)1865 add_detached_recur_attach_uris (gpointer key,
1866 gpointer value,
1867 gpointer user_data)
1868 {
1869 ECalComponent *recurrence = value;
1870 GSList **attachment_uris = user_data;
1871
1872 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (recurrence));
1873 }
1874
1875 /* Gets the list of attachments */
1876 static void
e_cal_backend_file_get_attachment_uris(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * uid,const gchar * rid,GSList ** attachment_uris,GError ** error)1877 e_cal_backend_file_get_attachment_uris (ECalBackendSync *backend,
1878 EDataCal *cal,
1879 GCancellable *cancellable,
1880 const gchar *uid,
1881 const gchar *rid,
1882 GSList **attachment_uris,
1883 GError **error)
1884 {
1885 ECalBackendFile *cbfile;
1886 ECalBackendFilePrivate *priv;
1887 ECalBackendFileObject *obj_data;
1888
1889 cbfile = E_CAL_BACKEND_FILE (backend);
1890 priv = cbfile->priv;
1891
1892 g_return_if_fail (priv->comp_uid_hash != NULL);
1893
1894 g_rec_mutex_lock (&priv->idle_save_rmutex);
1895
1896 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1897 if (!obj_data) {
1898 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1899 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1900 return;
1901 }
1902
1903 if (rid && *rid) {
1904 ECalComponent *comp;
1905
1906 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1907 if (comp) {
1908 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (comp));
1909 } else {
1910 ICalComponent *icomp;
1911 ICalTime *itt;
1912
1913 if (!obj_data->full_object) {
1914 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1915 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1916 return;
1917 }
1918
1919 itt = i_cal_time_new_from_string (rid);
1920 icomp = e_cal_util_construct_instance (
1921 e_cal_component_get_icalcomponent (obj_data->full_object),
1922 itt);
1923 g_object_unref (itt);
1924 if (!icomp) {
1925 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1926 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1927 return;
1928 }
1929
1930 add_attach_uris (attachment_uris, icomp);
1931
1932 g_object_unref (icomp);
1933 }
1934 } else {
1935 if (g_hash_table_size (obj_data->recurrences) > 0) {
1936 /* detached recurrences don't have full_object */
1937 if (obj_data->full_object)
1938 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (obj_data->full_object));
1939
1940 /* add all detached recurrences */
1941 g_hash_table_foreach (obj_data->recurrences, add_detached_recur_attach_uris, attachment_uris);
1942 } else if (obj_data->full_object)
1943 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (obj_data->full_object));
1944 }
1945
1946 *attachment_uris = g_slist_reverse (*attachment_uris);
1947
1948 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1949 }
1950
1951 /* get_query handler for the file backend */
1952 static void
e_cal_backend_file_start_view(ECalBackend * backend,EDataCalView * query)1953 e_cal_backend_file_start_view (ECalBackend *backend,
1954 EDataCalView *query)
1955 {
1956 ECalBackendFile *cbfile;
1957 ECalBackendFilePrivate *priv;
1958 ECalBackendSExp *sexp;
1959 MatchObjectData match_data = { 0, };
1960 time_t occur_start = -1, occur_end = -1;
1961 gboolean prunning_by_time;
1962 GList * objs_occuring_in_tw;
1963 cbfile = E_CAL_BACKEND_FILE (backend);
1964 priv = cbfile->priv;
1965
1966 sexp = e_data_cal_view_get_sexp (query);
1967
1968 d (g_message (G_STRLOC ": Starting query (%s)", e_cal_backend_sexp_text (sexp)));
1969
1970 /* try to match all currently existing objects */
1971 match_data.search_needed = TRUE;
1972 match_data.query = e_cal_backend_sexp_text (sexp);
1973 match_data.comps_list = NULL;
1974 match_data.as_string = FALSE;
1975 match_data.backend = backend;
1976 match_data.obj_sexp = e_data_cal_view_get_sexp (query);
1977 match_data.view = query;
1978
1979 if (match_data.query && !strcmp (match_data.query, "#t"))
1980 match_data.search_needed = FALSE;
1981
1982 if (!match_data.obj_sexp) {
1983 GError *error = EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY);
1984 e_data_cal_view_notify_complete (query, error);
1985 g_error_free (error);
1986 return;
1987 }
1988 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (
1989 match_data.obj_sexp,
1990 &occur_start,
1991 &occur_end);
1992
1993 objs_occuring_in_tw = NULL;
1994
1995 g_rec_mutex_lock (&priv->idle_save_rmutex);
1996
1997 if (!prunning_by_time) {
1998 /* full scan */
1999 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
2000 &match_data);
2001
2002 e_debug_log (
2003 FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES, "---;%p;QUERY-ITEMS;%s;%s;%d", query,
2004 e_cal_backend_sexp_text (sexp), G_OBJECT_TYPE_NAME (backend),
2005 g_hash_table_size (priv->comp_uid_hash));
2006 } else {
2007 /* matches objects in new "interval tree" way */
2008 /* events occuring in time window */
2009 objs_occuring_in_tw = e_intervaltree_search (priv->interval_tree, occur_start, occur_end);
2010
2011 g_list_foreach (objs_occuring_in_tw, (GFunc) match_object_sexp_to_component,
2012 &match_data);
2013
2014 e_debug_log (
2015 FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES, "---;%p;QUERY-ITEMS;%s;%s;%d", query,
2016 e_cal_backend_sexp_text (sexp), G_OBJECT_TYPE_NAME (backend),
2017 g_list_length (objs_occuring_in_tw));
2018 }
2019
2020 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2021
2022 /* notify listeners of all objects */
2023 if (match_data.comps_list) {
2024 match_data.comps_list = g_slist_reverse (match_data.comps_list);
2025
2026 e_data_cal_view_notify_components_added (query, match_data.comps_list);
2027
2028 /* free memory */
2029 g_slist_free (match_data.comps_list);
2030 }
2031
2032 if (objs_occuring_in_tw) {
2033 g_list_foreach (objs_occuring_in_tw, (GFunc) g_object_unref, NULL);
2034 g_list_free (objs_occuring_in_tw);
2035 }
2036
2037 e_data_cal_view_notify_complete (query, NULL /* Success */);
2038 }
2039
2040 static gboolean
free_busy_instance(ICalComponent * icomp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)2041 free_busy_instance (ICalComponent *icomp,
2042 ICalTime *instance_start,
2043 ICalTime *instance_end,
2044 gpointer user_data,
2045 GCancellable *cancellable,
2046 GError **error)
2047 {
2048 ICalComponent *vfb = user_data;
2049 ICalProperty *prop;
2050 ICalParameter *param;
2051 ICalPeriod *ipt;
2052 const gchar *summary, *location;
2053
2054 if (!i_cal_time_is_date (instance_start))
2055 i_cal_time_convert_to_zone_inplace (instance_start, i_cal_timezone_get_utc_timezone ());
2056
2057 if (!i_cal_time_is_date (instance_end))
2058 i_cal_time_convert_to_zone_inplace (instance_end, i_cal_timezone_get_utc_timezone ());
2059
2060 ipt = i_cal_period_new_null_period ();
2061 i_cal_period_set_start (ipt, instance_start);
2062 i_cal_period_set_end (ipt, instance_end);
2063
2064 /* add busy information to the vfb component */
2065 prop = i_cal_property_new (I_CAL_FREEBUSY_PROPERTY);
2066 i_cal_property_set_freebusy (prop, ipt);
2067 g_object_unref (ipt);
2068
2069 param = i_cal_parameter_new_fbtype (I_CAL_FBTYPE_BUSY);
2070 i_cal_property_take_parameter (prop, param);
2071
2072 summary = i_cal_component_get_summary (icomp);
2073 if (summary && *summary)
2074 i_cal_property_set_parameter_from_string (prop, "X-SUMMARY", summary);
2075 location = i_cal_component_get_location (icomp);
2076 if (location && *location)
2077 i_cal_property_set_parameter_from_string (prop, "X-LOCATION", location);
2078
2079 i_cal_component_take_property (vfb, prop);
2080
2081 return TRUE;
2082 }
2083
2084 static ICalComponent *
create_user_free_busy(ECalBackendFile * cbfile,const gchar * address,const gchar * cn,time_t start,time_t end,GCancellable * cancellable)2085 create_user_free_busy (ECalBackendFile *cbfile,
2086 const gchar *address,
2087 const gchar *cn,
2088 time_t start,
2089 time_t end,
2090 GCancellable *cancellable)
2091 {
2092 ECalBackendFilePrivate *priv;
2093 GList *l;
2094 ICalComponent *vfb;
2095 ICalTimezone *utc_zone;
2096 ICalTime *starttt, *endtt;
2097 ECalBackendSExp *obj_sexp;
2098 gchar *query, *iso_start, *iso_end;
2099
2100 priv = cbfile->priv;
2101
2102 /* create the (unique) VFREEBUSY object that we'll return */
2103 vfb = i_cal_component_new_vfreebusy ();
2104 if (address != NULL) {
2105 ICalProperty *prop;
2106 ICalParameter *param;
2107
2108 prop = i_cal_property_new_organizer (address);
2109 if (prop != NULL && cn != NULL) {
2110 param = i_cal_parameter_new_cn (cn);
2111 i_cal_property_take_parameter (prop, param);
2112 }
2113 if (prop != NULL)
2114 i_cal_component_take_property (vfb, prop);
2115 }
2116 utc_zone = i_cal_timezone_get_utc_timezone ();
2117
2118 starttt = i_cal_time_new_from_timet_with_zone (start, FALSE, utc_zone);
2119 i_cal_component_set_dtstart (vfb, starttt);
2120
2121 endtt = i_cal_time_new_from_timet_with_zone (end, FALSE, utc_zone);
2122 i_cal_component_set_dtend (vfb, endtt);
2123
2124 /* add all objects in the given interval */
2125 iso_start = isodate_from_time_t (start);
2126 iso_end = isodate_from_time_t (end);
2127 query = g_strdup_printf (
2128 "occur-in-time-range? (make-time \"%s\") (make-time \"%s\")",
2129 iso_start, iso_end);
2130 obj_sexp = e_cal_backend_sexp_new (query);
2131 g_free (query);
2132 g_free (iso_start);
2133 g_free (iso_end);
2134
2135 if (!obj_sexp) {
2136 g_clear_object (&starttt);
2137 g_clear_object (&endtt);
2138 return vfb;
2139 }
2140
2141 for (l = priv->comp; l; l = l->next) {
2142 ECalComponent *comp = l->data;
2143 ICalComponent *icomp, *vcalendar_comp;
2144 ICalProperty *prop;
2145 ResolveTzidData rtd;
2146
2147 icomp = e_cal_component_get_icalcomponent (comp);
2148 if (!icomp)
2149 continue;
2150
2151 /* If the event is TRANSPARENT, skip it. */
2152 prop = i_cal_component_get_first_property (icomp, I_CAL_TRANSP_PROPERTY);
2153 if (prop) {
2154 ICalPropertyTransp transp_val = i_cal_property_get_transp (prop);
2155
2156 g_object_unref (prop);
2157
2158 if (transp_val == I_CAL_TRANSP_TRANSPARENT ||
2159 transp_val == I_CAL_TRANSP_TRANSPARENTNOCONFLICT)
2160 continue;
2161 }
2162
2163 if (!e_cal_backend_sexp_match_comp (obj_sexp, comp, E_TIMEZONE_CACHE (cbfile)))
2164 continue;
2165
2166 vcalendar_comp = i_cal_component_get_parent (icomp);
2167
2168 resolve_tzid_data_init (&rtd, vcalendar_comp);
2169
2170 e_cal_recur_generate_instances_sync (
2171 e_cal_component_get_icalcomponent (comp), starttt, endtt,
2172 free_busy_instance,
2173 vfb,
2174 resolve_tzid_cb,
2175 &rtd,
2176 i_cal_timezone_get_utc_timezone (),
2177 cancellable, NULL);
2178
2179 resolve_tzid_data_clear (&rtd);
2180 g_clear_object (&vcalendar_comp);
2181 }
2182
2183 g_clear_object (&starttt);
2184 g_clear_object (&endtt);
2185 g_object_unref (obj_sexp);
2186
2187 return vfb;
2188 }
2189
2190 /* Get_free_busy handler for the file backend */
2191 static void
e_cal_backend_file_get_free_busy(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const GSList * users,time_t start,time_t end,GSList ** freebusy,GError ** error)2192 e_cal_backend_file_get_free_busy (ECalBackendSync *backend,
2193 EDataCal *cal,
2194 GCancellable *cancellable,
2195 const GSList *users,
2196 time_t start,
2197 time_t end,
2198 GSList **freebusy,
2199 GError **error)
2200 {
2201 ESourceRegistry *registry;
2202 ECalBackendFile *cbfile;
2203 ECalBackendFilePrivate *priv;
2204 gchar *address, *name;
2205 ICalComponent *vfb;
2206 gchar *calobj;
2207 const GSList *l;
2208
2209 cbfile = E_CAL_BACKEND_FILE (backend);
2210 priv = cbfile->priv;
2211
2212 if (priv->vcalendar == NULL) {
2213 g_set_error_literal (
2214 error, E_CAL_CLIENT_ERROR,
2215 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
2216 e_cal_client_error_to_string (
2217 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
2218 return;
2219 }
2220
2221 g_rec_mutex_lock (&priv->idle_save_rmutex);
2222
2223 *freebusy = NULL;
2224
2225 registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
2226
2227 if (users == NULL) {
2228 if (e_cal_backend_mail_account_get_default (registry, &address, &name)) {
2229 vfb = create_user_free_busy (cbfile, address, name, start, end, cancellable);
2230 calobj = i_cal_component_as_ical_string (vfb);
2231 *freebusy = g_slist_append (*freebusy, calobj);
2232 g_object_unref (vfb);
2233 g_free (address);
2234 g_free (name);
2235 }
2236 } else {
2237 for (l = users; l != NULL; l = l->next ) {
2238 address = l->data;
2239 if (e_cal_backend_mail_account_is_valid (registry, address, &name)) {
2240 vfb = create_user_free_busy (cbfile, address, name, start, end, cancellable);
2241 calobj = i_cal_component_as_ical_string (vfb);
2242 *freebusy = g_slist_append (*freebusy, calobj);
2243 g_object_unref (vfb);
2244 g_free (name);
2245 }
2246 }
2247 }
2248
2249 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2250 }
2251
2252 static void
sanitize_component(ECalBackendFile * cbfile,ECalComponent * comp)2253 sanitize_component (ECalBackendFile *cbfile,
2254 ECalComponent *comp)
2255 {
2256 ECalComponentDateTime *dt;
2257 ICalTimezone *zone;
2258
2259 /* Check dtstart, dtend and due's timezone, and convert it to local
2260 * default timezone if the timezone is not in our builtin timezone
2261 * list */
2262 dt = e_cal_component_get_dtstart (comp);
2263 if (dt && e_cal_component_datetime_get_value (dt) && e_cal_component_datetime_get_tzid (dt)) {
2264 zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cbfile), e_cal_component_datetime_get_tzid (dt));
2265 if (!zone) {
2266 e_cal_component_datetime_set_tzid (dt, "UTC");
2267 e_cal_component_set_dtstart (comp, dt);
2268 }
2269 }
2270 e_cal_component_datetime_free (dt);
2271
2272 dt = e_cal_component_get_dtend (comp);
2273 if (dt && e_cal_component_datetime_get_value (dt) && e_cal_component_datetime_get_tzid (dt)) {
2274 zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cbfile), e_cal_component_datetime_get_tzid (dt));
2275 if (!zone) {
2276 e_cal_component_datetime_set_tzid (dt, "UTC");
2277 e_cal_component_set_dtend (comp, dt);
2278 }
2279 }
2280 e_cal_component_datetime_free (dt);
2281
2282 dt = e_cal_component_get_due (comp);
2283 if (dt && e_cal_component_datetime_get_value (dt) && e_cal_component_datetime_get_tzid (dt)) {
2284 zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cbfile), e_cal_component_datetime_get_tzid (dt));
2285 if (!zone) {
2286 e_cal_component_datetime_set_tzid (dt, "UTC");
2287 e_cal_component_set_due (comp, dt);
2288 }
2289 }
2290 e_cal_component_datetime_free (dt);
2291
2292 e_cal_component_abort_sequence (comp);
2293 }
2294
2295 static void
e_cal_backend_file_create_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const GSList * in_calobjs,ECalOperationFlags opflags,GSList ** uids,GSList ** new_components,GError ** error)2296 e_cal_backend_file_create_objects (ECalBackendSync *backend,
2297 EDataCal *cal,
2298 GCancellable *cancellable,
2299 const GSList *in_calobjs,
2300 ECalOperationFlags opflags,
2301 GSList **uids,
2302 GSList **new_components,
2303 GError **error)
2304 {
2305 ECalBackendFile *cbfile;
2306 ECalBackendFilePrivate *priv;
2307 GSList *icomps = NULL;
2308 const GSList *l;
2309
2310 cbfile = E_CAL_BACKEND_FILE (backend);
2311 priv = cbfile->priv;
2312
2313 if (priv->vcalendar == NULL) {
2314 g_set_error_literal (
2315 error, E_CAL_CLIENT_ERROR,
2316 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
2317 e_cal_client_error_to_string (
2318 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
2319 return;
2320 }
2321
2322 if (uids)
2323 *uids = NULL;
2324
2325 g_rec_mutex_lock (&priv->idle_save_rmutex);
2326
2327 /* First step, parse input strings and do uid verification: may fail */
2328 for (l = in_calobjs; l; l = l->next) {
2329 ICalComponent *icomp;
2330 const gchar *comp_uid;
2331
2332 /* Parse the icalendar text */
2333 icomp = i_cal_parser_parse_string ((gchar *) l->data);
2334 if (!icomp) {
2335 g_slist_free_full (icomps, g_object_unref);
2336 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2337 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2338 return;
2339 }
2340
2341 /* Append icalcomponent to icalcomps */
2342 icomps = g_slist_prepend (icomps, icomp);
2343
2344 /* Check kind with the parent */
2345 if (i_cal_component_isa (icomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2346 g_slist_free_full (icomps, g_object_unref);
2347 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2348 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2349 return;
2350 }
2351
2352 /* Get the UID */
2353 comp_uid = i_cal_component_get_uid (icomp);
2354 if (!comp_uid) {
2355 gchar *new_uid;
2356
2357 new_uid = e_util_generate_uid ();
2358 if (!new_uid) {
2359 g_slist_free_full (icomps, g_object_unref);
2360 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2361 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2362 return;
2363 }
2364
2365 i_cal_component_set_uid (icomp, new_uid);
2366 comp_uid = i_cal_component_get_uid (icomp);
2367
2368 g_free (new_uid);
2369 }
2370
2371 /* check that the object is not in our cache */
2372 if (uid_in_use (cbfile, comp_uid)) {
2373 g_slist_free_full (icomps, g_object_unref);
2374 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2375 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_ID_ALREADY_EXISTS));
2376 return;
2377 }
2378 }
2379
2380 icomps = g_slist_reverse (icomps);
2381
2382 /* Second step, add the objects */
2383 for (l = icomps; l; l = l->next) {
2384 ECalComponent *comp;
2385 ICalTime *current;
2386 ICalComponent *icomp = l->data;
2387
2388 /* Create the cal component */
2389 comp = e_cal_component_new_from_icalcomponent (icomp);
2390 if (!comp)
2391 continue;
2392
2393 /* Set the created and last modified times on the component, if not there already */
2394 current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
2395
2396 if (!e_cal_util_component_has_property (icomp, I_CAL_CREATED_PROPERTY)) {
2397 /* Update both when CREATED is missing, to make sure the LAST-MODIFIED
2398 is not before CREATED */
2399 e_cal_component_set_created (comp, current);
2400 e_cal_component_set_last_modified (comp, current);
2401 } else if (!e_cal_util_component_has_property (icomp, I_CAL_LASTMODIFIED_PROPERTY)) {
2402 e_cal_component_set_last_modified (comp, current);
2403 }
2404
2405 g_object_unref (current);
2406
2407 /* sanitize the component*/
2408 sanitize_component (cbfile, comp);
2409
2410 /* Add the object */
2411 add_component (cbfile, comp, TRUE);
2412
2413 /* Keep the UID and the modified component to return them later */
2414 if (uids)
2415 *uids = g_slist_prepend (*uids, g_strdup (i_cal_component_get_uid (icomp)));
2416
2417 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
2418 }
2419
2420 g_slist_free (icomps);
2421
2422 /* Save the file */
2423 save (cbfile, TRUE);
2424
2425 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2426
2427 if (uids)
2428 *uids = g_slist_reverse (*uids);
2429
2430 *new_components = g_slist_reverse (*new_components);
2431 }
2432
2433 typedef struct {
2434 ECalBackendFile *cbfile;
2435 ECalBackendFileObject *obj_data;
2436 const gchar *rid;
2437 ECalObjModType mod;
2438 } RemoveRecurrenceData;
2439
2440 static gboolean
remove_object_instance_cb(gpointer key,gpointer value,gpointer user_data)2441 remove_object_instance_cb (gpointer key,
2442 gpointer value,
2443 gpointer user_data)
2444 {
2445 time_t fromtt, instancett;
2446 ICalTime *itt;
2447 ECalComponent *instance = value;
2448 RemoveRecurrenceData *rrdata = user_data;
2449
2450 itt = i_cal_time_new_from_string (rrdata->rid);
2451 fromtt = i_cal_time_as_timet (itt);
2452 g_object_unref (itt);
2453
2454 instancett = get_rid_as_time_t (instance);
2455
2456 if (fromtt > 0 && instancett > 0) {
2457 if ((rrdata->mod == E_CAL_OBJ_MOD_THIS_AND_PRIOR && instancett <= fromtt) ||
2458 (rrdata->mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE && instancett >= fromtt)) {
2459 /* remove the component from our data */
2460 i_cal_component_remove_component (
2461 rrdata->cbfile->priv->vcalendar,
2462 e_cal_component_get_icalcomponent (instance));
2463 rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
2464
2465 rrdata->obj_data->recurrences_list = g_list_remove (rrdata->obj_data->recurrences_list, instance);
2466
2467 return TRUE;
2468 }
2469 }
2470
2471 return FALSE;
2472 }
2473
2474 static void
e_cal_backend_file_modify_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const GSList * calobjs,ECalObjModType mod,ECalOperationFlags opflags,GSList ** old_components,GSList ** new_components,GError ** error)2475 e_cal_backend_file_modify_objects (ECalBackendSync *backend,
2476 EDataCal *cal,
2477 GCancellable *cancellable,
2478 const GSList *calobjs,
2479 ECalObjModType mod,
2480 ECalOperationFlags opflags,
2481 GSList **old_components,
2482 GSList **new_components,
2483 GError **error)
2484 {
2485 ECalBackendFile *cbfile;
2486 ECalBackendFilePrivate *priv;
2487 GSList *icomps = NULL;
2488 const GSList *l;
2489 ResolveTzidData rtd;
2490
2491 cbfile = E_CAL_BACKEND_FILE (backend);
2492 priv = cbfile->priv;
2493
2494 if (priv->vcalendar == NULL) {
2495 g_set_error_literal (
2496 error, E_CAL_CLIENT_ERROR,
2497 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
2498 e_cal_client_error_to_string (
2499 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
2500 return;
2501 }
2502
2503 resolve_tzid_data_init (&rtd, priv->vcalendar);
2504
2505 switch (mod) {
2506 case E_CAL_OBJ_MOD_THIS:
2507 case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
2508 case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
2509 case E_CAL_OBJ_MOD_ALL:
2510 break;
2511 default:
2512 g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_NOT_SUPPORTED));
2513 return;
2514 }
2515
2516 if (old_components)
2517 *old_components = NULL;
2518 if (new_components)
2519 *new_components = NULL;
2520
2521 g_rec_mutex_lock (&priv->idle_save_rmutex);
2522
2523 /* First step, parse input strings and do uid verification: may fail */
2524 for (l = calobjs; l; l = l->next) {
2525 const gchar *comp_uid;
2526 ICalComponent *icomp;
2527
2528 /* Parse the iCalendar text */
2529 icomp = i_cal_parser_parse_string (l->data);
2530 if (!icomp) {
2531 g_slist_free_full (icomps, g_object_unref);
2532 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2533 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2534 return;
2535 }
2536
2537 icomps = g_slist_prepend (icomps, icomp);
2538
2539 /* Check kind with the parent */
2540 if (i_cal_component_isa (icomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2541 g_slist_free_full (icomps, g_object_unref);
2542 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2543 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2544 return;
2545 }
2546
2547 /* Get the uid */
2548 comp_uid = i_cal_component_get_uid (icomp);
2549
2550 /* Get the object from our cache */
2551 if (!g_hash_table_lookup (priv->comp_uid_hash, comp_uid)) {
2552 g_slist_free_full (icomps, g_object_unref);
2553 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2554 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2555 return;
2556 }
2557 }
2558
2559 icomps = g_slist_reverse (icomps);
2560
2561 /* Second step, update the objects */
2562 for (l = icomps; l; l = l->next) {
2563 ICalTime *current;
2564 RemoveRecurrenceData rrdata;
2565 GList *detached = NULL;
2566 gchar *rid = NULL;
2567 const gchar *comp_uid;
2568 ICalComponent * icomp = l->data, *split_icomp = NULL;
2569 ECalComponent *comp, *recurrence;
2570 ECalBackendFileObject *obj_data;
2571 gpointer value;
2572
2573 /* Create the cal component */
2574 comp = e_cal_component_new_from_icalcomponent (icomp);
2575 if (!comp)
2576 continue;
2577
2578 comp_uid = i_cal_component_get_uid (icomp);
2579 obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid);
2580
2581 /* Set the last modified time on the component */
2582 current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
2583 e_cal_component_set_last_modified (comp, current);
2584 g_object_unref (current);
2585
2586 /* sanitize the component*/
2587 sanitize_component (cbfile, comp);
2588 rid = e_cal_component_get_recurid_as_string (comp);
2589
2590 /* handle mod_type */
2591 switch (mod) {
2592 case E_CAL_OBJ_MOD_THIS:
2593 if (!rid || !*rid) {
2594 if (old_components)
2595 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2596
2597 /* replace only the full object */
2598 if (obj_data->full_object) {
2599 i_cal_component_remove_component (
2600 priv->vcalendar,
2601 e_cal_component_get_icalcomponent (obj_data->full_object));
2602 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2603
2604 g_object_unref (obj_data->full_object);
2605 }
2606
2607 /* add the new object */
2608 obj_data->full_object = comp;
2609
2610 e_cal_recur_ensure_end_dates (comp, TRUE, resolve_tzid_cb, &rtd, cancellable, NULL);
2611
2612 if (!remove_component_from_intervaltree (cbfile, comp)) {
2613 g_message (G_STRLOC " Could not remove component from interval tree!");
2614 }
2615
2616 add_component_to_intervaltree (cbfile, comp);
2617
2618 i_cal_component_add_component (
2619 priv->vcalendar,
2620 e_cal_component_get_icalcomponent (obj_data->full_object));
2621 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2622 break;
2623 }
2624
2625 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, NULL, &value)) {
2626 recurrence = value;
2627
2628 if (old_components)
2629 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (recurrence));
2630
2631 /* remove the component from our data */
2632 i_cal_component_remove_component (
2633 priv->vcalendar,
2634 e_cal_component_get_icalcomponent (recurrence));
2635 priv->comp = g_list_remove (priv->comp, recurrence);
2636 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2637 g_hash_table_remove (obj_data->recurrences, rid);
2638 } else {
2639 if (old_components)
2640 *old_components = g_slist_prepend (*old_components, NULL);
2641 }
2642
2643 /* add the detached instance */
2644 g_hash_table_insert (
2645 obj_data->recurrences,
2646 g_strdup (rid),
2647 comp);
2648 i_cal_component_add_component (
2649 priv->vcalendar,
2650 e_cal_component_get_icalcomponent (comp));
2651 priv->comp = g_list_append (priv->comp, comp);
2652 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
2653 break;
2654 case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
2655 case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
2656 if (!rid || !*rid)
2657 goto like_mod_all;
2658
2659 /* remove the component from our data, temporarily */
2660 if (obj_data->full_object) {
2661 if (mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE) {
2662 ICalTime *itt = i_cal_component_get_recurrenceid (icomp);
2663
2664 if (e_cal_util_is_first_instance (obj_data->full_object, itt, resolve_tzid_cb, &rtd)) {
2665 ICalProperty *prop = i_cal_component_get_first_property (icomp, I_CAL_RECURRENCEID_PROPERTY);
2666
2667 g_clear_object (&itt);
2668
2669 if (prop) {
2670 i_cal_component_remove_property (icomp, prop);
2671 g_object_unref (prop);
2672 }
2673
2674 goto like_mod_all;
2675 }
2676
2677 g_clear_object (&itt);
2678 }
2679
2680 i_cal_component_remove_component (
2681 priv->vcalendar,
2682 e_cal_component_get_icalcomponent (obj_data->full_object));
2683 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2684 }
2685
2686 /* now deal with the detached recurrence */
2687 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, NULL, &value)) {
2688 recurrence = value;
2689
2690 if (old_components)
2691 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (recurrence));
2692
2693 /* remove the component from our data */
2694 i_cal_component_remove_component (
2695 priv->vcalendar,
2696 e_cal_component_get_icalcomponent (recurrence));
2697 priv->comp = g_list_remove (priv->comp, recurrence);
2698 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2699 g_hash_table_remove (obj_data->recurrences, rid);
2700 } else {
2701 if (*old_components)
2702 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2703 }
2704
2705 rrdata.cbfile = cbfile;
2706 rrdata.obj_data = obj_data;
2707 rrdata.rid = rid;
2708 rrdata.mod = mod;
2709 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2710
2711 /* add the modified object to the beginning of the list,
2712 * so that it's always before any detached instance we
2713 * might have */
2714 if (obj_data->full_object) {
2715 ICalTime *rid_struct = i_cal_component_get_recurrenceid (icomp), *master_dtstart;
2716 ICalComponent *master_icomp = e_cal_component_get_icalcomponent (obj_data->full_object);
2717 ICalProperty *prop = i_cal_component_get_first_property (icomp, I_CAL_RECURRENCEID_PROPERTY);
2718
2719 if (prop) {
2720 i_cal_component_remove_property (icomp, prop);
2721 g_object_unref (prop);
2722 }
2723
2724 master_dtstart = i_cal_component_get_dtstart (master_icomp);
2725 if (master_dtstart && i_cal_time_get_timezone (master_dtstart) &&
2726 i_cal_time_get_timezone (master_dtstart) != i_cal_time_get_timezone (rid_struct)) {
2727 i_cal_time_convert_to_zone_inplace (rid_struct, i_cal_time_get_timezone (master_dtstart));
2728 }
2729
2730 split_icomp = e_cal_util_split_at_instance_ex (icomp, rid_struct, master_dtstart, resolve_tzid_cb, &rtd);
2731 if (split_icomp) {
2732 ECalComponent *prev_comp;
2733
2734 prev_comp = e_cal_component_clone (obj_data->full_object);
2735
2736 e_cal_util_remove_instances_ex (e_cal_component_get_icalcomponent (obj_data->full_object), rid_struct, mod, resolve_tzid_cb, &rtd);
2737 e_cal_recur_ensure_end_dates (obj_data->full_object, TRUE, resolve_tzid_cb, &rtd, cancellable, NULL);
2738
2739 e_cal_backend_notify_component_modified (E_CAL_BACKEND (backend), prev_comp, obj_data->full_object);
2740
2741 g_clear_object (&prev_comp);
2742 }
2743
2744 i_cal_component_add_component (
2745 priv->vcalendar,
2746 e_cal_component_get_icalcomponent (obj_data->full_object));
2747 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2748
2749 g_clear_object (&rid_struct);
2750 g_clear_object (&master_dtstart);
2751 } else {
2752 ICalTime *rid_struct = i_cal_component_get_recurrenceid (icomp);
2753
2754 split_icomp = e_cal_util_split_at_instance_ex (icomp, rid_struct, NULL, resolve_tzid_cb, &rtd);
2755
2756 g_object_unref (rid_struct);
2757 }
2758
2759 if (split_icomp) {
2760 gchar *new_uid;
2761
2762 new_uid = e_util_generate_uid ();
2763 i_cal_component_set_uid (split_icomp, new_uid);
2764 g_free (new_uid);
2765
2766 g_warn_if_fail (e_cal_component_set_icalcomponent (comp, split_icomp));
2767 e_cal_recur_ensure_end_dates (comp, TRUE, resolve_tzid_cb, &rtd, cancellable, NULL);
2768
2769 /* sanitize the component */
2770 sanitize_component (cbfile, comp);
2771
2772 /* Add the object */
2773 add_component (cbfile, comp, TRUE);
2774 }
2775 break;
2776 case E_CAL_OBJ_MOD_ALL :
2777 like_mod_all:
2778 /* Remove the old version */
2779 if (old_components)
2780 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2781
2782 if (obj_data->recurrences_list) {
2783 /* has detached components, preserve them */
2784 GList *ll;
2785
2786 for (ll = obj_data->recurrences_list; ll; ll = ll->next) {
2787 detached = g_list_prepend (detached, g_object_ref (ll->data));
2788 }
2789 }
2790
2791 remove_component (cbfile, comp_uid, obj_data);
2792
2793 e_cal_recur_ensure_end_dates (comp, TRUE, resolve_tzid_cb, &rtd, cancellable, NULL);
2794
2795 /* Add the new object */
2796 add_component (cbfile, comp, TRUE);
2797
2798 if (detached) {
2799 /* it had some detached components, place them back */
2800 comp_uid = i_cal_component_get_uid (e_cal_component_get_icalcomponent (comp));
2801
2802 if ((obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid)) != NULL) {
2803 GList *ll;
2804
2805 for (ll = detached; ll; ll = ll->next) {
2806 ECalComponent *c = ll->data;
2807
2808 g_hash_table_insert (obj_data->recurrences, e_cal_component_get_recurid_as_string (c), c);
2809 i_cal_component_add_component (priv->vcalendar, e_cal_component_get_icalcomponent (c));
2810 priv->comp = g_list_append (priv->comp, c);
2811 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, c);
2812 }
2813 }
2814
2815 g_list_free (detached);
2816 }
2817 break;
2818 /* coverity[dead_error_begin] */
2819 case E_CAL_OBJ_MOD_ONLY_THIS:
2820 /* not reached, keep compiler happy */
2821 g_warn_if_reached ();
2822 break;
2823 }
2824
2825 g_free (rid);
2826
2827 if (new_components) {
2828 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
2829 }
2830 }
2831
2832 resolve_tzid_data_clear (&rtd);
2833
2834 g_slist_free (icomps);
2835
2836 /* All the components were updated, now we save the file */
2837 save (cbfile, TRUE);
2838
2839 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2840
2841 if (old_components)
2842 *old_components = g_slist_reverse (*old_components);
2843
2844 if (new_components)
2845 *new_components = g_slist_reverse (*new_components);
2846 }
2847
2848 static void
e_cal_backend_file_discard_alarm_sync(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * uid,const gchar * rid,const gchar * auid,ECalOperationFlags opflags,GError ** error)2849 e_cal_backend_file_discard_alarm_sync (ECalBackendSync *backend,
2850 EDataCal *cal,
2851 GCancellable *cancellable,
2852 const gchar *uid,
2853 const gchar *rid,
2854 const gchar *auid,
2855 ECalOperationFlags opflags,
2856 GError **error)
2857 {
2858 ECalBackendFile *cbfile;
2859 ECalBackendFilePrivate *priv;
2860 ECalBackendFileObject *obj_data;
2861 ECalComponent *comp = NULL;
2862
2863 cbfile = E_CAL_BACKEND_FILE (backend);
2864 priv = cbfile->priv;
2865
2866 if (priv->vcalendar == NULL) {
2867 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2868 return;
2869 }
2870
2871 g_return_if_fail (uid != NULL);
2872 g_return_if_fail (priv->comp_uid_hash != NULL);
2873
2874 g_rec_mutex_lock (&priv->idle_save_rmutex);
2875
2876 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2877 if (!obj_data) {
2878 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2879 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2880 return;
2881 }
2882
2883 if (rid && *rid) {
2884 comp = g_hash_table_lookup (obj_data->recurrences, rid);
2885
2886 if (comp) {
2887 g_object_ref (comp);
2888 } else if (obj_data->full_object) {
2889 ICalComponent *icomp;
2890 ICalTime *itt;
2891
2892 itt = i_cal_time_new_from_string (rid);
2893 icomp = e_cal_util_construct_instance (
2894 e_cal_component_get_icalcomponent (obj_data->full_object),
2895 itt);
2896 g_object_unref (itt);
2897
2898 if (icomp)
2899 comp = e_cal_component_new_from_icalcomponent (icomp);
2900 }
2901 } else if (obj_data->full_object) {
2902 comp = g_object_ref (obj_data->full_object);
2903 }
2904
2905 if (comp) {
2906 if (e_cal_util_set_alarm_acknowledged (comp, auid, 0)) {
2907 GSList *calobjs;
2908
2909 calobjs = g_slist_prepend (NULL, e_cal_component_get_as_string (comp));
2910
2911 e_cal_backend_file_modify_objects (backend, cal, cancellable, calobjs,
2912 (rid && *rid) ? E_CAL_OBJ_MOD_THIS : E_CAL_OBJ_MOD_ALL,
2913 opflags, NULL, NULL, error);
2914
2915 g_slist_free_full (calobjs, g_free);
2916 } else {
2917 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2918 }
2919
2920 g_object_unref (comp);
2921 } else {
2922 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2923 }
2924
2925 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2926 }
2927
2928 /**
2929 * Remove one and only one instance. The object may be empty
2930 * afterwards, in which case it will be removed completely.
2931 *
2932 * @mod E_CAL_OBJ_MOD_THIS or E_CAL_OBJ_MOD_ONLY_THIS: the later only
2933 * removes the instance, the former also adds an EXDATE if rid is set
2934 * TODO: E_CAL_OBJ_MOD_ONLY_THIS
2935 * @uid pointer to UID which must remain valid even if the object gets
2936 * removed
2937 * @rid NULL, "", or non-empty string when manipulating a specific recurrence;
2938 * also must remain valid
2939 * @error may be NULL if caller is not interested in errors
2940 * @return modified object or NULL if it got removed
2941 */
2942 static ECalBackendFileObject *
remove_instance(ECalBackendFile * cbfile,ECalBackendFileObject * obj_data,const gchar * uid,const gchar * rid,ECalObjModType mod,ECalComponent ** old_comp,ECalComponent ** new_comp,GError ** error)2943 remove_instance (ECalBackendFile *cbfile,
2944 ECalBackendFileObject *obj_data,
2945 const gchar *uid,
2946 const gchar *rid,
2947 ECalObjModType mod,
2948 ECalComponent **old_comp,
2949 ECalComponent **new_comp,
2950 GError **error)
2951 {
2952 ECalComponent *comp;
2953 ICalTime *current;
2954
2955 /* only check for non-NULL below, empty string is detected here */
2956 if (rid && !*rid)
2957 rid = NULL;
2958
2959 if (rid) {
2960 ICalTime *rid_struct;
2961 ResolveTzidData rtd;
2962 gpointer value;
2963
2964 /* remove recurrence */
2965 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, NULL, &value)) {
2966 comp = value;
2967
2968 /* Removing without parent or not modifying parent?
2969 * Report removal to caller. */
2970 if (old_comp &&
2971 (!obj_data->full_object || mod == E_CAL_OBJ_MOD_ONLY_THIS)) {
2972 *old_comp = e_cal_component_clone (comp);
2973 }
2974
2975 /* Reporting parent modification to caller?
2976 * Report directly instead of going via caller. */
2977 if (obj_data->full_object &&
2978 mod != E_CAL_OBJ_MOD_ONLY_THIS) {
2979 /* old object string not provided,
2980 * instead rely on the view detecting
2981 * whether it contains the id */
2982 ECalComponentId *id;
2983
2984 id = e_cal_component_id_new (uid, rid);
2985 e_cal_backend_notify_component_removed (E_CAL_BACKEND (cbfile), id, NULL, NULL);
2986 e_cal_component_id_free (id);
2987 }
2988
2989 /* remove the component from our data */
2990 i_cal_component_remove_component (
2991 cbfile->priv->vcalendar,
2992 e_cal_component_get_icalcomponent (comp));
2993 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
2994 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp);
2995 g_hash_table_remove (obj_data->recurrences, rid);
2996 } else if (mod == E_CAL_OBJ_MOD_ONLY_THIS) {
2997 if (error)
2998 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2999 return obj_data;
3000 } else {
3001 /* not an error, only add EXDATE */
3002 }
3003 /* component empty? */
3004 if (!obj_data->full_object) {
3005 if (!obj_data->recurrences_list) {
3006 /* empty now, remove it */
3007 remove_component (cbfile, uid, obj_data);
3008 return NULL;
3009 } else {
3010 return obj_data;
3011 }
3012 }
3013
3014 /* avoid modifying parent? */
3015 if (mod == E_CAL_OBJ_MOD_ONLY_THIS)
3016 return obj_data;
3017
3018 /* remove the main component from our data before modifying it */
3019 i_cal_component_remove_component (
3020 cbfile->priv->vcalendar,
3021 e_cal_component_get_icalcomponent (obj_data->full_object));
3022 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
3023
3024 /* add EXDATE or EXRULE to parent, report as update */
3025 if (old_comp) {
3026 *old_comp = e_cal_component_clone (obj_data->full_object);
3027 }
3028
3029 rid_struct = i_cal_time_new_from_string (rid);
3030 if (!i_cal_time_get_timezone (rid_struct)) {
3031 ICalTime *master_dtstart = i_cal_component_get_dtstart (e_cal_component_get_icalcomponent (obj_data->full_object));
3032
3033 if (master_dtstart && i_cal_time_get_timezone (master_dtstart)) {
3034 i_cal_time_convert_to_zone_inplace (rid_struct, i_cal_time_get_timezone (master_dtstart));
3035 }
3036 }
3037
3038 resolve_tzid_data_init (&rtd, cbfile->priv->vcalendar);
3039
3040 e_cal_util_remove_instances_ex (
3041 e_cal_component_get_icalcomponent (obj_data->full_object),
3042 rid_struct, mod, resolve_tzid_cb, &rtd);
3043
3044 resolve_tzid_data_clear (&rtd);
3045 g_clear_object (&rid_struct);
3046
3047 /* Since we are only removing one instance of recurrence
3048 * event, update the last modified time on the component */
3049 current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
3050 e_cal_component_set_last_modified (obj_data->full_object, current);
3051 g_object_unref (current);
3052
3053 /* report update */
3054 if (new_comp) {
3055 *new_comp = e_cal_component_clone (obj_data->full_object);
3056 }
3057
3058 /* add the modified object to the beginning of the list,
3059 * so that it's always before any detached instance we
3060 * might have */
3061 i_cal_component_add_component (
3062 cbfile->priv->vcalendar,
3063 e_cal_component_get_icalcomponent (obj_data->full_object));
3064 cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
3065 } else {
3066 if (!obj_data->full_object) {
3067 /* Nothing to do, parent doesn't exist. Tell
3068 * caller about this? Not an error with
3069 * E_CAL_OBJ_MOD_THIS. */
3070 if (mod == E_CAL_OBJ_MOD_ONLY_THIS && error)
3071 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
3072 return obj_data;
3073 }
3074
3075 /* remove the main component from our data before deleting it */
3076 if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) {
3077 /* return without changing anything */
3078 g_message (G_STRLOC " Could not remove component from interval tree!");
3079 return obj_data;
3080 }
3081 i_cal_component_remove_component (
3082 cbfile->priv->vcalendar,
3083 e_cal_component_get_icalcomponent (obj_data->full_object));
3084 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
3085
3086 /* remove parent, report as removal */
3087 if (old_comp) {
3088 *old_comp = g_object_ref (obj_data->full_object);
3089 }
3090 g_object_unref (obj_data->full_object);
3091 obj_data->full_object = NULL;
3092
3093 /* component may be empty now, check that */
3094 if (!obj_data->recurrences_list) {
3095 remove_component (cbfile, uid, obj_data);
3096 return NULL;
3097 }
3098 }
3099
3100 /* component still exists in a modified form */
3101 return obj_data;
3102 }
3103
3104 static ECalComponent *
clone_ecalcomp_from_fileobject(ECalBackendFileObject * obj_data,const gchar * rid)3105 clone_ecalcomp_from_fileobject (ECalBackendFileObject *obj_data,
3106 const gchar *rid)
3107 {
3108 ECalComponent *comp = obj_data->full_object;
3109
3110 if (!comp)
3111 return NULL;
3112
3113 if (rid) {
3114 gpointer value;
3115
3116 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, NULL, &value)) {
3117 comp = value;
3118 } else {
3119 /* FIXME remove this once we delete an instance from master object through
3120 * modify request by setting exception */
3121 comp = obj_data->full_object;
3122 }
3123 }
3124
3125 return comp ? e_cal_component_clone (comp) : NULL;
3126 }
3127
3128 static void
notify_comp_removed_cb(gpointer pecalcomp,gpointer pbackend)3129 notify_comp_removed_cb (gpointer pecalcomp,
3130 gpointer pbackend)
3131 {
3132 ECalComponent *comp = pecalcomp;
3133 ECalBackend *backend = pbackend;
3134 ECalComponentId *id;
3135
3136 g_return_if_fail (comp != NULL);
3137 g_return_if_fail (backend != NULL);
3138
3139 id = e_cal_component_get_id (comp);
3140 g_return_if_fail (id != NULL);
3141
3142 e_cal_backend_notify_component_removed (backend, id, comp, NULL);
3143
3144 e_cal_component_id_free (id);
3145 }
3146
3147 /* Remove_object handler for the file backend */
3148 static void
e_cal_backend_file_remove_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const GSList * ids,ECalObjModType mod,ECalOperationFlags opflags,GSList ** old_components,GSList ** new_components,GError ** error)3149 e_cal_backend_file_remove_objects (ECalBackendSync *backend,
3150 EDataCal *cal,
3151 GCancellable *cancellable,
3152 const GSList *ids,
3153 ECalObjModType mod,
3154 ECalOperationFlags opflags,
3155 GSList **old_components,
3156 GSList **new_components,
3157 GError **error)
3158 {
3159 ECalBackendFile *cbfile;
3160 ECalBackendFilePrivate *priv;
3161 const GSList *l;
3162
3163 cbfile = E_CAL_BACKEND_FILE (backend);
3164 priv = cbfile->priv;
3165
3166 if (priv->vcalendar == NULL) {
3167 g_set_error_literal (
3168 error, E_CAL_CLIENT_ERROR,
3169 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
3170 e_cal_client_error_to_string (
3171 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
3172 return;
3173 }
3174
3175 switch (mod) {
3176 case E_CAL_OBJ_MOD_THIS:
3177 case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
3178 case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
3179 case E_CAL_OBJ_MOD_ONLY_THIS:
3180 case E_CAL_OBJ_MOD_ALL:
3181 break;
3182 default:
3183 g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_NOT_SUPPORTED));
3184 return;
3185 }
3186
3187 *old_components = *new_components = NULL;
3188
3189 g_rec_mutex_lock (&priv->idle_save_rmutex);
3190
3191 /* First step, validate the input */
3192 for (l = ids; l; l = l->next) {
3193 ECalComponentId *id = l->data;
3194 /* Make the ID contains a uid */
3195 if (!id || !e_cal_component_id_get_uid (id)) {
3196 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3197 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
3198 return;
3199 }
3200 /* Check that it has a recurrence id if mod is E_CAL_OBJ_MOD_THIS_AND_PRIOR
3201 or E_CAL_OBJ_MOD_THIS_AND_FUTURE */
3202 if ((mod == E_CAL_OBJ_MOD_THIS_AND_PRIOR || mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE) &&
3203 !e_cal_component_id_get_rid (id)) {
3204 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3205 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
3206 return;
3207 }
3208 /* Make sure the uid exists in the local hash table */
3209 if (!g_hash_table_lookup (priv->comp_uid_hash, e_cal_component_id_get_uid (id))) {
3210 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3211 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
3212 return;
3213 }
3214 }
3215
3216 /* Second step, remove objects from the calendar */
3217 for (l = ids; l; l = l->next) {
3218 const gchar *recur_id = NULL;
3219 ECalComponent *comp;
3220 RemoveRecurrenceData rrdata;
3221 ECalBackendFileObject *obj_data;
3222 ECalComponentId *id = l->data;
3223
3224 obj_data = g_hash_table_lookup (priv->comp_uid_hash, e_cal_component_id_get_uid (id));
3225 recur_id = e_cal_component_id_get_rid (id);
3226
3227 switch (mod) {
3228 case E_CAL_OBJ_MOD_ALL :
3229 *old_components = g_slist_prepend (*old_components, clone_ecalcomp_from_fileobject (obj_data, recur_id));
3230 *new_components = g_slist_prepend (*new_components, NULL);
3231
3232 if (obj_data->recurrences_list)
3233 g_list_foreach (obj_data->recurrences_list, notify_comp_removed_cb, cbfile);
3234 remove_component (cbfile, e_cal_component_id_get_uid (id), obj_data);
3235 break;
3236 case E_CAL_OBJ_MOD_ONLY_THIS:
3237 case E_CAL_OBJ_MOD_THIS: {
3238 ECalComponent *old_component = NULL;
3239 ECalComponent *new_component = NULL;
3240
3241 remove_instance (
3242 cbfile, obj_data, e_cal_component_id_get_uid (id), recur_id, mod,
3243 &old_component, &new_component, error);
3244
3245 *old_components = g_slist_prepend (*old_components, old_component);
3246 *new_components = g_slist_prepend (*new_components, new_component);
3247 break;
3248 }
3249 case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
3250 case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
3251 comp = obj_data->full_object;
3252
3253 if (comp) {
3254 ICalTime *rid_struct;
3255 ResolveTzidData rtd;
3256
3257 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (comp));
3258
3259 /* remove the component from our data, temporarily */
3260 i_cal_component_remove_component (
3261 priv->vcalendar,
3262 e_cal_component_get_icalcomponent (comp));
3263 priv->comp = g_list_remove (priv->comp, comp);
3264
3265 rid_struct = i_cal_time_new_from_string (recur_id);
3266 if (!i_cal_time_get_timezone (rid_struct)) {
3267 ICalTime *master_dtstart = i_cal_component_get_dtstart (e_cal_component_get_icalcomponent (comp));
3268
3269 if (master_dtstart && i_cal_time_get_timezone (master_dtstart)) {
3270 i_cal_time_convert_to_zone_inplace (rid_struct, i_cal_time_get_timezone (master_dtstart));
3271 }
3272
3273 i_cal_time_convert_to_zone_inplace (rid_struct, i_cal_timezone_get_utc_timezone ());
3274 }
3275
3276 resolve_tzid_data_init (&rtd, priv->vcalendar);
3277
3278 e_cal_util_remove_instances_ex (
3279 e_cal_component_get_icalcomponent (comp),
3280 rid_struct, mod, resolve_tzid_cb, &rtd);
3281
3282 resolve_tzid_data_clear (&rtd);
3283 g_object_unref (rid_struct);
3284 } else {
3285 *old_components = g_slist_prepend (*old_components, NULL);
3286 }
3287
3288 /* now remove all detached instances */
3289 rrdata.cbfile = cbfile;
3290 rrdata.obj_data = obj_data;
3291 rrdata.rid = recur_id;
3292 rrdata.mod = mod;
3293 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
3294
3295 /* add the modified object to the beginning of the list,
3296 * so that it's always before any detached instance we
3297 * might have */
3298 if (comp)
3299 priv->comp = g_list_prepend (priv->comp, comp);
3300
3301 if (obj_data->full_object) {
3302 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (obj_data->full_object));
3303 } else {
3304 *new_components = g_slist_prepend (*new_components, NULL);
3305 }
3306 break;
3307 }
3308 }
3309
3310 save (cbfile, TRUE);
3311
3312 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3313
3314 *old_components = g_slist_reverse (*old_components);
3315 *new_components = g_slist_reverse (*new_components);
3316 }
3317
3318 static gboolean
cancel_received_object(ECalBackendFile * cbfile,ECalComponent * comp,ECalObjModType mod,ECalComponent ** old_comp,ECalComponent ** new_comp)3319 cancel_received_object (ECalBackendFile *cbfile,
3320 ECalComponent *comp,
3321 ECalObjModType mod,
3322 ECalComponent **old_comp,
3323 ECalComponent **new_comp)
3324 {
3325 ECalBackendFileObject *obj_data;
3326 ECalBackendFilePrivate *priv;
3327 gchar *rid;
3328 const gchar *uid;
3329
3330 priv = cbfile->priv;
3331
3332 *old_comp = NULL;
3333 *new_comp = NULL;
3334
3335 uid = e_cal_component_get_uid (comp);
3336
3337 /* Find the old version of the component. */
3338 obj_data = uid ? g_hash_table_lookup (priv->comp_uid_hash, uid) : NULL;
3339 if (!obj_data)
3340 return FALSE;
3341
3342 /* And remove it */
3343 rid = e_cal_component_get_recurid_as_string (comp);
3344 if (rid && *rid) {
3345 obj_data = remove_instance (
3346 cbfile, obj_data, uid, rid, mod,
3347 old_comp, new_comp, NULL);
3348 if (obj_data && obj_data->full_object && !*new_comp) {
3349 *new_comp = e_cal_component_clone (obj_data->full_object);
3350 }
3351 } else {
3352 /* report as removal by keeping *new_component NULL */
3353 if (obj_data->full_object) {
3354 *old_comp = e_cal_component_clone (obj_data->full_object);
3355 }
3356 remove_component (cbfile, uid, obj_data);
3357 }
3358
3359 g_free (rid);
3360
3361 return TRUE;
3362 }
3363
3364 typedef struct {
3365 GHashTable *zones;
3366
3367 gboolean found;
3368 } ECalBackendFileTzidData;
3369
3370 static void
check_tzids(ICalParameter * param,gpointer data)3371 check_tzids (ICalParameter *param,
3372 gpointer data)
3373 {
3374 ECalBackendFileTzidData *tzdata = data;
3375 const gchar *tzid;
3376
3377 tzid = i_cal_parameter_get_tzid (param);
3378 if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
3379 tzdata->found = FALSE;
3380 }
3381
3382 /* This function is largely duplicated in
3383 * ../groupwise/e-cal-backend-groupwise.c
3384 */
3385 static void
fetch_attachments(ECalBackendSync * backend,ECalComponent * comp)3386 fetch_attachments (ECalBackendSync *backend,
3387 ECalComponent *comp)
3388 {
3389 GSList *attach_list;
3390 GSList *l;
3391 gchar *dest_url, *dest_file;
3392 gint fd, fileindex;
3393 const gchar *uid;
3394
3395 attach_list = e_cal_component_get_attachments (comp);
3396 uid = e_cal_component_get_uid (comp);
3397
3398 for (l = attach_list, fileindex = 0; l; l = l->next, fileindex++) {
3399 ICalAttach *attach = l->data;
3400 gchar *sfname;
3401 gchar *filename;
3402 GMappedFile *mapped_file;
3403 GError *error = NULL;
3404
3405 if (!attach || !i_cal_attach_get_is_url (attach))
3406 continue;
3407
3408 sfname = g_filename_from_uri (i_cal_attach_get_url (attach), NULL, NULL);
3409 if (!sfname)
3410 continue;
3411
3412 mapped_file = g_mapped_file_new (sfname, FALSE, &error);
3413 if (!mapped_file) {
3414 g_message (
3415 "DEBUG: could not map %s: %s\n",
3416 sfname, error ? error->message : "???");
3417 g_error_free (error);
3418 g_free (sfname);
3419 continue;
3420 }
3421 filename = g_path_get_basename (sfname);
3422 dest_file = e_cal_backend_create_cache_filename (E_CAL_BACKEND (backend), uid, filename, fileindex);
3423 g_free (filename);
3424 fd = g_open (dest_file, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
3425 if (fd == -1) {
3426 /* TODO handle error conditions */
3427 g_message (
3428 "DEBUG: could not open %s for writing\n",
3429 dest_file);
3430 } else if (write (fd, g_mapped_file_get_contents (mapped_file),
3431 g_mapped_file_get_length (mapped_file)) == -1) {
3432 /* TODO handle error condition */
3433 g_message ("DEBUG: attachment write failed.\n");
3434 }
3435
3436 g_mapped_file_unref (mapped_file);
3437
3438 if (fd != -1)
3439 close (fd);
3440 dest_url = g_filename_to_uri (dest_file, NULL, NULL);
3441 g_free (dest_file);
3442
3443 g_object_unref (attach);
3444 l->data = i_cal_attach_new_from_url (dest_url);
3445
3446 g_free (dest_url);
3447 g_free (sfname);
3448 }
3449
3450 e_cal_component_set_attachments (comp, attach_list);
3451
3452 g_slist_free_full (attach_list, g_object_unref);
3453 }
3454
3455 static gint
masters_first_cmp(gconstpointer ptr1,gconstpointer ptr2)3456 masters_first_cmp (gconstpointer ptr1,
3457 gconstpointer ptr2)
3458 {
3459 ICalComponent *icomp1 = (ICalComponent *) ptr1;
3460 ICalComponent *icomp2 = (ICalComponent *) ptr2;
3461 gboolean has_rid1, has_rid2;
3462
3463 has_rid1 = (icomp1 && e_cal_util_component_has_property (icomp1, I_CAL_RECURRENCEID_PROPERTY)) ? 1 : 0;
3464 has_rid2 = (icomp2 && e_cal_util_component_has_property (icomp2, I_CAL_RECURRENCEID_PROPERTY)) ? 1 : 0;
3465
3466 if (has_rid1 == has_rid2)
3467 return g_strcmp0 (icomp1 ? i_cal_component_get_uid (icomp1) : NULL,
3468 icomp2 ? i_cal_component_get_uid (icomp2) : NULL);
3469
3470 if (has_rid1)
3471 return 1;
3472
3473 return -1;
3474 }
3475
3476 /* Update_objects handler for the file backend. */
3477 static void
e_cal_backend_file_receive_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * calobj,ECalOperationFlags opflags,GError ** error)3478 e_cal_backend_file_receive_objects (ECalBackendSync *backend,
3479 EDataCal *cal,
3480 GCancellable *cancellable,
3481 const gchar *calobj,
3482 ECalOperationFlags opflags,
3483 GError **error)
3484 {
3485 ESourceRegistry *registry;
3486 ECalBackendFile *cbfile;
3487 ECalBackendFilePrivate *priv;
3488 ECalClientTzlookupICalCompData *lookup_data = NULL;
3489 ICalComponent *toplevel_comp, *icomp = NULL;
3490 ICalComponentKind kind;
3491 ICalPropertyMethod toplevel_method, method;
3492 ICalComponent *subcomp;
3493 GSList *comps = NULL, *del_comps = NULL, *link;
3494 ECalComponent *comp;
3495 ECalBackendFileTzidData tzdata;
3496 GError *err = NULL;
3497
3498 cbfile = E_CAL_BACKEND_FILE (backend);
3499 priv = cbfile->priv;
3500
3501 if (priv->vcalendar == NULL) {
3502 g_set_error_literal (
3503 error, E_CAL_CLIENT_ERROR,
3504 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
3505 e_cal_client_error_to_string (
3506 E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
3507 return;
3508 }
3509
3510 /* Pull the component from the string and ensure that it is sane */
3511 toplevel_comp = i_cal_parser_parse_string (calobj);
3512 if (!toplevel_comp) {
3513 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
3514 return;
3515 }
3516
3517 g_rec_mutex_lock (&priv->idle_save_rmutex);
3518
3519 registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
3520
3521 kind = i_cal_component_isa (toplevel_comp);
3522 if (kind != I_CAL_VCALENDAR_COMPONENT) {
3523 /* If it is not a VCALENDAR, make it one to simplify below */
3524 icomp = toplevel_comp;
3525 toplevel_comp = e_cal_util_new_top_level ();
3526 if (i_cal_component_get_method (icomp) == I_CAL_METHOD_CANCEL)
3527 i_cal_component_set_method (toplevel_comp, I_CAL_METHOD_CANCEL);
3528 else
3529 i_cal_component_set_method (toplevel_comp, I_CAL_METHOD_PUBLISH);
3530 i_cal_component_take_component (toplevel_comp, icomp);
3531 } else {
3532 if (!e_cal_util_component_has_property (toplevel_comp, I_CAL_METHOD_PROPERTY))
3533 i_cal_component_set_method (toplevel_comp, I_CAL_METHOD_PUBLISH);
3534 }
3535
3536 toplevel_method = i_cal_component_get_method (toplevel_comp);
3537
3538 /* Build a list of timezones so we can make sure all the objects have valid info */
3539 tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
3540
3541 for (subcomp = i_cal_component_get_first_component (toplevel_comp, I_CAL_VTIMEZONE_COMPONENT);
3542 subcomp;
3543 g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (toplevel_comp, I_CAL_VTIMEZONE_COMPONENT)) {
3544 ICalTimezone *zone;
3545
3546 zone = i_cal_timezone_new ();
3547 if (i_cal_timezone_set_component (zone, subcomp))
3548 g_hash_table_insert (tzdata.zones, g_strdup (i_cal_timezone_get_tzid (zone)), NULL);
3549 g_object_unref (zone);
3550 }
3551
3552 /* First we make sure all the components are usuable */
3553 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
3554
3555 for (subcomp = i_cal_component_get_first_component (toplevel_comp, I_CAL_ANY_COMPONENT);
3556 subcomp;
3557 g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (toplevel_comp, I_CAL_ANY_COMPONENT)) {
3558 ICalComponentKind child_kind = i_cal_component_isa (subcomp);
3559
3560 if (child_kind != kind) {
3561 /* remove the component from the toplevel VCALENDAR */
3562 if (child_kind != I_CAL_VTIMEZONE_COMPONENT)
3563 del_comps = g_slist_prepend (del_comps, g_object_ref (subcomp));
3564 continue;
3565 }
3566
3567 tzdata.found = TRUE;
3568 i_cal_component_foreach_tzid (subcomp, check_tzids, &tzdata);
3569
3570 if (!tzdata.found) {
3571 err = ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT);
3572 g_object_unref (subcomp);
3573 goto error;
3574 }
3575
3576 if (!i_cal_component_get_uid (subcomp)) {
3577 if (toplevel_method == I_CAL_METHOD_PUBLISH) {
3578 gchar *new_uid = NULL;
3579
3580 new_uid = e_util_generate_uid ();
3581 i_cal_component_set_uid (subcomp, new_uid);
3582 g_free (new_uid);
3583 } else {
3584 err = ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT);
3585 g_object_unref (subcomp);
3586 goto error;
3587 }
3588
3589 }
3590
3591 comps = g_slist_prepend (comps, g_object_ref (subcomp));
3592 }
3593
3594 /* Now we remove the components we don't care about */
3595 for (link = del_comps; link; link = g_slist_next (link)) {
3596 subcomp = link->data;
3597
3598 i_cal_component_remove_component (toplevel_comp, subcomp);
3599 }
3600
3601 g_slist_free_full (del_comps, g_object_unref);
3602 del_comps = NULL;
3603
3604 lookup_data = e_cal_client_tzlookup_icalcomp_data_new (priv->vcalendar);
3605
3606 /* check and patch timezones */
3607 if (!e_cal_client_check_timezones_sync (toplevel_comp,
3608 NULL,
3609 e_cal_client_tzlookup_icalcomp_cb,
3610 lookup_data,
3611 NULL,
3612 &err)) {
3613 /*
3614 * This makes assumptions about what kind of
3615 * errors can occur inside e_cal_check_timezones().
3616 * We control it, so that should be safe, but
3617 * is the code really identical with the calendar
3618 * status?
3619 */
3620 goto error;
3621 }
3622
3623 /* Merge the iCalendar components with our existing VCALENDAR,
3624 * resolving any conflicting TZIDs. It also frees the toplevel_comp. */
3625 i_cal_component_merge_component (priv->vcalendar, toplevel_comp);
3626 g_clear_object (&toplevel_comp);
3627
3628 /* Now we manipulate the components we care about */
3629 comps = g_slist_sort (comps, masters_first_cmp);
3630
3631 for (link = comps; link; link = g_slist_next (link)) {
3632 ECalComponent *old_component = NULL;
3633 ECalComponent *new_component = NULL;
3634 ECalObjModType mod = E_CAL_OBJ_MOD_THIS;
3635 ICalTime *current;
3636 const gchar *uid;
3637 gchar *rid;
3638 ECalBackendFileObject *obj_data;
3639 gboolean is_declined;
3640
3641 subcomp = link->data;
3642
3643 /* Create the cal component */
3644 comp = e_cal_component_new_from_icalcomponent (g_object_ref (subcomp));
3645 if (!comp)
3646 continue;
3647
3648 /* Set the created and last modified times on the component, if not there already */
3649 current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
3650
3651 if (!e_cal_util_component_has_property (subcomp, I_CAL_CREATED_PROPERTY)) {
3652 /* Update both when CREATED is missing, to make sure the LAST-MODIFIED
3653 is not before CREATED */
3654 e_cal_component_set_created (comp, current);
3655 e_cal_component_set_last_modified (comp, current);
3656 } else if (!e_cal_util_component_has_property (subcomp, I_CAL_LASTMODIFIED_PROPERTY)) {
3657 e_cal_component_set_last_modified (comp, current);
3658 }
3659
3660 g_clear_object (¤t);
3661
3662 uid = e_cal_component_get_uid (comp);
3663 rid = e_cal_component_get_recurid_as_string (comp);
3664
3665 if (rid) {
3666 ECalComponentRange *range;
3667
3668 range = e_cal_component_get_recurid (comp);
3669
3670 if (range && e_cal_component_range_get_kind (range) == E_CAL_COMPONENT_RANGE_THISFUTURE)
3671 mod = E_CAL_OBJ_MOD_THIS_AND_FUTURE;
3672
3673 e_cal_component_range_free (range);
3674 }
3675
3676 if (e_cal_util_component_has_property (subcomp, I_CAL_METHOD_PROPERTY))
3677 method = i_cal_component_get_method (subcomp);
3678 else
3679 method = toplevel_method;
3680
3681 switch (method) {
3682 case I_CAL_METHOD_PUBLISH:
3683 case I_CAL_METHOD_REQUEST:
3684 case I_CAL_METHOD_REPLY:
3685 is_declined = e_cal_backend_user_declined (registry, subcomp);
3686
3687 /* handle attachments */
3688 if (!is_declined && e_cal_component_has_attachments (comp))
3689 fetch_attachments (backend, comp);
3690 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
3691 if (obj_data) {
3692
3693 if (rid) {
3694 ECalComponent *ignore_comp = NULL;
3695
3696 remove_instance (
3697 cbfile, obj_data, uid, rid, mod,
3698 &old_component, &ignore_comp, NULL);
3699
3700 if (ignore_comp)
3701 g_object_unref (ignore_comp);
3702 } else {
3703 if (obj_data->full_object) {
3704 old_component = e_cal_component_clone (obj_data->full_object);
3705 }
3706 remove_component (cbfile, uid, obj_data);
3707 }
3708
3709 if (!is_declined)
3710 add_component (cbfile, comp, FALSE);
3711
3712 if (!is_declined)
3713 e_cal_backend_notify_component_modified (E_CAL_BACKEND (backend),
3714 old_component, comp);
3715 else {
3716 ECalComponentId *id = e_cal_component_get_id (comp);
3717
3718 e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
3719 id, old_component,
3720 rid ? comp : NULL);
3721
3722 e_cal_component_id_free (id);
3723 g_object_unref (comp);
3724 }
3725
3726 if (old_component)
3727 g_object_unref (old_component);
3728
3729 } else if (!is_declined) {
3730 add_component (cbfile, comp, FALSE);
3731
3732 e_cal_backend_notify_component_created (E_CAL_BACKEND (backend), comp);
3733 } else {
3734 g_object_unref (comp);
3735 }
3736 g_free (rid);
3737 break;
3738 case I_CAL_METHOD_ADD:
3739 /* FIXME This should be doable once all the recurid stuff is done */
3740 err = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Unsupported method"));
3741 g_object_unref (comp);
3742 g_free (rid);
3743 goto error;
3744 break;
3745 case I_CAL_METHOD_COUNTER:
3746 err = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Unsupported method"));
3747 g_object_unref (comp);
3748 g_free (rid);
3749 goto error;
3750 break;
3751 case I_CAL_METHOD_DECLINECOUNTER:
3752 err = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Unsupported method"));
3753 g_object_unref (comp);
3754 g_free (rid);
3755 goto error;
3756 break;
3757 case I_CAL_METHOD_CANCEL:
3758 if (cancel_received_object (cbfile, comp, mod, &old_component, &new_component)) {
3759 ECalComponentId *id;
3760
3761 id = e_cal_component_get_id (comp);
3762
3763 e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
3764 id, old_component, new_component);
3765
3766 /* remove the component from the toplevel VCALENDAR */
3767 i_cal_component_remove_component (priv->vcalendar, subcomp);
3768 e_cal_component_id_free (id);
3769
3770 if (new_component)
3771 g_object_unref (new_component);
3772 if (old_component)
3773 g_object_unref (old_component);
3774 }
3775 g_object_unref (comp);
3776 g_free (rid);
3777 break;
3778 default:
3779 err = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Unsupported method"));
3780 g_object_unref (comp);
3781 g_free (rid);
3782 goto error;
3783 }
3784 }
3785
3786 save (cbfile, TRUE);
3787
3788 error:
3789 g_slist_free_full (del_comps, g_object_unref);
3790 g_slist_free_full (comps, g_object_unref);
3791
3792 g_hash_table_destroy (tzdata.zones);
3793 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3794 e_cal_client_tzlookup_icalcomp_data_free (lookup_data);
3795
3796 if (err)
3797 g_propagate_error (error, err);
3798 }
3799
3800 static void
e_cal_backend_file_send_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * calobj,ECalOperationFlags opflags,GSList ** users,gchar ** modified_calobj,GError ** perror)3801 e_cal_backend_file_send_objects (ECalBackendSync *backend,
3802 EDataCal *cal,
3803 GCancellable *cancellable,
3804 const gchar *calobj,
3805 ECalOperationFlags opflags,
3806 GSList **users,
3807 gchar **modified_calobj,
3808 GError **perror)
3809 {
3810 *users = NULL;
3811 *modified_calobj = g_strdup (calobj);
3812 }
3813
3814 static void
cal_backend_file_email_address_changed_cb(GObject * object,GParamSpec * param,gpointer user_data)3815 cal_backend_file_email_address_changed_cb (GObject *object,
3816 GParamSpec *param,
3817 gpointer user_data)
3818 {
3819 ECalBackend *cal_backend = user_data;
3820 gchar *email_address;
3821
3822 g_return_if_fail (E_IS_SOURCE_LOCAL (object));
3823 g_return_if_fail (E_IS_CAL_BACKEND (cal_backend));
3824
3825 email_address = e_source_local_dup_email_address (E_SOURCE_LOCAL (object));
3826
3827 e_cal_backend_notify_property_changed (cal_backend, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, email_address);
3828 e_cal_backend_notify_property_changed (cal_backend, E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS, email_address);
3829 }
3830
3831 static void
cal_backend_file_constructed(GObject * object)3832 cal_backend_file_constructed (GObject *object)
3833 {
3834 ECalBackend *backend;
3835 ESourceRegistry *registry;
3836 ESource *builtin_source;
3837 ESource *source;
3838 ESourceLocal *local_extension;
3839 ICalComponentKind kind;
3840 const gchar *user_data_dir;
3841 const gchar *component_type;
3842 const gchar *uid;
3843 gchar *filename;
3844
3845 user_data_dir = e_get_user_data_dir ();
3846
3847 /* Chain up to parent's constructed() method. */
3848 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->constructed (object);
3849
3850 /* Override the cache directory that the parent class just set. */
3851
3852 backend = E_CAL_BACKEND (object);
3853 kind = e_cal_backend_get_kind (backend);
3854 source = e_backend_get_source (E_BACKEND (backend));
3855 registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
3856
3857 uid = e_source_get_uid (source);
3858 g_return_if_fail (uid != NULL);
3859
3860 switch (kind) {
3861 case I_CAL_VEVENT_COMPONENT:
3862 component_type = "calendar";
3863 builtin_source = e_source_registry_ref_builtin_calendar (registry);
3864 break;
3865 case I_CAL_VTODO_COMPONENT:
3866 component_type = "tasks";
3867 builtin_source = e_source_registry_ref_builtin_task_list (registry);
3868 break;
3869 case I_CAL_VJOURNAL_COMPONENT:
3870 component_type = "memos";
3871 builtin_source = e_source_registry_ref_builtin_memo_list (registry);
3872 break;
3873 default:
3874 g_warn_if_reached ();
3875 component_type = "calendar";
3876 builtin_source = e_source_registry_ref_builtin_calendar (registry);
3877 break;
3878 }
3879
3880 /* XXX Backward-compatibility hack:
3881 *
3882 * The special built-in "Personal" data source UIDs are now named
3883 * "system-$COMPONENT" but since the data directories are already
3884 * split out by component, we'll continue to use the old "system"
3885 * directories for these particular data sources. */
3886 if (e_source_equal (source, builtin_source))
3887 uid = "system";
3888
3889 filename = g_build_filename (user_data_dir, component_type, uid, NULL);
3890 e_cal_backend_set_cache_dir (backend, filename);
3891 g_free (filename);
3892
3893 g_object_unref (builtin_source);
3894
3895 local_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_LOCAL_BACKEND);
3896
3897 g_signal_connect_object (local_extension, "notify::email-address",
3898 G_CALLBACK (cal_backend_file_email_address_changed_cb), backend, 0);
3899 }
3900
3901 static void
cal_backend_file_add_cached_timezone(ETimezoneCache * cache,ICalTimezone * zone)3902 cal_backend_file_add_cached_timezone (ETimezoneCache *cache,
3903 ICalTimezone *zone)
3904 {
3905 ECalBackendFilePrivate *priv;
3906 const gchar *tzid;
3907 gboolean timezone_added = FALSE;
3908
3909 priv = E_CAL_BACKEND_FILE (cache)->priv;
3910
3911 g_rec_mutex_lock (&priv->idle_save_rmutex);
3912
3913 tzid = i_cal_timezone_get_tzid (zone);
3914 if (!i_cal_component_get_timezone (priv->vcalendar, tzid)) {
3915 ICalComponent *tz_comp;
3916
3917 tz_comp = i_cal_timezone_get_component (zone);
3918
3919 i_cal_component_take_component (priv->vcalendar, i_cal_component_clone (tz_comp));
3920
3921 g_clear_object (&tz_comp);
3922
3923 timezone_added = TRUE;
3924 save (E_CAL_BACKEND_FILE (cache), TRUE);
3925 }
3926
3927 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3928
3929 /* Emit the signal outside of the mutex. */
3930 if (timezone_added)
3931 g_signal_emit_by_name (cache, "timezone-added", zone);
3932 }
3933
3934 static ICalTimezone *
cal_backend_file_get_cached_timezone(ETimezoneCache * cache,const gchar * tzid)3935 cal_backend_file_get_cached_timezone (ETimezoneCache *cache,
3936 const gchar *tzid)
3937 {
3938 ECalBackendFilePrivate *priv;
3939 ICalTimezone *zone;
3940
3941 priv = E_CAL_BACKEND_FILE (cache)->priv;
3942
3943 g_rec_mutex_lock (&priv->idle_save_rmutex);
3944 zone = g_hash_table_lookup (priv->cached_timezones, tzid);
3945 if (!zone) {
3946 zone = i_cal_component_get_timezone (priv->vcalendar, tzid);
3947 if (zone)
3948 g_hash_table_insert (priv->cached_timezones, g_strdup (tzid), zone);
3949 }
3950 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3951
3952 if (zone != NULL)
3953 return zone;
3954
3955 /* Chain up and let ECalBackend try to match
3956 * the TZID against a built-in ICalTimezone. */
3957 return parent_timezone_cache_interface->tzcache_get_timezone (cache, tzid);
3958 }
3959
3960 static GList *
cal_backend_file_list_cached_timezones(ETimezoneCache * cache)3961 cal_backend_file_list_cached_timezones (ETimezoneCache *cache)
3962 {
3963 /* XXX As of 3.7, the only e_timezone_cache_list_timezones()
3964 * call comes from ECalBackendStore, which this backend
3965 * does not use. So we should never get here. Emit a
3966 * runtime warning so we know if this changes. */
3967
3968 g_return_val_if_reached (NULL);
3969 }
3970
3971 static void
e_cal_backend_file_class_init(ECalBackendFileClass * class)3972 e_cal_backend_file_class_init (ECalBackendFileClass *class)
3973 {
3974 GObjectClass *object_class;
3975 ECalBackendClass *backend_class;
3976 ECalBackendSyncClass *sync_class;
3977
3978 object_class = (GObjectClass *) class;
3979 backend_class = (ECalBackendClass *) class;
3980 sync_class = (ECalBackendSyncClass *) class;
3981
3982 object_class->dispose = e_cal_backend_file_dispose;
3983 object_class->finalize = e_cal_backend_file_finalize;
3984 object_class->constructed = cal_backend_file_constructed;
3985
3986 backend_class->impl_get_backend_property = e_cal_backend_file_get_backend_property;
3987 backend_class->impl_start_view = e_cal_backend_file_start_view;
3988
3989 sync_class->open_sync = e_cal_backend_file_open;
3990 sync_class->create_objects_sync = e_cal_backend_file_create_objects;
3991 sync_class->modify_objects_sync = e_cal_backend_file_modify_objects;
3992 sync_class->remove_objects_sync = e_cal_backend_file_remove_objects;
3993 sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
3994 sync_class->send_objects_sync = e_cal_backend_file_send_objects;
3995 sync_class->get_object_sync = e_cal_backend_file_get_object;
3996 sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
3997 sync_class->get_attachment_uris_sync = e_cal_backend_file_get_attachment_uris;
3998 sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
3999 sync_class->get_free_busy_sync = e_cal_backend_file_get_free_busy;
4000 sync_class->discard_alarm_sync = e_cal_backend_file_discard_alarm_sync;
4001
4002 /* Register our ESource extension. */
4003 E_TYPE_SOURCE_LOCAL;
4004 }
4005
4006 static void
e_cal_backend_file_timezone_cache_init(ETimezoneCacheInterface * iface)4007 e_cal_backend_file_timezone_cache_init (ETimezoneCacheInterface *iface)
4008 {
4009 parent_timezone_cache_interface = g_type_interface_peek_parent (iface);
4010
4011 iface->tzcache_add_timezone = cal_backend_file_add_cached_timezone;
4012 iface->tzcache_get_timezone = cal_backend_file_get_cached_timezone;
4013 iface->tzcache_list_timezones = cal_backend_file_list_cached_timezones;
4014 }
4015
4016 static void
e_cal_backend_file_init(ECalBackendFile * cbfile)4017 e_cal_backend_file_init (ECalBackendFile *cbfile)
4018 {
4019 cbfile->priv = e_cal_backend_file_get_instance_private (cbfile);
4020
4021 cbfile->priv->file_name = g_strdup ("calendar.ics");
4022
4023 g_rec_mutex_init (&cbfile->priv->idle_save_rmutex);
4024
4025 g_mutex_init (&cbfile->priv->refresh_lock);
4026
4027 cbfile->priv->cached_timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
4028 }
4029
4030 void
e_cal_backend_file_set_file_name(ECalBackendFile * cbfile,const gchar * file_name)4031 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile,
4032 const gchar *file_name)
4033 {
4034 ECalBackendFilePrivate *priv;
4035
4036 g_return_if_fail (cbfile != NULL);
4037 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
4038 g_return_if_fail (file_name != NULL);
4039
4040 priv = cbfile->priv;
4041 g_rec_mutex_lock (&priv->idle_save_rmutex);
4042
4043 g_free (priv->file_name);
4044 priv->file_name = g_strdup (file_name);
4045
4046 g_rec_mutex_unlock (&priv->idle_save_rmutex);
4047 }
4048
4049 const gchar *
e_cal_backend_file_get_file_name(ECalBackendFile * cbfile)4050 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
4051 {
4052 ECalBackendFilePrivate *priv;
4053
4054 g_return_val_if_fail (cbfile != NULL, NULL);
4055 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
4056
4057 priv = cbfile->priv;
4058
4059 return priv->file_name;
4060 }
4061
4062 void
e_cal_backend_file_reload(ECalBackendFile * cbfile,GError ** perror)4063 e_cal_backend_file_reload (ECalBackendFile *cbfile,
4064 GError **perror)
4065 {
4066 ECalBackendFilePrivate *priv;
4067 gchar *str_uri;
4068 gboolean writable = FALSE;
4069 GError *err = NULL;
4070
4071 priv = cbfile->priv;
4072 g_rec_mutex_lock (&priv->idle_save_rmutex);
4073
4074 str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
4075 if (!str_uri) {
4076 err = EC_ERROR_NO_URI ();
4077 goto done;
4078 }
4079
4080 writable = e_cal_backend_get_writable (E_CAL_BACKEND (cbfile));
4081
4082 if (g_access (str_uri, R_OK) == 0) {
4083 reload_cal (cbfile, str_uri, &err);
4084 if (g_access (str_uri, W_OK) != 0)
4085 writable = FALSE;
4086 } else {
4087 err = ECC_ERROR (E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR);
4088 }
4089
4090 g_free (str_uri);
4091
4092 if (!err && writable) {
4093 if (!get_source_writable (E_BACKEND (cbfile)))
4094 writable = FALSE;
4095 }
4096 done:
4097 g_rec_mutex_unlock (&priv->idle_save_rmutex);
4098 e_cal_backend_set_writable (E_CAL_BACKEND (cbfile), writable);
4099
4100 if (err)
4101 g_propagate_error (perror, err);
4102 }
4103
4104 #ifdef TEST_QUERY_RESULT
4105
4106 static void
test_query_by_scanning_all_objects(ECalBackendFile * cbfile,const gchar * sexp,GSList ** objects)4107 test_query_by_scanning_all_objects (ECalBackendFile *cbfile,
4108 const gchar *sexp,
4109 GSList **objects)
4110 {
4111 MatchObjectData match_data;
4112 ECalBackendFilePrivate *priv;
4113
4114 priv = cbfile->priv;
4115
4116 match_data.search_needed = TRUE;
4117 match_data.query = sexp;
4118 match_data.comps_list = NULL;
4119 match_data.as_string = TRUE;
4120 match_data.backend = E_CAL_BACKEND (cbfile);
4121
4122 if (sexp && !strcmp (sexp, "#t"))
4123 match_data.search_needed = FALSE;
4124
4125 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
4126 if (!match_data.obj_sexp)
4127 return;
4128
4129 g_rec_mutex_lock (&priv->idle_save_rmutex);
4130
4131 if (!match_data.obj_sexp)
4132 {
4133 g_message (G_STRLOC ": Getting object list (%s)", sexp);
4134 exit (-1);
4135 }
4136
4137 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
4138 &match_data);
4139
4140 g_rec_mutex_unlock (&priv->idle_save_rmutex);
4141
4142 *objects = g_slist_reverse (match_data.comps_list);
4143
4144 g_object_unref (match_data.obj_sexp);
4145 }
4146
4147 static void
write_list(GSList * list)4148 write_list (GSList *list)
4149 {
4150 GSList *l;
4151
4152 for (l = list; l; l = l->next)
4153 {
4154 const gchar *str = l->data;
4155 ECalComponent *comp = e_cal_component_new_from_string (str);
4156 const gchar *uid;
4157 uid = e_cal_component_get_uid (comp);
4158 g_print ("%s\n", uid);
4159 }
4160 }
4161
4162 static void
get_difference_of_lists(ECalBackendFile * cbfile,GSList * smaller,GSList * bigger)4163 get_difference_of_lists (ECalBackendFile *cbfile,
4164 GSList *smaller,
4165 GSList *bigger)
4166 {
4167 GSList *l, *lsmaller;
4168
4169 for (l = bigger; l; l = l->next) {
4170 gchar *str = l->data;
4171 const gchar *uid;
4172 ECalComponent *comp = e_cal_component_new_from_string (str);
4173 gboolean found = FALSE;
4174 uid = e_cal_component_get_uid (comp);
4175
4176 for (lsmaller = smaller; lsmaller && !found; lsmaller = lsmaller->next)
4177 {
4178 gchar *strsmaller = lsmaller->data;
4179 const gchar *uidsmaller;
4180 ECalComponent *compsmaller = e_cal_component_new_from_string (strsmaller);
4181 uidsmaller = e_cal_component_get_uid (compsmaller);
4182
4183 found = strcmp (uid, uidsmaller) == 0;
4184
4185 g_object_unref (compsmaller);
4186 }
4187
4188 if (!found)
4189 {
4190 time_t time_start, time_end;
4191 ResolveTzidData rtd;
4192 printf ("%s IS MISSING\n", uid);
4193
4194 resolve_tzid_data_init (&rtd, cbfile->priv->vcalendar);
4195
4196 e_cal_util_get_component_occur_times (
4197 comp, &time_start, &time_end,
4198 resolve_tzid_cb, &rtd,
4199 i_cal_timezone_get_utc_timezone (),
4200 e_cal_backend_get_kind (E_CAL_BACKEND (cbfile)));
4201
4202 resolve_tzid_data_clear (&rtd);
4203
4204 d (printf ("start %s\n", asctime (gmtime (&time_start))));
4205 d (printf ("end %s\n", asctime (gmtime (&time_end))));
4206 }
4207
4208 g_object_unref (comp);
4209 }
4210 }
4211
4212 static void
test_query(ECalBackendFile * cbfile,const gchar * query)4213 test_query (ECalBackendFile *cbfile,
4214 const gchar *query)
4215 {
4216 GSList *objects = NULL, *all_objects = NULL;
4217
4218 g_return_if_fail (query != NULL);
4219
4220 d (g_print ("Query %s\n", query));
4221
4222 test_query_by_scanning_all_objects (cbfile, query, &all_objects);
4223 e_cal_backend_file_get_object_list (E_CAL_BACKEND_SYNC (cbfile), NULL, NULL, query, &objects, NULL);
4224 if (objects == NULL)
4225 {
4226 g_message (G_STRLOC " failed to get objects\n");
4227 exit (0);
4228 }
4229
4230 if (g_slist_length (objects) < g_slist_length (all_objects) )
4231 {
4232 g_print ("ERROR\n");
4233 get_difference_of_lists (cbfile, objects, all_objects);
4234 exit (-1);
4235 }
4236 else if (g_slist_length (objects) > g_slist_length (all_objects) )
4237 {
4238 g_print ("ERROR\n");
4239 write_list (all_objects);
4240 get_difference_of_lists (cbfile, all_objects, objects);
4241 exit (-1);
4242 }
4243
4244 g_slist_foreach (objects, (GFunc) g_free, NULL);
4245 g_slist_free (objects);
4246 g_slist_foreach (all_objects, (GFunc) g_free, NULL);
4247 g_slist_free (all_objects);
4248 }
4249
4250 static void
execute_query(ECalBackendFile * cbfile,const gchar * query)4251 execute_query (ECalBackendFile *cbfile,
4252 const gchar *query)
4253 {
4254 GSList *objects = NULL;
4255
4256 g_return_if_fail (query != NULL);
4257
4258 d (g_print ("Query %s\n", query));
4259 e_cal_backend_file_get_object_list (E_CAL_BACKEND_SYNC (cbfile), NULL, NULL, query, &objects, NULL);
4260 if (objects == NULL)
4261 {
4262 g_message (G_STRLOC " failed to get objects\n");
4263 exit (0);
4264 }
4265
4266 g_slist_foreach (objects, (GFunc) g_free, NULL);
4267 g_slist_free (objects);
4268 }
4269
4270 static gchar *fname = NULL;
4271 static gboolean only_execute = FALSE;
4272 static gchar *calendar_fname = NULL;
4273
4274 static GOptionEntry entries[] =
4275 {
4276 { "test-file", 't', 0, G_OPTION_ARG_STRING, &fname, "File with prepared queries", NULL },
4277 { "only-execute", 'e', 0, G_OPTION_ARG_NONE, &only_execute, "Only execute, do not test query", NULL },
4278 { "calendar-file", 'c', 0, G_OPTION_ARG_STRING, &calendar_fname, "Path to the calendar.ics file", NULL },
4279 { NULL }
4280 };
4281
4282 /* Always add at least this many bytes when extending the buffer. */
4283 #define MIN_CHUNK 64
4284
4285 static gint
private_getline(gchar ** lineptr,gsize * n,FILE * stream)4286 private_getline (gchar **lineptr,
4287 gsize *n,
4288 FILE *stream)
4289 {
4290 gint nchars_avail;
4291 gchar *read_pos;
4292
4293 if (!lineptr || !n || !stream)
4294 return -1;
4295
4296 if (!*lineptr) {
4297 *n = MIN_CHUNK;
4298 *lineptr = (char *)malloc (*n);
4299 if (!*lineptr)
4300 return -1;
4301 }
4302
4303 nchars_avail = (gint) *n;
4304 read_pos = *lineptr;
4305
4306 for (;;) {
4307 gint c = getc (stream);
4308
4309 if (nchars_avail < 2) {
4310 if (*n > MIN_CHUNK)
4311 *n *= 2;
4312 else
4313 *n += MIN_CHUNK;
4314
4315 nchars_avail = (gint)(*n + *lineptr - read_pos);
4316 *lineptr = (char *)realloc (*lineptr, *n);
4317 if (!*lineptr)
4318 return -1;
4319 read_pos = *n - nchars_avail + *lineptr;
4320 }
4321
4322 if (ferror (stream) || c == EOF) {
4323 if (read_pos == *lineptr)
4324 return -1;
4325 else
4326 break;
4327 }
4328
4329 *read_pos++ = c;
4330 nchars_avail--;
4331
4332 if (c == '\n')
4333 /* Return the line. */
4334 break;
4335 }
4336
4337 *read_pos = '\0';
4338
4339 return (gint)(read_pos - (*lineptr));
4340 }
4341
4342 gint
main(gint argc,gchar ** argv)4343 main (gint argc,
4344 gchar **argv)
4345 {
4346 gchar * line = NULL;
4347 gsize len = 0;
4348 ECalBackendFile * cbfile;
4349 gint num = 0;
4350 GError *error = NULL;
4351 GOptionContext *context;
4352 FILE * fin = NULL;
4353
4354 context = g_option_context_new ("- test utility for e-d-s file backend");
4355 g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
4356 if (!g_option_context_parse (context, &argc, &argv, &error))
4357 {
4358 g_print ("option parsing failed: %s\n", error->message);
4359 exit (1);
4360 }
4361
4362 calendar_fname = g_strdup ("calendar.ics");
4363
4364 if (!calendar_fname)
4365 {
4366 g_message (G_STRLOC " Please, use -c parameter");
4367 exit (-1);
4368 }
4369
4370 cbfile = g_object_new (E_TYPE_CAL_BACKEND_FILE, NULL);
4371 open_cal (cbfile, calendar_fname, &error);
4372 if (error != NULL) {
4373 g_message (G_STRLOC " Could not open calendar %s: %s", calendar_fname, error->message);
4374 exit (-1);
4375 }
4376
4377 if (fname)
4378 {
4379 fin = fopen (fname, "r");
4380
4381 if (!fin)
4382 {
4383 g_message (G_STRLOC " Could not open file %s", fname);
4384 goto err0;
4385 }
4386 }
4387 else
4388 {
4389 g_message (G_STRLOC " Reading from stdin");
4390 fin = stdin;
4391 }
4392
4393 while (private_getline (&line, &len, fin) != -1) {
4394 g_print ("Query %d: %s", num++, line);
4395
4396 if (only_execute)
4397 execute_query (cbfile, line);
4398 else
4399 test_query (cbfile, line);
4400 }
4401
4402 if (line)
4403 free (line);
4404
4405 if (fname)
4406 fclose (fin);
4407
4408 err0:
4409 g_object_unref (cbfile);
4410
4411 return 0;
4412 }
4413 #endif
4414