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