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