Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / calendar / backends / file / e-cal-backend-file.c
1 /* Evolution calendar - iCalendar file backend
2  *
3  * Copyright (C) 2000-2003 Ximian, Inc.
4  *
5  * Authors: Federico Mena-Quintero <federico@ximian.com>
6  *          Rodrigo Moya <rodrigo@ximian.com>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of version 2 of the GNU Lesser General Public
10  * License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <bonobo/bonobo-exception.h>
32 #include <bonobo/bonobo-moniker-util.h>
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
35 #include <libgnomevfs/gnome-vfs.h>
36 #include "libedataserver/e-data-server-util.h"
37 #include "libedataserver/e-xml-hash-utils.h"
38 #include <libecal/e-cal-recur.h>
39 #include <libecal/e-cal-time-util.h>
40 #include <libecal/e-cal-util.h>
41 #include <libedata-cal/e-cal-backend-util.h>
42 #include <libedata-cal/e-cal-backend-sexp.h>
43 #include "e-cal-backend-file-events.h"
44
45 #ifndef O_BINARY
46 #define O_BINARY 0
47 #endif
48
49 /* Placeholder for each component and its recurrences */
50 typedef struct {
51         ECalComponent *full_object;
52         GHashTable *recurrences;
53         GList *recurrences_list;
54 } ECalBackendFileObject;
55
56 /* Private part of the ECalBackendFile structure */
57 struct _ECalBackendFilePrivate {
58         /* URI where the calendar data is stored */
59         char *uri;
60
61         /* Filename in the dir */
62         char *file_name;        
63         gboolean read_only;
64         gboolean is_dirty;
65         guint dirty_idle_id;
66
67         /* locked in high-level functions to ensure data is consistent
68          * in idle and CORBA thread(s?); because high-level functions
69          * may call other high-level functions the mutex must allow
70          * recursive locking
71          */
72         GStaticRecMutex idle_save_rmutex;
73
74         /* Toplevel VCALENDAR component */
75         icalcomponent *icalcomp;
76
77         /* All the objects in the calendar, hashed by UID.  The
78          * hash key *is* the uid returned by cal_component_get_uid(); it is not
79          * copied, so don't free it when you remove an object from the hash
80          * table. Each item in the hash table is a ECalBackendFileObject.
81          */
82         GHashTable *comp_uid_hash;
83
84         GList *comp;
85         
86         /* The calendar's default timezone, used for resolving DATE and
87            floating DATE-TIME values. */
88         icaltimezone *default_zone;
89
90         /* The list of live queries */
91         GList *queries;
92 };
93
94 \f
95
96 #define d(x)
97
98 static void e_cal_backend_file_dispose (GObject *object);
99 static void e_cal_backend_file_finalize (GObject *object);
100
101 static ECalBackendSyncClass *parent_class;
102
103 static ECalBackendSyncStatus
104 e_cal_backend_file_add_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj);
105
106 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
107 static void
108 free_object_data (gpointer data)
109 {
110         ECalBackendFileObject *obj_data = data;
111
112         if (obj_data->full_object)
113                 g_object_unref (obj_data->full_object);
114         g_hash_table_destroy (obj_data->recurrences);
115         g_list_free (obj_data->recurrences_list);
116
117         g_free (obj_data);
118 }
119
120 /* Saves the calendar data */
121 static gboolean
122 save_file_when_idle (gpointer user_data)
123 {
124         ECalBackendFilePrivate *priv;
125         GnomeVFSURI *uri, *backup_uri;
126         GnomeVFSHandle *handle = NULL;
127         GnomeVFSResult result = GNOME_VFS_ERROR_BAD_FILE;
128         GnomeVFSFileSize out;
129         gchar *tmp, *backup_uristr;
130         char *buf;
131         ECalBackendFile *cbfile = user_data;
132         
133         priv = cbfile->priv;
134         g_assert (priv->uri != NULL);
135         g_assert (priv->icalcomp != NULL);
136
137         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
138         if (!priv->is_dirty) {
139                 priv->dirty_idle_id = 0;
140                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
141                 return FALSE;
142         }
143
144         uri = gnome_vfs_uri_new (priv->uri);
145         if (!uri)
146                 goto error_malformed_uri;
147
148         /* save calendar to backup file */
149         tmp = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
150         if (!tmp) {
151                 gnome_vfs_uri_unref (uri);
152                 goto error_malformed_uri;
153         }
154                 
155         backup_uristr = g_strconcat (tmp, "~", NULL);
156         backup_uri = gnome_vfs_uri_new (backup_uristr);
157
158         g_free (tmp);
159         g_free (backup_uristr);
160
161         if (!backup_uri) {
162                 gnome_vfs_uri_unref (uri);
163                 goto error_malformed_uri;
164         }
165         
166         result = gnome_vfs_create_uri (&handle, backup_uri,
167                                        GNOME_VFS_OPEN_WRITE,
168                                        FALSE, 0666);
169         if (result != GNOME_VFS_OK) {
170                 gnome_vfs_uri_unref (uri);
171                 gnome_vfs_uri_unref (backup_uri);
172                 goto error;
173         }
174
175         buf = icalcomponent_as_ical_string (priv->icalcomp);
176         result = gnome_vfs_write (handle, buf, strlen (buf) * sizeof (char), &out);
177         gnome_vfs_close (handle);
178         if (result != GNOME_VFS_OK) {
179                 gnome_vfs_uri_unref (uri);
180                 gnome_vfs_uri_unref (backup_uri);
181                 goto error;
182         }
183
184         /* now copy the temporary file to the real file */
185         result = gnome_vfs_move_uri (backup_uri, uri, TRUE);
186
187         gnome_vfs_uri_unref (uri);
188         gnome_vfs_uri_unref (backup_uri);
189         if (result != GNOME_VFS_OK)
190                 goto error;
191
192         priv->is_dirty = FALSE;
193         priv->dirty_idle_id = 0;
194
195         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
196
197         return FALSE;
198
199  error_malformed_uri:
200         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
201         e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
202                                   _("Cannot save calendar data: Malformed URI."));
203         return TRUE;
204
205  error:
206         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
207         e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), gnome_vfs_result_to_string (result));
208         return TRUE;
209 }
210
211 static void
212 save (ECalBackendFile *cbfile)
213 {
214         ECalBackendFilePrivate *priv;
215
216         priv = cbfile->priv;
217
218         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
219         priv->is_dirty = TRUE;
220
221         if (!priv->dirty_idle_id)
222                 priv->dirty_idle_id = g_idle_add ((GSourceFunc) save_file_when_idle, cbfile);
223
224         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
225 }
226
227 static void
228 free_calendar_components (GHashTable *comp_uid_hash, icalcomponent *top_icomp)
229 {
230         if (comp_uid_hash)
231                 g_hash_table_destroy (comp_uid_hash);
232
233         if (top_icomp)
234                 icalcomponent_free (top_icomp);
235 }
236
237 static void
238 free_calendar_data (ECalBackendFile *cbfile)
239 {
240         ECalBackendFilePrivate *priv;
241
242         priv = cbfile->priv;
243
244         free_calendar_components (priv->comp_uid_hash, priv->icalcomp);
245         priv->comp_uid_hash = NULL;
246         priv->icalcomp = NULL;
247
248         g_list_free (priv->comp);
249         priv->comp = NULL;
250 }
251
252 /* Dispose handler for the file backend */
253 static void
254 e_cal_backend_file_dispose (GObject *object)
255 {
256         ECalBackendFile *cbfile;
257         ECalBackendFilePrivate *priv;
258
259         cbfile = E_CAL_BACKEND_FILE (object);
260         priv = cbfile->priv;
261
262         /* Save if necessary */
263         if (priv->is_dirty)
264                 save_file_when_idle (cbfile);
265
266         free_calendar_data (cbfile);
267
268         if (G_OBJECT_CLASS (parent_class)->dispose)
269                 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
270 }
271
272 /* Finalize handler for the file backend */
273 static void
274 e_cal_backend_file_finalize (GObject *object)
275 {
276         ECalBackendFile *cbfile;
277         ECalBackendFilePrivate *priv;
278
279         g_return_if_fail (object != NULL);
280         g_return_if_fail (E_IS_CAL_BACKEND_FILE (object));
281
282         cbfile = E_CAL_BACKEND_FILE (object);
283         priv = cbfile->priv;
284
285         /* Clean up */
286
287         if (priv->dirty_idle_id) {
288                 g_source_remove (priv->dirty_idle_id);
289                 priv->dirty_idle_id = 0;
290         }
291
292         g_static_rec_mutex_free (&priv->idle_save_rmutex);
293
294         if (priv->uri) {
295                 g_free (priv->uri);
296                 priv->uri = NULL;
297         }
298
299         if (priv->default_zone && priv->default_zone != icaltimezone_get_utc_timezone ()) {
300                 icaltimezone_free (priv->default_zone, 1);
301         }
302         priv->default_zone = NULL;
303
304         if (priv->file_name) {
305                 g_free (priv->file_name);
306                 priv->file_name = NULL;
307         }       
308         g_free (priv);
309         cbfile->priv = NULL;
310
311         if (G_OBJECT_CLASS (parent_class)->finalize)
312                 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
313 }
314
315 \f
316
317 /* Looks up a component by its UID on the backend's component hash table */
318 static ECalComponent *
319 lookup_component (ECalBackendFile *cbfile, const char *uid)
320 {
321         ECalBackendFilePrivate *priv;
322         ECalBackendFileObject *obj_data;
323
324         priv = cbfile->priv;
325
326         obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
327         return obj_data ? obj_data->full_object : NULL;
328 }
329
330 \f
331
332 /* Calendar backend methods */
333
334 /* Is_read_only handler for the file backend */
335 static ECalBackendSyncStatus
336 e_cal_backend_file_is_read_only (ECalBackendSync *backend, EDataCal *cal, gboolean *read_only)
337 {
338         ECalBackendFile *cbfile = (ECalBackendFile *) backend;
339
340         *read_only = cbfile->priv->read_only;
341         
342         return GNOME_Evolution_Calendar_Success;
343 }
344
345 /* Get_email_address handler for the file backend */
346 static ECalBackendSyncStatus
347 e_cal_backend_file_get_cal_address (ECalBackendSync *backend, EDataCal *cal, char **address)
348 {
349         /* A file backend has no particular email address associated
350          * with it (although that would be a useful feature some day).
351          */
352         *address = NULL;
353
354         return GNOME_Evolution_Calendar_Success;
355 }
356
357 static ECalBackendSyncStatus
358 e_cal_backend_file_get_ldap_attribute (ECalBackendSync *backend, EDataCal *cal, char **attribute)
359 {
360         *attribute = NULL;
361         
362         return GNOME_Evolution_Calendar_Success;
363 }
364
365 static ECalBackendSyncStatus
366 e_cal_backend_file_get_alarm_email_address (ECalBackendSync *backend, EDataCal *cal, char **address)
367 {
368         /* A file backend has no particular email address associated
369          * with it (although that would be a useful feature some day).
370          */
371         *address = NULL;
372         
373         return GNOME_Evolution_Calendar_Success;
374 }
375
376 static ECalBackendSyncStatus
377 e_cal_backend_file_get_static_capabilities (ECalBackendSync *backend, EDataCal *cal, char **capabilities)
378 {
379         *capabilities = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS ","
380                                   CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
381                                   CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED ","
382                                   CAL_STATIC_CAPABILITY_NO_THISANDPRIOR);
383         
384         return GNOME_Evolution_Calendar_Success;
385 }
386
387 /* function to resolve timezones */
388 static icaltimezone *
389 resolve_tzid (const char *tzid, gpointer user_data)
390 {
391         icalcomponent *vcalendar_comp = user_data;
392
393         if (!tzid || !tzid[0])
394                 return NULL;
395         else if (!strcmp (tzid, "UTC"))
396                 return icaltimezone_get_utc_timezone ();
397
398         return icalcomponent_get_timezone (vcalendar_comp, tzid);
399 }
400
401 /* Checks if the specified component has a duplicated UID and if so changes it */
402 static void
403 check_dup_uid (ECalBackendFile *cbfile, ECalComponent *comp)
404 {
405         ECalBackendFilePrivate *priv;
406         ECalBackendFileObject *obj_data;
407         const char *uid = NULL;
408         char *new_uid;
409
410         priv = cbfile->priv;
411
412         e_cal_component_get_uid (comp, &uid);
413
414         if (!uid) {
415                 g_warning ("Checking for duplicate uid, the component does not have a valid UID skipping it\n");
416                 return;
417         }
418
419         obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
420         if (!obj_data)
421                 return; /* Everything is fine */
422
423         d(g_message (G_STRLOC ": Got object with duplicated UID `%s', changing it...", uid));
424
425         new_uid = e_cal_component_gen_uid ();
426         e_cal_component_set_uid (comp, new_uid);
427         g_free (new_uid);
428
429         /* FIXME: I think we need to reset the SEQUENCE property and reset the
430          * CREATED/DTSTAMP/LAST-MODIFIED.
431          */
432
433         save (cbfile);
434 }
435
436 static struct icaltimetype
437 get_rid_icaltime (ECalComponent *comp)
438 {
439         ECalComponentRange range;
440         struct icaltimetype tt;
441                                                                                    
442         e_cal_component_get_recurid (comp, &range);
443         if (!range.datetime.value)
444                 return icaltime_null_time ();
445         tt = *range.datetime.value;
446         e_cal_component_free_range (&range);
447                                                                                    
448         return tt;
449 }
450
451 /* Tries to add an icalcomponent to the file backend.  We only store the objects
452  * of the types we support; all others just remain in the toplevel component so
453  * that we don't lose them.
454  */
455 static void
456 add_component (ECalBackendFile *cbfile, ECalComponent *comp, gboolean add_to_toplevel)
457 {
458         ECalBackendFilePrivate *priv;
459         ECalBackendFileObject *obj_data;
460         const char *uid = NULL;
461
462         priv = cbfile->priv;
463
464         e_cal_component_get_uid (comp, &uid);
465
466         if (!uid) {
467                 g_warning ("The component does not have a valid UID skipping it\n");
468                 return;
469         }
470                 
471         obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
472         if (e_cal_component_is_instance (comp)) {
473                 const char *rid;
474         
475                 rid = e_cal_component_get_recurid_as_string (comp);
476                 if (obj_data) {
477                         if (g_hash_table_lookup (obj_data->recurrences, rid)) {
478                                 g_warning (G_STRLOC ": Tried to add an already existing recurrence");
479                                 return;
480                         }
481                 } else {
482                         obj_data = g_new0 (ECalBackendFileObject, 1);
483                         obj_data->full_object = NULL;
484                         obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
485                         g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
486                 }
487
488                 g_hash_table_insert (obj_data->recurrences, g_strdup (rid), comp);
489                 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
490         } else {
491                 /* Ensure that the UID is unique; some broken implementations spit
492                  * components with duplicated UIDs.
493                  */
494                 check_dup_uid (cbfile, comp);
495
496                 if (obj_data) {
497                         if (obj_data->full_object) {
498                                 g_warning (G_STRLOC ": Tried to add an already existing object");
499                                 return;
500                         }
501
502                         obj_data->full_object = comp;
503                 } else {
504                         obj_data = g_new0 (ECalBackendFileObject, 1);
505                         obj_data->full_object = comp;
506                         obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
507
508                         g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
509                 }
510         }
511
512         priv->comp = g_list_prepend (priv->comp, comp);
513
514         /* Put the object in the toplevel component if required */
515
516         if (add_to_toplevel) {
517                 icalcomponent *icalcomp;
518
519                 icalcomp = e_cal_component_get_icalcomponent (comp);
520                 g_assert (icalcomp != NULL);
521
522                 icalcomponent_add_component (priv->icalcomp, icalcomp);
523
524                 save (cbfile);
525         }
526 }
527
528 /* g_hash_table_foreach_remove() callback to remove recurrences from the calendar */
529 static gboolean
530 remove_recurrence_cb (gpointer key, gpointer value, gpointer data)
531 {
532         GList *l;
533         icalcomponent *icalcomp;
534         ECalBackendFilePrivate *priv;
535         ECalComponent *comp = value;
536         ECalBackendFile *cbfile = data;
537
538         priv = cbfile->priv;
539
540         /* remove the recurrence from the top-level calendar */
541         icalcomp = e_cal_component_get_icalcomponent (comp);
542         g_assert (icalcomp != NULL);
543
544         icalcomponent_remove_component (priv->icalcomp, icalcomp);
545
546         /* remove it from our mapping */
547         l = g_list_find (priv->comp, comp);
548         priv->comp = g_list_delete_link (priv->comp, l);
549
550         return TRUE;
551 }
552
553 /* Removes a component from the backend's hash and lists.  Does not perform
554  * notification on the clients.  Also removes the component from the toplevel
555  * icalcomponent.
556  */
557 static void
558 remove_component (ECalBackendFile *cbfile, const char *uid, ECalBackendFileObject *obj_data)
559 {
560         ECalBackendFilePrivate *priv;
561         icalcomponent *icalcomp;
562         GList *l;
563
564         priv = cbfile->priv;
565
566         /* Remove the icalcomp from the toplevel */
567         if (obj_data->full_object) {
568                 icalcomp = e_cal_component_get_icalcomponent (obj_data->full_object);
569                 g_assert (icalcomp != NULL);
570
571                 icalcomponent_remove_component (priv->icalcomp, icalcomp);
572
573                 /* Remove it from our mapping */
574                 l = g_list_find (priv->comp, obj_data->full_object);
575                 g_assert (l != NULL);
576                 priv->comp = g_list_delete_link (priv->comp, l);
577         }
578
579         /* remove the recurrences also */
580         g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_recurrence_cb, cbfile);
581
582         g_hash_table_remove (priv->comp_uid_hash, uid);
583
584         save (cbfile);
585 }
586
587 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
588 static void
589 scan_vcalendar (ECalBackendFile *cbfile)
590 {
591         ECalBackendFilePrivate *priv;
592         icalcompiter iter;
593
594         priv = cbfile->priv;
595         g_assert (priv->icalcomp != NULL);
596         g_assert (priv->comp_uid_hash != NULL);
597
598         for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_ANY_COMPONENT);
599              icalcompiter_deref (&iter) != NULL;
600              icalcompiter_next (&iter)) {
601                 icalcomponent *icalcomp;
602                 icalcomponent_kind kind;
603                 ECalComponent *comp;
604
605                 icalcomp = icalcompiter_deref (&iter);
606                 
607                 kind = icalcomponent_isa (icalcomp);
608
609                 if (!(kind == ICAL_VEVENT_COMPONENT
610                       || kind == ICAL_VTODO_COMPONENT
611                       || kind == ICAL_VJOURNAL_COMPONENT))
612                         continue;
613
614                 comp = e_cal_component_new ();
615
616                 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
617                         continue;
618
619                 add_component (cbfile, comp, FALSE);
620         }
621 }
622
623 static char *
624 get_uri_string_for_gnome_vfs (ECalBackend *backend)
625 {
626         ECalBackendFile *cbfile;
627         ECalBackendFilePrivate *priv;
628         const char *master_uri;
629         char *full_uri, *str_uri;
630         GnomeVFSURI *uri;
631
632         cbfile = E_CAL_BACKEND_FILE (backend);
633         priv = cbfile->priv;
634         
635         master_uri = e_cal_backend_get_uri (backend);
636
637         /* FIXME Check the error conditions a little more elegantly here */
638         if (g_strrstr ("tasks.ics", master_uri) || g_strrstr ("calendar.ics", master_uri)) {
639                 g_warning (G_STRLOC ": Existing file name %s", master_uri);
640
641                 return NULL;
642         }
643         
644         full_uri = g_strdup_printf ("%s/%s", master_uri, priv->file_name);
645         uri = gnome_vfs_uri_new (full_uri);
646         g_free (full_uri);
647         
648         if (!uri)
649                 return NULL;
650
651         str_uri = gnome_vfs_uri_to_string (uri,
652                                            (GNOME_VFS_URI_HIDE_USER_NAME
653                                             | GNOME_VFS_URI_HIDE_PASSWORD
654                                             | GNOME_VFS_URI_HIDE_HOST_NAME
655                                             | GNOME_VFS_URI_HIDE_HOST_PORT
656                                             | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD));
657         gnome_vfs_uri_unref (uri);
658
659         if (!str_uri || !strlen (str_uri)) {
660                 g_free (str_uri);
661
662                 return NULL;
663         }       
664
665         return str_uri;
666 }
667
668 /* Parses an open iCalendar file and loads it into the backend */
669 static ECalBackendSyncStatus
670 open_cal (ECalBackendFile *cbfile, const char *uristr)
671 {
672         ECalBackendFilePrivate *priv;
673         icalcomponent *icalcomp;
674
675         priv = cbfile->priv;
676
677         icalcomp = e_cal_util_parse_ics_file (uristr);
678         if (!icalcomp)
679                 return GNOME_Evolution_Calendar_OtherError;
680
681         /* FIXME: should we try to demangle XROOT components and
682          * individual components as well?
683          */
684
685         if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
686                 icalcomponent_free (icalcomp);
687
688                 return GNOME_Evolution_Calendar_OtherError;
689         }
690
691         priv->icalcomp = icalcomp;
692         priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
693
694         priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
695         scan_vcalendar (cbfile);
696
697         return GNOME_Evolution_Calendar_Success;
698 }
699
700 typedef struct
701 {
702         ECalBackend *backend;
703         GHashTable *old_uid_hash;
704         GHashTable *new_uid_hash;
705 }
706 BackendDeltaContext;
707
708 static void
709 notify_removals_cb (gpointer key, gpointer value, gpointer data)
710 {
711         BackendDeltaContext *context = data;
712         const gchar *uid = key;
713         ECalBackendFileObject *old_obj_data = value;
714
715         if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
716                 icalcomponent *old_icomp;
717                 gchar *old_obj_str;
718                 ECalComponent *comp; 
719                 ECalComponentId *id;
720
721                 /* Object was removed */
722
723                 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
724                 if (!old_icomp)
725                         return;
726
727                 old_obj_str = icalcomponent_as_ical_string (old_icomp);
728                 if (!old_obj_str)
729                         return;
730                 
731                 comp = e_cal_component_new_from_string (old_obj_str);
732                 id = e_cal_component_get_id (comp);
733                 
734
735                 e_cal_backend_notify_object_removed (context->backend, id, old_obj_str, NULL);
736         
737                 e_cal_component_free_id (id);
738                 g_object_unref (comp);
739         }
740 }
741
742 static void
743 notify_adds_modifies_cb (gpointer key, gpointer value, gpointer data)
744 {
745         BackendDeltaContext *context = data;
746         const gchar *uid = key;
747         ECalBackendFileObject *new_obj_data = value;
748         ECalBackendFileObject *old_obj_data;
749         icalcomponent *old_icomp, *new_icomp;
750         gchar *old_obj_str, *new_obj_str;
751
752         old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
753
754         if (!old_obj_data) {
755                 /* Object was added */
756
757                 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
758                 if (!new_icomp)
759                         return;
760
761                 new_obj_str = icalcomponent_as_ical_string (new_icomp);
762                 if (!new_obj_str)
763                         return;
764
765                 e_cal_backend_notify_object_created (context->backend, new_obj_str);
766         } else {
767                 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
768                 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
769                 if (!old_icomp || !new_icomp)
770                         return;
771
772                 old_obj_str = icalcomponent_as_ical_string (old_icomp);
773                 new_obj_str = icalcomponent_as_ical_string (new_icomp);
774                 if (!old_obj_str || !new_obj_str)
775                         return;
776
777                 if (strcmp (old_obj_str, new_obj_str)) {
778                         /* Object was modified */
779
780                         e_cal_backend_notify_object_modified (context->backend, old_obj_str, new_obj_str);
781                 }
782         }
783 }
784
785 static void
786 notify_changes (ECalBackendFile *cbfile, GHashTable *old_uid_hash, GHashTable *new_uid_hash)
787 {
788         BackendDeltaContext context;
789
790         context.backend = E_CAL_BACKEND (cbfile);
791         context.old_uid_hash = old_uid_hash;
792         context.new_uid_hash = new_uid_hash;
793
794         g_hash_table_foreach (old_uid_hash, (GHFunc) notify_removals_cb, &context);
795         g_hash_table_foreach (new_uid_hash, (GHFunc) notify_adds_modifies_cb, &context);
796 }
797
798 static ECalBackendSyncStatus
799 reload_cal (ECalBackendFile *cbfile, const char *uristr)
800 {
801         ECalBackendFilePrivate *priv;
802         icalcomponent *icalcomp, *icalcomp_old;
803         GHashTable *comp_uid_hash_old;
804
805         priv = cbfile->priv;
806
807         icalcomp = e_cal_util_parse_ics_file (uristr);
808         if (!icalcomp)
809                 return GNOME_Evolution_Calendar_OtherError;
810
811         /* FIXME: should we try to demangle XROOT components and
812          * individual components as well?
813          */
814
815         if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
816                 icalcomponent_free (icalcomp);
817
818                 return GNOME_Evolution_Calendar_OtherError;
819         }
820
821         /* Keep old data for comparison - free later */
822
823         icalcomp_old = priv->icalcomp;
824         priv->icalcomp = NULL;
825
826         comp_uid_hash_old = priv->comp_uid_hash;
827         priv->comp_uid_hash = NULL;
828
829         /* Load new calendar */
830
831         free_calendar_data (cbfile);
832
833         priv->icalcomp = icalcomp;
834
835         priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
836         scan_vcalendar (cbfile);
837
838         priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
839
840         /* Compare old and new versions of calendar */
841
842         notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
843
844         /* Free old data */
845
846         free_calendar_components (comp_uid_hash_old, icalcomp_old);
847         return GNOME_Evolution_Calendar_Success;
848 }
849
850 static ECalBackendSyncStatus
851 create_cal (ECalBackendFile *cbfile, const char *uristr)
852 {
853         char *dirname;
854         ECalBackendFilePrivate *priv;
855
856         priv = cbfile->priv;
857
858         /* Create the directory to contain the file */
859         dirname = g_path_get_dirname (uristr);
860         if (g_mkdir_with_parents (dirname, 0700) != 0) {
861                 g_free (dirname);
862                 return GNOME_Evolution_Calendar_NoSuchCal;
863         }
864
865         g_free (dirname);
866
867         /* Create the new calendar information */
868         priv->icalcomp = e_cal_util_new_top_level ();
869
870         /* Create our internal data */
871         priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
872
873         priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
874
875         save (cbfile);
876
877         return GNOME_Evolution_Calendar_Success;
878 }
879
880 static char *
881 get_uri_string (ECalBackend *backend)
882 {
883         gchar *str_uri, *full_uri;
884
885         str_uri = get_uri_string_for_gnome_vfs (backend);
886         full_uri = gnome_vfs_unescape_string (str_uri, "");
887         g_free (str_uri);
888
889         return full_uri;
890 }
891
892 /* Open handler for the file backend */
893 static ECalBackendSyncStatus
894 e_cal_backend_file_open (ECalBackendSync *backend, EDataCal *cal, gboolean only_if_exists,
895                          const char *username, const char *password)
896 {
897         ECalBackendFile *cbfile;
898         ECalBackendFilePrivate *priv;
899         char *str_uri;
900         ECalBackendSyncStatus status;
901         
902         cbfile = E_CAL_BACKEND_FILE (backend);
903         priv = cbfile->priv;
904         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
905
906         /* Claim a succesful open if we are already open */
907         if (priv->uri && priv->comp_uid_hash) {
908                 status = GNOME_Evolution_Calendar_Success;
909                 goto done;
910         }
911         
912         str_uri = get_uri_string (E_CAL_BACKEND (backend));
913         if (!str_uri) {
914                 status = GNOME_Evolution_Calendar_OtherError;
915                 goto done;
916         }
917         
918         if (g_access (str_uri, R_OK) == 0) {
919                 status = open_cal (cbfile, str_uri);
920                 if (g_access (str_uri, W_OK) != 0)
921                         priv->read_only = TRUE;
922         } else {
923                 if (only_if_exists)
924                         status = GNOME_Evolution_Calendar_NoSuchCal;
925                 else
926                         status = create_cal (cbfile, str_uri);
927         }
928
929         if (status == GNOME_Evolution_Calendar_Success) {
930                 if (priv->default_zone) {
931                         icalcomponent *icalcomp = icaltimezone_get_component (priv->default_zone);
932
933                         icalcomponent_add_component (priv->icalcomp, icalcomponent_new_clone (icalcomp));
934                         save (cbfile);
935                 }
936         }
937
938         g_free (str_uri);
939
940   done:
941         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
942         return status;
943 }
944
945 static ECalBackendSyncStatus
946 e_cal_backend_file_remove (ECalBackendSync *backend, EDataCal *cal)
947 {
948         ECalBackendFile *cbfile;
949         ECalBackendFilePrivate *priv;
950         char *str_uri = NULL, *dirname = NULL;
951         char *full_path = NULL;
952         const char *fname;
953         GDir *dir = NULL;
954         GError *error = NULL;
955         ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;
956         
957         cbfile = E_CAL_BACKEND_FILE (backend);
958         priv = cbfile->priv;
959         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
960
961         str_uri = get_uri_string (E_CAL_BACKEND (backend));
962         if (!str_uri) {
963                 status = GNOME_Evolution_Calendar_OtherError;
964                 goto done;
965         }
966
967         if (g_access (str_uri, W_OK) != 0) {
968                 status = GNOME_Evolution_Calendar_PermissionDenied;
969                 goto done;
970         }
971
972         /* remove all files in the directory */
973         dirname = g_path_get_dirname (str_uri);
974         dir = g_dir_open (dirname, 0, &error);
975         if (!dir) {
976                 status = GNOME_Evolution_Calendar_PermissionDenied;
977                 goto done;
978         }
979
980         while ((fname = g_dir_read_name (dir))) {
981                 full_path = g_build_filename (dirname, fname, NULL);
982                 if (g_unlink (full_path) != 0) {
983                         status = GNOME_Evolution_Calendar_OtherError;
984                         goto done;
985                 }
986
987                 g_free (full_path);
988                 full_path = NULL;
989         }
990
991         /* remove the directory itself */
992         if (g_rmdir (dirname) != 0) {
993                 status = GNOME_Evolution_Calendar_OtherError;
994         }
995                 
996   done:
997         if (dir) {
998             g_dir_close (dir);
999         }
1000         g_free (str_uri);
1001         g_free (dirname);
1002         g_free (full_path);
1003
1004         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1005         return status;
1006 }
1007
1008 /* is_loaded handler for the file backend */
1009 static gboolean
1010 e_cal_backend_file_is_loaded (ECalBackend *backend)
1011 {
1012         ECalBackendFile *cbfile;
1013         ECalBackendFilePrivate *priv;
1014
1015         cbfile = E_CAL_BACKEND_FILE (backend);
1016         priv = cbfile->priv;
1017
1018         return (priv->icalcomp != NULL);
1019 }
1020
1021 /* is_remote handler for the file backend */
1022 static CalMode
1023 e_cal_backend_file_get_mode (ECalBackend *backend)
1024 {
1025         ECalBackendFile *cbfile;
1026         ECalBackendFilePrivate *priv;
1027
1028         cbfile = E_CAL_BACKEND_FILE (backend);
1029         priv = cbfile->priv;
1030
1031         return CAL_MODE_LOCAL;  
1032 }
1033
1034 /* Set_mode handler for the file backend */
1035 static void
1036 e_cal_backend_file_set_mode (ECalBackend *backend, CalMode mode)
1037 {
1038         e_cal_backend_notify_mode (backend,
1039                                    GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
1040                                    GNOME_Evolution_Calendar_MODE_LOCAL);
1041         
1042 }
1043
1044 static ECalBackendSyncStatus
1045 e_cal_backend_file_get_default_object (ECalBackendSync *backend, EDataCal *cal, char **object)
1046 {
1047         ECalComponent *comp;
1048         
1049         comp = e_cal_component_new ();
1050
1051         switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
1052         case ICAL_VEVENT_COMPONENT:
1053                 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
1054                 break;
1055         case ICAL_VTODO_COMPONENT:
1056                 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
1057                 break;
1058         case ICAL_VJOURNAL_COMPONENT:
1059                 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
1060                 break;
1061         default:
1062                 g_object_unref (comp);
1063                 return GNOME_Evolution_Calendar_ObjectNotFound;
1064         }
1065         
1066         *object = e_cal_component_get_as_string (comp);
1067         g_object_unref (comp);
1068  
1069         return GNOME_Evolution_Calendar_Success;
1070 }
1071
1072 static void
1073 add_detached_recur_to_vcalendar (gpointer key, gpointer value, gpointer user_data)
1074 {
1075         ECalComponent *recurrence = value;
1076         icalcomponent *vcalendar = user_data;
1077
1078         icalcomponent_add_component (
1079                 vcalendar,
1080                 icalcomponent_new_clone (e_cal_component_get_icalcomponent (recurrence)));                   
1081 }
1082
1083 /* Get_object_component handler for the file backend */
1084 static ECalBackendSyncStatus
1085 e_cal_backend_file_get_object (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *rid, char **object)
1086 {
1087         ECalBackendFile *cbfile;
1088         ECalBackendFilePrivate *priv;
1089         ECalBackendFileObject *obj_data;
1090
1091         cbfile = E_CAL_BACKEND_FILE (backend);
1092         priv = cbfile->priv;
1093
1094         g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
1095         g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1096         g_assert (priv->comp_uid_hash != NULL);
1097
1098         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1099
1100         obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1101         if (!obj_data) {
1102                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1103                 return GNOME_Evolution_Calendar_ObjectNotFound;
1104         }
1105
1106         if (rid && *rid) {
1107                 ECalComponent *comp;
1108
1109                 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1110                 if (comp) {
1111                         *object = e_cal_component_get_as_string (comp);
1112                 } else {
1113                         icalcomponent *icalcomp;
1114                         struct icaltimetype itt;
1115
1116                         itt = icaltime_from_string (rid);
1117                         icalcomp = e_cal_util_construct_instance (
1118                                 e_cal_component_get_icalcomponent (obj_data->full_object),
1119                                 itt);
1120                         if (!icalcomp) {
1121                                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1122                                 return GNOME_Evolution_Calendar_ObjectNotFound;
1123                         }
1124
1125                         *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1126
1127                         icalcomponent_free (icalcomp);
1128                 }
1129         } else {
1130                 if (g_hash_table_size (obj_data->recurrences) > 0) {
1131                         icalcomponent *icalcomp;
1132
1133                         /* if we have detached recurrences, return a VCALENDAR */
1134                         icalcomp = e_cal_util_new_top_level ();
1135                         icalcomponent_add_component (
1136                                 icalcomp,
1137                                 icalcomponent_new_clone (e_cal_component_get_icalcomponent (obj_data->full_object)));
1138
1139                         /* add all detached recurrences */
1140                         g_hash_table_foreach (obj_data->recurrences, (GHFunc) add_detached_recur_to_vcalendar, icalcomp);
1141
1142                         *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1143
1144                         icalcomponent_free (icalcomp);
1145                 } else
1146                         *object = e_cal_component_get_as_string (obj_data->full_object);
1147         }
1148
1149         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1150         return GNOME_Evolution_Calendar_Success;
1151 }
1152
1153 /* Get_timezone_object handler for the file backend */
1154 static ECalBackendSyncStatus
1155 e_cal_backend_file_get_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid, char **object)
1156 {
1157         ECalBackendFile *cbfile;
1158         ECalBackendFilePrivate *priv;
1159         icaltimezone *zone;
1160         icalcomponent *icalcomp;
1161
1162         cbfile = E_CAL_BACKEND_FILE (backend);
1163         priv = cbfile->priv;
1164
1165         g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1166         g_return_val_if_fail (tzid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1167
1168         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1169
1170         if (!strcmp (tzid, "UTC")) {
1171                 zone = icaltimezone_get_utc_timezone ();
1172         } else {
1173                 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1174                 if (!zone) {
1175                         zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1176                         if (!zone) {
1177                                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1178                                 return GNOME_Evolution_Calendar_ObjectNotFound;
1179                         }
1180                 }
1181         }
1182         
1183         icalcomp = icaltimezone_get_component (zone);
1184         if (!icalcomp) {
1185                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1186                 return GNOME_Evolution_Calendar_InvalidObject;
1187         }
1188
1189         *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1190
1191         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1192         return GNOME_Evolution_Calendar_Success;
1193 }
1194
1195 /* Add_timezone handler for the file backend */
1196 static ECalBackendSyncStatus
1197 e_cal_backend_file_add_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj)
1198 {
1199         icalcomponent *tz_comp;
1200         ECalBackendFile *cbfile;
1201         ECalBackendFilePrivate *priv;
1202
1203         cbfile = (ECalBackendFile *) backend;
1204
1205         g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), GNOME_Evolution_Calendar_OtherError);
1206         g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
1207
1208         priv = cbfile->priv;
1209
1210         tz_comp = icalparser_parse_string (tzobj);
1211         if (!tz_comp)
1212                 return GNOME_Evolution_Calendar_InvalidObject;
1213
1214         if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
1215                 icaltimezone *zone;
1216
1217                 zone = icaltimezone_new ();
1218                 icaltimezone_set_component (zone, tz_comp);
1219
1220                 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1221                 if (!icalcomponent_get_timezone (priv->icalcomp,
1222                                                  icaltimezone_get_tzid (zone))) {
1223                         icalcomponent_add_component (priv->icalcomp, tz_comp);
1224                         save (cbfile);
1225                 }
1226                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1227
1228                 icaltimezone_free (zone, 1);
1229         }
1230
1231         return GNOME_Evolution_Calendar_Success;
1232 }
1233
1234 static ECalBackendSyncStatus
1235 e_cal_backend_file_set_default_zone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj)
1236 {
1237         icalcomponent *tz_comp;
1238         ECalBackendFile *cbfile;
1239         ECalBackendFilePrivate *priv;
1240         icaltimezone *zone;
1241
1242         cbfile = (ECalBackendFile *) backend;
1243
1244         g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), GNOME_Evolution_Calendar_OtherError);
1245         g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
1246
1247         priv = cbfile->priv;
1248
1249         tz_comp = icalparser_parse_string (tzobj);
1250         if (!tz_comp)
1251                 return GNOME_Evolution_Calendar_InvalidObject;
1252
1253         zone = icaltimezone_new ();
1254         icaltimezone_set_component (zone, tz_comp);
1255
1256         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1257         if (priv->default_zone != icaltimezone_get_utc_timezone ())
1258                 icaltimezone_free (priv->default_zone, 1);
1259
1260         /* Set the default timezone to it. */
1261         priv->default_zone = zone;
1262         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1263
1264         return GNOME_Evolution_Calendar_Success;
1265 }
1266
1267 typedef struct {
1268         GList *obj_list;
1269         gboolean search_needed;
1270         const char *query;
1271         ECalBackendSExp *obj_sexp;
1272         ECalBackend *backend;
1273         icaltimezone *default_zone;
1274 } MatchObjectData;
1275
1276 static void
1277 match_recurrence_sexp (gpointer key, gpointer value, gpointer data)
1278 {
1279         ECalComponent *comp = value;
1280         MatchObjectData *match_data = data;
1281
1282         if ((!match_data->search_needed) ||
1283             (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
1284                 match_data->obj_list = g_list_append (match_data->obj_list,
1285                                                       e_cal_component_get_as_string (comp));
1286         }
1287 }
1288
1289 static void
1290 match_object_sexp (gpointer key, gpointer value, gpointer data)
1291 {
1292         ECalBackendFileObject *obj_data = value;
1293         MatchObjectData *match_data = data;
1294
1295         if (obj_data->full_object) {
1296                 if ((!match_data->search_needed) ||
1297                     (e_cal_backend_sexp_match_comp (match_data->obj_sexp, obj_data->full_object, match_data->backend))) {
1298                         match_data->obj_list = g_list_append (match_data->obj_list,
1299                                                               e_cal_component_get_as_string (obj_data->full_object));
1300                 }
1301         }
1302
1303         /* match also recurrences */
1304         g_hash_table_foreach (obj_data->recurrences,
1305                               (GHFunc) match_recurrence_sexp,
1306                               match_data);
1307 }
1308
1309 /* Get_objects_in_range handler for the file backend */
1310 static ECalBackendSyncStatus
1311 e_cal_backend_file_get_object_list (ECalBackendSync *backend, EDataCal *cal, const char *sexp, GList **objects)
1312 {
1313         ECalBackendFile *cbfile;
1314         ECalBackendFilePrivate *priv;
1315         MatchObjectData match_data;
1316
1317         cbfile = E_CAL_BACKEND_FILE (backend);
1318         priv = cbfile->priv;
1319
1320         d(g_message (G_STRLOC ": Getting object list (%s)", sexp));
1321
1322         match_data.search_needed = TRUE;
1323         match_data.query = sexp;
1324         match_data.obj_list = NULL;
1325         match_data.backend = E_CAL_BACKEND (backend);
1326         match_data.default_zone = priv->default_zone;
1327
1328         if (!strcmp (sexp, "#t"))
1329                 match_data.search_needed = FALSE;
1330
1331         match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1332         if (!match_data.obj_sexp)
1333                 return GNOME_Evolution_Calendar_InvalidQuery;
1334
1335         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1336         g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1337         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1338
1339         *objects = match_data.obj_list;
1340
1341         g_object_unref (match_data.obj_sexp);
1342         
1343         return GNOME_Evolution_Calendar_Success;        
1344 }
1345
1346 /* Gets the list of attachments */
1347 static ECalBackendSyncStatus
1348 e_cal_backend_file_get_attachment_list (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *rid, GSList **list) 
1349 {
1350          
1351         /* TODO implement the function */
1352         return GNOME_Evolution_Calendar_Success;
1353 }
1354
1355 /* get_query handler for the file backend */
1356 static void
1357 e_cal_backend_file_start_query (ECalBackend *backend, EDataCalView *query)
1358 {
1359         ECalBackendFile *cbfile;
1360         ECalBackendFilePrivate *priv;
1361         MatchObjectData match_data;
1362
1363         cbfile = E_CAL_BACKEND_FILE (backend);
1364         priv = cbfile->priv;
1365
1366         d(g_message (G_STRLOC ": Starting query (%s)", e_data_cal_view_get_text (query)));
1367
1368         /* try to match all currently existing objects */
1369         match_data.search_needed = TRUE;
1370         match_data.query = e_data_cal_view_get_text (query);
1371         match_data.obj_list = NULL;
1372         match_data.backend = backend;
1373         match_data.default_zone = priv->default_zone;
1374
1375         if (!strcmp (match_data.query, "#t"))
1376                 match_data.search_needed = FALSE;
1377
1378         match_data.obj_sexp = e_data_cal_view_get_object_sexp (query);
1379         if (!match_data.obj_sexp) {
1380                 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_InvalidQuery);
1381                 return;
1382         }
1383
1384         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1385         g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1386         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1387
1388         /* notify listeners of all objects */
1389         if (match_data.obj_list) {
1390                 e_data_cal_view_notify_objects_added (query, (const GList *) match_data.obj_list);
1391
1392                 /* free memory */
1393                 g_list_foreach (match_data.obj_list, (GFunc) g_free, NULL);
1394                 g_list_free (match_data.obj_list);
1395         }
1396         g_object_unref (match_data.obj_sexp);
1397
1398         e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
1399 }
1400
1401 static gboolean
1402 free_busy_instance (ECalComponent *comp,
1403                     time_t        instance_start,
1404                     time_t        instance_end,
1405                     gpointer      data)
1406 {
1407         icalcomponent *vfb = data;
1408         icalproperty *prop;
1409         icalparameter *param;
1410         struct icalperiodtype ipt;
1411         icaltimezone *utc_zone;
1412
1413         utc_zone = icaltimezone_get_utc_timezone ();
1414
1415         ipt.start = icaltime_from_timet_with_zone (instance_start, FALSE, utc_zone);
1416         ipt.end = icaltime_from_timet_with_zone (instance_end, FALSE, utc_zone);
1417         ipt.duration = icaldurationtype_null_duration ();
1418         
1419         /* add busy information to the vfb component */
1420         prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
1421         icalproperty_set_freebusy (prop, ipt);
1422         
1423         param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
1424         icalproperty_add_parameter (prop, param);
1425         
1426         icalcomponent_add_property (vfb, prop);
1427
1428         return TRUE;
1429 }
1430
1431 static icalcomponent *
1432 create_user_free_busy (ECalBackendFile *cbfile, const char *address, const char *cn,
1433                        time_t start, time_t end)
1434 {       
1435         ECalBackendFilePrivate *priv;
1436         GList *l;
1437         icalcomponent *vfb;
1438         icaltimezone *utc_zone;
1439         ECalBackendSExp *obj_sexp;
1440         char *query, *iso_start, *iso_end;
1441         
1442         priv = cbfile->priv;
1443
1444         /* create the (unique) VFREEBUSY object that we'll return */
1445         vfb = icalcomponent_new_vfreebusy ();
1446         if (address != NULL) {
1447                 icalproperty *prop;
1448                 icalparameter *param;
1449                 
1450                 prop = icalproperty_new_organizer (address);
1451                 if (prop != NULL && cn != NULL) {
1452                         param = icalparameter_new_cn (cn);
1453                         icalproperty_add_parameter (prop, param);                       
1454                 }
1455                 if (prop != NULL)
1456                         icalcomponent_add_property (vfb, prop);         
1457         }
1458         utc_zone = icaltimezone_get_utc_timezone ();
1459         icalcomponent_set_dtstart (vfb, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
1460         icalcomponent_set_dtend (vfb, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
1461
1462         /* add all objects in the given interval */
1463         iso_start = isodate_from_time_t (start);
1464         iso_end = isodate_from_time_t (end);
1465         query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") (make-time \"%s\")",
1466                                  iso_start, iso_end);
1467         obj_sexp = e_cal_backend_sexp_new (query);
1468         g_free (query);
1469         g_free (iso_start);
1470         g_free (iso_end);
1471
1472         if (!obj_sexp)
1473                 return vfb;
1474
1475         for (l = priv->comp; l; l = l->next) {
1476                 ECalComponent *comp = l->data;
1477                 icalcomponent *icalcomp, *vcalendar_comp;
1478                 icalproperty *prop;
1479                 
1480                 icalcomp = e_cal_component_get_icalcomponent (comp);
1481                 if (!icalcomp)
1482                         continue;
1483
1484                 /* If the event is TRANSPARENT, skip it. */
1485                 prop = icalcomponent_get_first_property (icalcomp,
1486                                                          ICAL_TRANSP_PROPERTY);
1487                 if (prop) {
1488                         icalproperty_transp transp_val = icalproperty_get_transp (prop);
1489                         if (transp_val == ICAL_TRANSP_TRANSPARENT ||
1490                             transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
1491                                 continue;
1492                 }
1493         
1494                 if (!e_cal_backend_sexp_match_comp (obj_sexp, l->data, E_CAL_BACKEND (cbfile)))
1495                         continue;
1496                 
1497                 vcalendar_comp = icalcomponent_get_parent (icalcomp);
1498                 e_cal_recur_generate_instances (comp, start, end,
1499                                                 free_busy_instance,
1500                                                 vfb,
1501                                                 resolve_tzid,
1502                                                 vcalendar_comp,
1503                                                 priv->default_zone);
1504         }
1505         g_object_unref (obj_sexp);
1506
1507         return vfb;     
1508 }
1509
1510 /* Get_free_busy handler for the file backend */
1511 static ECalBackendSyncStatus
1512 e_cal_backend_file_get_free_busy (ECalBackendSync *backend, EDataCal *cal, GList *users,
1513                                 time_t start, time_t end, GList **freebusy)
1514 {
1515         ECalBackendFile *cbfile;
1516         ECalBackendFilePrivate *priv;
1517         gchar *address, *name;  
1518         icalcomponent *vfb;
1519         char *calobj;
1520         GList *l;
1521
1522         cbfile = E_CAL_BACKEND_FILE (backend);
1523         priv = cbfile->priv;
1524
1525         g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1526         g_return_val_if_fail (start != -1 && end != -1, GNOME_Evolution_Calendar_InvalidRange);
1527         g_return_val_if_fail (start <= end, GNOME_Evolution_Calendar_InvalidRange);
1528
1529         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1530
1531         *freebusy = NULL;
1532         
1533         if (users == NULL) {
1534                 if (e_cal_backend_mail_account_get_default (&address, &name)) {
1535                         vfb = create_user_free_busy (cbfile, address, name, start, end);
1536                         calobj = icalcomponent_as_ical_string (vfb);
1537                         *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1538                         icalcomponent_free (vfb);
1539                         g_free (address);
1540                         g_free (name);
1541                 }               
1542         } else {
1543                 for (l = users; l != NULL; l = l->next ) {
1544                         address = l->data;                      
1545                         if (e_cal_backend_mail_account_is_valid (address, &name)) {
1546                                 vfb = create_user_free_busy (cbfile, address, name, start, end);
1547                                 calobj = icalcomponent_as_ical_string (vfb);
1548                                 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1549                                 icalcomponent_free (vfb);
1550                                 g_free (name);
1551                         }
1552                 }               
1553         }
1554
1555         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1556
1557         return GNOME_Evolution_Calendar_Success;
1558 }
1559
1560 typedef struct 
1561 {
1562         ECalBackendFile *backend;
1563         icalcomponent_kind kind;
1564         GList *deletes;
1565         EXmlHash *ehash;
1566 } ECalBackendFileComputeChangesData;
1567
1568 static gboolean
1569 e_cal_backend_file_compute_changes_foreach_key (const char *key, gpointer value, gpointer data)
1570 {
1571         ECalBackendFileComputeChangesData *be_data = data;
1572         
1573         if (!lookup_component (be_data->backend, key)) {
1574                 ECalComponent *comp;
1575
1576                 comp = e_cal_component_new ();
1577                 if (be_data->kind == ICAL_VTODO_COMPONENT)
1578                         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
1579                 else
1580                         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
1581
1582                 e_cal_component_set_uid (comp, key);
1583                 be_data->deletes = g_list_prepend (be_data->deletes, e_cal_component_get_as_string (comp));
1584
1585                 g_object_unref (comp);
1586                 return TRUE;
1587         }
1588         return FALSE;
1589 }
1590
1591 static ECalBackendSyncStatus
1592 e_cal_backend_file_compute_changes (ECalBackendFile *cbfile, const char *change_id,
1593                                     GList **adds, GList **modifies, GList **deletes)
1594 {
1595         ECalBackendFilePrivate *priv;
1596         char    *filename;
1597         EXmlHash *ehash;
1598         ECalBackendFileComputeChangesData be_data;
1599         GList *i;
1600         gchar *unescaped_uri;
1601
1602         priv = cbfile->priv;
1603
1604
1605         /* FIXME Will this always work? */
1606         unescaped_uri = gnome_vfs_unescape_string (priv->uri, "");
1607         filename = g_strdup_printf ("%s-%s.db", unescaped_uri, change_id);
1608         g_free (unescaped_uri);
1609         if (!(ehash = e_xmlhash_new (filename))) {
1610                 g_free (filename);
1611                 return GNOME_Evolution_Calendar_OtherError;
1612         }
1613         
1614         g_free (filename);
1615         
1616         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1617
1618         /* Calculate adds and modifies */
1619         for (i = priv->comp; i != NULL; i = i->next) {
1620                 const char *uid;
1621                 char *calobj;
1622
1623                 e_cal_component_get_uid (i->data, &uid);
1624                 calobj = e_cal_component_get_as_string (i->data);
1625
1626                 g_assert (calobj != NULL);
1627
1628                 /* check what type of change has occurred, if any */
1629                 switch (e_xmlhash_compare (ehash, uid, calobj)) {
1630                 case E_XMLHASH_STATUS_SAME:
1631                         break;
1632                 case E_XMLHASH_STATUS_NOT_FOUND:
1633                         *adds = g_list_prepend (*adds, g_strdup (calobj));
1634                         e_xmlhash_add (ehash, uid, calobj);
1635                         break;
1636                 case E_XMLHASH_STATUS_DIFFERENT:
1637                         *modifies = g_list_prepend (*modifies, g_strdup (calobj));
1638                         e_xmlhash_add (ehash, uid, calobj);
1639                         break;
1640                 }
1641
1642                 g_free (calobj);
1643         }
1644
1645         /* Calculate deletions */
1646         be_data.backend = cbfile;
1647         be_data.kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbfile));
1648         be_data.deletes = NULL;
1649         be_data.ehash = ehash;
1650         
1651         e_xmlhash_foreach_key_remove (ehash, (EXmlHashRemoveFunc)e_cal_backend_file_compute_changes_foreach_key, &be_data);
1652         
1653         *deletes = be_data.deletes;
1654
1655         e_xmlhash_write (ehash);
1656         e_xmlhash_destroy (ehash);
1657         
1658         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1659         return GNOME_Evolution_Calendar_Success;
1660 }
1661
1662 /* Get_changes handler for the file backend */
1663 static ECalBackendSyncStatus
1664 e_cal_backend_file_get_changes (ECalBackendSync *backend, EDataCal *cal, const char *change_id,
1665                               GList **adds, GList **modifies, GList **deletes)
1666 {
1667         ECalBackendFile *cbfile;
1668         ECalBackendFilePrivate *priv;
1669
1670         cbfile = E_CAL_BACKEND_FILE (backend);
1671         priv = cbfile->priv;
1672
1673         g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1674         g_return_val_if_fail (change_id != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1675
1676         return e_cal_backend_file_compute_changes (cbfile, change_id, adds, modifies, deletes);
1677 }
1678
1679 /* Discard_alarm handler for the file backend */
1680 static ECalBackendSyncStatus
1681 e_cal_backend_file_discard_alarm (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *auid)
1682 {
1683         /* we just do nothing with the alarm */
1684         return GNOME_Evolution_Calendar_Success;
1685 }
1686
1687 static icaltimezone *
1688 e_cal_backend_file_internal_get_default_timezone (ECalBackend *backend)
1689 {
1690         ECalBackendFile *cbfile;
1691         ECalBackendFilePrivate *priv;
1692
1693         cbfile = E_CAL_BACKEND_FILE (backend);
1694         priv = cbfile->priv;
1695
1696         g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1697
1698         return priv->default_zone;
1699 }
1700
1701 static icaltimezone *
1702 e_cal_backend_file_internal_get_timezone (ECalBackend *backend, const char *tzid)
1703 {
1704         ECalBackendFile *cbfile;
1705         ECalBackendFilePrivate *priv;
1706         icaltimezone *zone;
1707
1708         cbfile = E_CAL_BACKEND_FILE (backend);
1709         priv = cbfile->priv;
1710
1711         g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1712
1713         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1714
1715         if (!strcmp (tzid, "UTC"))
1716                 zone = icaltimezone_get_utc_timezone ();
1717         else {
1718                 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1719                 if (!zone)
1720                         zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1721         }
1722
1723         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1724         return zone;
1725 }
1726
1727 static void
1728 sanitize_component (ECalBackendFile *cbfile, ECalComponent *comp)
1729 {
1730         ECalComponentDateTime dt;
1731         icaltimezone *zone, *default_zone;
1732
1733         /* Check dtstart, dtend and due's timezone, and convert it to local 
1734          * default timezone if the timezone is not in our builtin timezone
1735          * list */
1736         e_cal_component_get_dtstart (comp, &dt);
1737         if (dt.value && dt.tzid) {
1738                 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1739                 if (!zone) {
1740                         default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1741                         g_free ((char *)dt.tzid);
1742                         dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1743                         e_cal_component_set_dtstart (comp, &dt);
1744                 }
1745         }
1746         e_cal_component_free_datetime (&dt);
1747
1748         e_cal_component_get_dtend (comp, &dt);
1749         if (dt.value && dt.tzid) {
1750                 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1751                 if (!zone) {
1752                         default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1753                         g_free ((char *)dt.tzid);
1754                         dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1755                         e_cal_component_set_dtend (comp, &dt);
1756                 }
1757         }
1758         e_cal_component_free_datetime (&dt);
1759          
1760         e_cal_component_get_due (comp, &dt);
1761         if (dt.value && dt.tzid) {
1762                 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1763                 if (!zone) {
1764                         default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1765                         g_free ((char *)dt.tzid);
1766                         dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1767                         e_cal_component_set_due (comp, &dt);
1768                 }
1769         }
1770         e_cal_component_free_datetime (&dt);
1771         e_cal_component_abort_sequence (comp);
1772
1773 }       
1774
1775 static ECalBackendSyncStatus
1776 e_cal_backend_file_create_object (ECalBackendSync *backend, EDataCal *cal, char **calobj, char **uid)
1777 {
1778         ECalBackendFile *cbfile;
1779         ECalBackendFilePrivate *priv;
1780         icalcomponent *icalcomp;
1781         ECalComponent *comp;
1782         const char *comp_uid;
1783         struct icaltimetype current;
1784         
1785         cbfile = E_CAL_BACKEND_FILE (backend);
1786         priv = cbfile->priv;
1787
1788         g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1789         g_return_val_if_fail (*calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1790
1791         /* Parse the icalendar text */
1792         icalcomp = icalparser_parse_string (*calobj);
1793         if (!icalcomp)
1794                 return GNOME_Evolution_Calendar_InvalidObject;
1795
1796         /* Check kind with the parent */
1797         if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
1798                 icalcomponent_free (icalcomp);
1799                 return GNOME_Evolution_Calendar_InvalidObject;
1800         }
1801
1802         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1803
1804         /* Get the UID */
1805         comp_uid = icalcomponent_get_uid (icalcomp);
1806         if (!comp_uid) {
1807                 char *new_uid;
1808
1809                 new_uid = e_cal_component_gen_uid ();
1810                 if (!new_uid) {
1811                         icalcomponent_free (icalcomp);
1812                         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1813                         return GNOME_Evolution_Calendar_InvalidObject;
1814                 }
1815
1816                 icalcomponent_set_uid (icalcomp, new_uid);
1817                 comp_uid = icalcomponent_get_uid (icalcomp);
1818
1819                 g_free (new_uid);
1820         }
1821
1822         /* check the object is not in our cache */
1823         if (lookup_component (cbfile, comp_uid)) {
1824                 icalcomponent_free (icalcomp);
1825                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1826                 return GNOME_Evolution_Calendar_ObjectIdAlreadyExists;
1827         }
1828
1829         /* Create the cal component */
1830         comp = e_cal_component_new ();
1831         e_cal_component_set_icalcomponent (comp, icalcomp);
1832
1833         /* Set the created and last modified times on the component */
1834         current = icaltime_from_timet (time (NULL), 0);
1835         e_cal_component_set_created (comp, &current);
1836         e_cal_component_set_last_modified (comp, &current);
1837
1838         /* sanitize the component*/
1839         sanitize_component (cbfile, comp);
1840
1841         /* Add the object */
1842         add_component (cbfile, comp, TRUE);
1843
1844         /* Save the file */
1845         save (cbfile);
1846
1847         /* Return the UID and the modified component */
1848         if (uid)
1849                 *uid = g_strdup (comp_uid);
1850         *calobj = e_cal_component_get_as_string (comp);
1851
1852         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1853         return GNOME_Evolution_Calendar_Success;
1854 }
1855
1856 typedef struct {
1857         ECalBackendFile *cbfile;
1858         ECalBackendFileObject *obj_data;
1859         const char *rid;
1860         CalObjModType mod;
1861 } RemoveRecurrenceData;
1862
1863 static gboolean
1864 remove_object_instance_cb (gpointer key, gpointer value, gpointer user_data)
1865 {
1866         time_t fromtt, instancett;
1867         ECalComponent *instance = value;
1868         RemoveRecurrenceData *rrdata = user_data;
1869
1870         fromtt = icaltime_as_timet (icaltime_from_string (rrdata->rid));
1871         instancett = icaltime_as_timet (get_rid_icaltime (instance));
1872
1873         if (fromtt > 0 && instancett > 0) {
1874                 if ((rrdata->mod == CALOBJ_MOD_THISANDPRIOR && instancett <= fromtt) ||
1875                     (rrdata->mod == CALOBJ_MOD_THISANDFUTURE && instancett >= fromtt)) {
1876                         /* remove the component from our data */
1877                         icalcomponent_remove_component (rrdata->cbfile->priv->icalcomp,
1878                                                         e_cal_component_get_icalcomponent (instance));
1879                         rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
1880
1881                         rrdata->obj_data->recurrences_list = g_list_remove (rrdata->obj_data->recurrences_list, instance);
1882
1883                         return TRUE;
1884                 }
1885         }
1886
1887         return FALSE;
1888 }
1889
1890 static ECalBackendSyncStatus
1891 e_cal_backend_file_modify_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj, 
1892                                   CalObjModType mod, char **old_object, char **new_object)
1893 {
1894         RemoveRecurrenceData rrdata;
1895         ECalBackendFile *cbfile;
1896         ECalBackendFilePrivate *priv;
1897         icalcomponent *icalcomp;
1898         const char *comp_uid, *rid = NULL;
1899         char *real_rid;
1900         ECalComponent *comp, *recurrence;
1901         ECalBackendFileObject *obj_data;
1902         struct icaltimetype current;
1903
1904         cbfile = E_CAL_BACKEND_FILE (backend);
1905         priv = cbfile->priv;
1906                 
1907         g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1908         g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1909
1910         /* Parse the icalendar text */
1911         icalcomp = icalparser_parse_string ((char *) calobj);
1912         if (!icalcomp)
1913                 return GNOME_Evolution_Calendar_InvalidObject;
1914
1915         /* Check kind with the parent */
1916         if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
1917                 icalcomponent_free (icalcomp);
1918                 return GNOME_Evolution_Calendar_InvalidObject;
1919         }
1920
1921         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1922
1923         /* Get the uid */
1924         comp_uid = icalcomponent_get_uid (icalcomp);
1925
1926         /* Get the object from our cache */
1927         if (!(obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid))) {
1928                 icalcomponent_free (icalcomp);
1929                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1930                 return GNOME_Evolution_Calendar_ObjectNotFound;
1931         }
1932
1933         /* Create the cal component */
1934         comp = e_cal_component_new ();
1935         e_cal_component_set_icalcomponent (comp, icalcomp);
1936
1937         /* Set the last modified time on the component */
1938         current = icaltime_from_timet (time (NULL), 0);
1939         e_cal_component_set_last_modified (comp, &current);
1940
1941         /* sanitize the component*/
1942         sanitize_component (cbfile, comp);
1943         rid = e_cal_component_get_recurid_as_string (comp);
1944
1945         /* handle mod_type */
1946         switch (mod) {
1947         case CALOBJ_MOD_THIS :
1948                 if (!rid || !*rid) {
1949                         if (old_object)
1950                                 *old_object = e_cal_component_get_as_string (obj_data->full_object);
1951
1952                         /* replace only the full object */
1953                         icalcomponent_remove_component (priv->icalcomp,
1954                                                         e_cal_component_get_icalcomponent (obj_data->full_object));
1955                         priv->comp = g_list_remove (priv->comp, obj_data->full_object);
1956
1957                         /* add the new object */
1958                         g_object_unref (obj_data->full_object);
1959                         obj_data->full_object = comp;
1960
1961                         icalcomponent_add_component (priv->icalcomp,
1962                                                      e_cal_component_get_icalcomponent (obj_data->full_object));
1963                         priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
1964
1965                         save (cbfile);
1966
1967                         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1968                         return GNOME_Evolution_Calendar_Success;
1969                 }
1970
1971                 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (void **)&real_rid, (void **)&recurrence)) {
1972                         if (old_object)
1973                                 *old_object = e_cal_component_get_as_string (recurrence);
1974
1975                         /* remove the component from our data */
1976                         icalcomponent_remove_component (priv->icalcomp,
1977                                                         e_cal_component_get_icalcomponent (recurrence));
1978                         priv->comp = g_list_remove (priv->comp, recurrence);
1979                         obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
1980                         g_hash_table_remove (obj_data->recurrences, rid);
1981                 }
1982
1983                 /* add the detached instance */
1984                 g_hash_table_insert (obj_data->recurrences, 
1985                                      g_strdup (rid),
1986                                      comp);
1987                 icalcomponent_add_component (priv->icalcomp,
1988                                              e_cal_component_get_icalcomponent (comp));
1989                 priv->comp = g_list_append (priv->comp, comp);
1990                 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
1991                 break;
1992         case CALOBJ_MOD_THISANDPRIOR :
1993         case CALOBJ_MOD_THISANDFUTURE :
1994                 rid = e_cal_component_get_recurid_as_string (comp);
1995                 if (!rid || !*rid) {
1996                         if (old_object)
1997                                 *old_object = e_cal_component_get_as_string (obj_data->full_object);
1998
1999                         remove_component (cbfile, comp_uid, obj_data);
2000
2001                         /* Add the new object */
2002                         add_component (cbfile, comp, TRUE);
2003                         break;
2004                 }
2005
2006                 /* remove the component from our data, temporarily */
2007                 icalcomponent_remove_component (priv->icalcomp,
2008                                                 e_cal_component_get_icalcomponent (obj_data->full_object));
2009                 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2010
2011                 /* now deal with the detached recurrence */
2012                 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
2013                                                   (void **)&real_rid, (void **)&recurrence)) {
2014                         if (old_object)
2015                                 *old_object = e_cal_component_get_as_string (recurrence);
2016
2017                         /* remove the component from our data */
2018                         icalcomponent_remove_component (priv->icalcomp,
2019                                                         e_cal_component_get_icalcomponent (recurrence));
2020                         priv->comp = g_list_remove (priv->comp, recurrence);
2021                         obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2022                         g_hash_table_remove (obj_data->recurrences, rid);
2023                 } else {
2024                         if (old_object)
2025                                 *old_object = e_cal_component_get_as_string (obj_data->full_object);
2026                 }
2027
2028                 rrdata.cbfile = cbfile;
2029                 rrdata.obj_data = obj_data;
2030                 rrdata.rid = rid;
2031                 rrdata.mod = mod;
2032                 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2033
2034                 /* add the modified object to the beginning of the list,
2035                    so that it's always before any detached instance we
2036                    might have */
2037                 icalcomponent_add_component (priv->icalcomp,
2038                                              e_cal_component_get_icalcomponent (obj_data->full_object));
2039                 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2040
2041                 /* add the new detached recurrence */
2042                 g_hash_table_insert (obj_data->recurrences, 
2043                                      g_strdup (e_cal_component_get_recurid_as_string (comp)),
2044                                      comp);
2045                 icalcomponent_add_component (priv->icalcomp,
2046                                              e_cal_component_get_icalcomponent (comp));
2047                 priv->comp = g_list_append (priv->comp, comp);
2048                 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
2049                 break;
2050         case CALOBJ_MOD_ALL :
2051                 /* in this case, we blow away all recurrences, and start over
2052                    with a clean component */
2053
2054                 if (e_cal_util_component_has_recurrences (icalcomp) && rid && *rid) {
2055                         icaltimetype start, recur = icaltime_from_string (rid);
2056
2057                         start = icalcomponent_get_dtstart (icalcomp);
2058
2059                         /* This means its a instance generated from master object. So replace 
2060                             the dates stored dates from the master object */
2061                         if (!recur.zone)   
2062                                 recur.zone = start.zone;
2063                         
2064                         if (icaltime_compare_date_only (start, recur) == 0) {
2065                                 ECalComponentDateTime m_sdate, m_endate;
2066
2067                                 e_cal_component_get_dtstart (obj_data->full_object, &m_sdate);
2068                                 e_cal_component_get_dtend (obj_data->full_object, &m_endate);
2069
2070                                 if (icaltime_compare (start, recur) != 0) {
2071                                         icaltimetype end = icalcomponent_get_dtend (icalcomp);
2072                                         
2073                                         m_sdate.value->hour = start.hour;
2074                                         m_sdate.value->minute = start.minute;
2075                                         m_sdate.value->second = 
2076                                                 
2077                                         m_endate.value->hour = end.hour;
2078                                         m_endate.value->minute = end.minute;
2079                                         m_endate.value->second = end.second;
2080                                 }
2081
2082                                 e_cal_component_set_dtstart (comp, &m_sdate);
2083                                 e_cal_component_set_dtend (comp, &m_endate);
2084                                 e_cal_component_set_recurid (comp, NULL);
2085                                 e_cal_component_commit_sequence (comp);
2086                         } 
2087                         e_cal_component_set_recurid (comp, NULL);
2088                         *new_object = e_cal_component_get_as_string (comp);
2089                 }
2090                 
2091                 /* Remove the old version */
2092                 if (old_object)
2093                         *old_object = e_cal_component_get_as_string (obj_data->full_object);
2094
2095                 remove_component (cbfile, comp_uid, obj_data);
2096
2097                 /* Add the new object */
2098                 add_component (cbfile, comp, TRUE);
2099                 break;
2100         }
2101
2102         save (cbfile);
2103
2104         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2105         return GNOME_Evolution_Calendar_Success;
2106 }
2107
2108 static void
2109 remove_instance (ECalBackendFile *cbfile, ECalBackendFileObject *obj_data, const char *rid)
2110 {
2111         char *hash_rid;
2112         ECalComponent *comp;
2113
2114         if (!rid || !*rid)
2115                 return;
2116
2117         if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (void **)&hash_rid, (void **)&comp)) {
2118                 /* remove the component from our data */
2119                 icalcomponent_remove_component (cbfile->priv->icalcomp,
2120                                                 e_cal_component_get_icalcomponent (comp));
2121                 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
2122                 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp);
2123                 g_hash_table_remove (obj_data->recurrences, rid);
2124         }
2125
2126         /* remove the component from our data, temporarily */
2127         icalcomponent_remove_component (cbfile->priv->icalcomp,
2128                                         e_cal_component_get_icalcomponent (obj_data->full_object));
2129         cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
2130
2131         e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
2132                                      icaltime_from_string (rid), CALOBJ_MOD_THIS);
2133
2134         /* add the modified object to the beginning of the list, 
2135            so that it's always before any detached instance we
2136            might have */
2137         icalcomponent_add_component (cbfile->priv->icalcomp,
2138                                      e_cal_component_get_icalcomponent (obj_data->full_object));
2139         cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
2140 }
2141
2142 static char *
2143 get_object_string_from_fileobject (ECalBackendFileObject *obj_data, const char *rid)
2144 {
2145         ECalComponent *comp = obj_data->full_object;
2146         char *real_rid;
2147
2148         if (!rid) {
2149                 return e_cal_component_get_as_string (comp);
2150         } else {
2151                 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (void **)&real_rid, (void **)&comp))
2152                         return e_cal_component_get_as_string (comp);
2153                 else {
2154                         /* FIXME remove this once we delete an instance from master object through
2155                            modify request by setting exception */
2156                         return e_cal_component_get_as_string (comp);
2157                 }       
2158         }
2159
2160         return NULL;
2161 }
2162
2163 /* Remove_object handler for the file backend */
2164 static ECalBackendSyncStatus
2165 e_cal_backend_file_remove_object (ECalBackendSync *backend, EDataCal *cal,
2166                                   const char *uid, const char *rid,
2167                                   CalObjModType mod, char **old_object,
2168                                   char **object)
2169 {
2170         ECalBackendFile *cbfile;
2171         ECalBackendFilePrivate *priv;
2172         ECalBackendFileObject *obj_data;
2173         ECalComponent *comp;
2174         RemoveRecurrenceData rrdata;
2175         const char *recur_id = NULL;
2176
2177         cbfile = E_CAL_BACKEND_FILE (backend);
2178         priv = cbfile->priv;
2179
2180         g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
2181         g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
2182
2183         *old_object = *object = NULL;
2184
2185         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2186
2187         obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2188         if (!obj_data) {
2189                 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2190                 return GNOME_Evolution_Calendar_ObjectNotFound;
2191         }
2192         
2193         if (rid && *rid)
2194                 recur_id = rid;
2195         
2196         comp = obj_data->full_object;
2197
2198         switch (mod) {
2199         case CALOBJ_MOD_ALL :
2200                 *old_object = get_object_string_from_fileobject (obj_data, recur_id);
2201                 remove_component (cbfile, uid, obj_data);
2202
2203                 *object = NULL;
2204                 break;
2205         case CALOBJ_MOD_THIS :
2206                 if (!recur_id) {
2207                         *old_object = get_object_string_from_fileobject (obj_data, recur_id);
2208                         remove_component (cbfile, uid, obj_data);
2209                         *object = NULL;
2210                 } else {
2211                         *old_object = get_object_string_from_fileobject (obj_data, recur_id);
2212
2213                         remove_instance (cbfile, obj_data, recur_id);
2214                         if (comp)
2215                                 *object = e_cal_component_get_as_string (comp);
2216                 }
2217                 break;
2218         case CALOBJ_MOD_THISANDPRIOR :
2219         case CALOBJ_MOD_THISANDFUTURE :
2220                 if (!recur_id || !*recur_id) {
2221                         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2222                         return GNOME_Evolution_Calendar_ObjectNotFound;
2223                 }
2224
2225                 *old_object = e_cal_component_get_as_string (comp);
2226
2227                 /* remove the component from our data, temporarily */
2228                 icalcomponent_remove_component (priv->icalcomp,
2229                                                 e_cal_component_get_icalcomponent (comp));
2230                 priv->comp = g_list_remove (priv->comp, comp);
2231
2232                 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (comp),
2233                                              icaltime_from_string (recur_id), mod);
2234
2235                 /* now remove all detached instances */
2236                 rrdata.cbfile = cbfile;
2237                 rrdata.obj_data = obj_data;
2238                 rrdata.rid = recur_id;
2239                 rrdata.mod = mod;
2240                 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2241
2242                 /* add the modified object to the beginning of the list, 
2243                    so that it's always before any detached instance we
2244                    might have */
2245                 priv->comp = g_list_prepend (priv->comp, comp);
2246
2247                 *object = e_cal_component_get_as_string (obj_data->full_object);
2248                 break;
2249         }
2250
2251         save (cbfile);
2252
2253         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2254         return GNOME_Evolution_Calendar_Success;
2255 }
2256
2257 static gboolean
2258 cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp)
2259 {
2260         ECalBackendFileObject *obj_data;
2261         ECalBackendFilePrivate *priv;
2262         const char *rid;
2263         ECalComponent *comp;
2264
2265         priv = cbfile->priv;
2266
2267         /* Find the old version of the component. */
2268         obj_data = g_hash_table_lookup (priv->comp_uid_hash, icalcomponent_get_uid (icalcomp));
2269         if (!obj_data)
2270                 return FALSE;
2271
2272         /* And remove it */
2273         comp = e_cal_component_new ();
2274         if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
2275                 g_object_unref (comp);
2276                 return FALSE;
2277         }
2278
2279         rid = e_cal_component_get_recurid_as_string (comp);
2280         if (rid && *rid)
2281                 remove_instance (cbfile, obj_data, rid);
2282         else
2283                 remove_component (cbfile, icalcomponent_get_uid (icalcomp), obj_data);
2284
2285         return TRUE;
2286 }
2287
2288 typedef struct {
2289         GHashTable *zones;
2290         
2291         gboolean found;
2292 } ECalBackendFileTzidData;
2293
2294 static void
2295 check_tzids (icalparameter *param, void *data)
2296 {
2297         ECalBackendFileTzidData *tzdata = data;
2298         const char *tzid;
2299         
2300         tzid = icalparameter_get_tzid (param);
2301         if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
2302                 tzdata->found = FALSE;
2303 }
2304
2305
2306 /* This function is largely duplicated in
2307  * ../groupwise/e-cal-backend-groupwise.c
2308  */
2309 static void
2310 fetch_attachments (ECalBackendSync *backend, ECalComponent *comp)
2311 {
2312         GSList *attach_list = NULL, *new_attach_list = NULL;
2313         GSList *l;
2314         char  *attach_store;
2315         char *dest_url, *dest_file;
2316         int fd;
2317         const char *uid;
2318
2319         e_cal_component_get_attachment_list (comp, &attach_list);
2320         e_cal_component_get_uid (comp, &uid);
2321         /*FIXME  get the uri rather than computing the path */
2322         attach_store = g_build_filename (g_get_home_dir (),
2323                         ".evolution/calendar/local/system", NULL);
2324         
2325         for (l = attach_list; l ; l = l->next) {
2326                 char *sfname = (char *)l->data;
2327                 char *filename, *new_filename;
2328                 GMappedFile *mapped_file;
2329                 GError *error = NULL;
2330
2331                 mapped_file = g_mapped_file_new (sfname, FALSE, &error);
2332                 if (!mapped_file) {
2333                         g_message ("DEBUG: could not map %s: %s\n",
2334                                    sfname, error->message);
2335                         g_error_free (error);
2336                         continue;
2337                 }
2338                 filename = g_path_get_basename (sfname);
2339                 new_filename = g_strconcat (uid, "-", filename, NULL);
2340                 g_free (filename);
2341                 dest_file = g_build_filename (attach_store, new_filename, NULL);
2342                 g_free (new_filename);
2343                 fd = g_open (dest_file, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0600);
2344                 if (fd == -1) {
2345                         /* TODO handle error conditions */
2346                         g_message ("DEBUG: could not open %s for writing\n",
2347                                    dest_file);
2348                 } else if (write (fd, g_mapped_file_get_contents (mapped_file),
2349                                   g_mapped_file_get_length (mapped_file)) == -1) {
2350                         /* TODO handle error condition */
2351                         g_message ("DEBUG: attachment write failed.\n");
2352                 }
2353
2354                 g_mapped_file_free (mapped_file);
2355                 if (fd != -1)
2356                         close (fd);
2357                 dest_url = g_filename_to_uri (dest_file, NULL, NULL);
2358                 g_free (dest_file);
2359                 new_attach_list = g_slist_append (new_attach_list, dest_url);
2360         }
2361         g_free (attach_store);
2362         e_cal_component_set_attachment_list (comp, new_attach_list);
2363 }
2364
2365 /* Update_objects handler for the file backend. */
2366 static ECalBackendSyncStatus
2367 e_cal_backend_file_receive_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj)
2368 {
2369         ECalBackendFile *cbfile;
2370         ECalBackendFilePrivate *priv;
2371         icalcomponent *toplevel_comp, *icalcomp = NULL;
2372         icalcomponent_kind kind;
2373         icalproperty_method toplevel_method, method;
2374         icalcomponent *subcomp;
2375         GList *comps, *del_comps, *l;
2376         ECalComponent *comp;
2377         struct icaltimetype current;
2378         ECalBackendFileTzidData tzdata;
2379         ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;
2380
2381         cbfile = E_CAL_BACKEND_FILE (backend);
2382         priv = cbfile->priv;
2383
2384         g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
2385         g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_InvalidObject);
2386
2387         /* Pull the component from the string and ensure that it is sane */
2388         toplevel_comp = icalparser_parse_string ((char *) calobj);
2389         if (!toplevel_comp)
2390                 return GNOME_Evolution_Calendar_InvalidObject;
2391
2392         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2393
2394         kind = icalcomponent_isa (toplevel_comp);
2395         if (kind != ICAL_VCALENDAR_COMPONENT) {
2396                 /* If its not a VCALENDAR, make it one to simplify below */
2397                 icalcomp = toplevel_comp;
2398                 toplevel_comp = e_cal_util_new_top_level ();
2399                 if (icalcomponent_get_method (icalcomp) == ICAL_METHOD_CANCEL)
2400                         icalcomponent_set_method (toplevel_comp, ICAL_METHOD_CANCEL);
2401                 else
2402                         icalcomponent_set_method (toplevel_comp, ICAL_METHOD_PUBLISH);
2403                 icalcomponent_add_component (toplevel_comp, icalcomp);
2404         } else {
2405                 if (!icalcomponent_get_first_property (toplevel_comp, ICAL_METHOD_PROPERTY))
2406                         icalcomponent_set_method (toplevel_comp, ICAL_METHOD_PUBLISH);
2407         }
2408
2409         toplevel_method = icalcomponent_get_method (toplevel_comp);
2410
2411         /* Build a list of timezones so we can make sure all the objects have valid info */
2412         tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2413
2414         subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
2415         while (subcomp) {
2416                 icaltimezone *zone;
2417                 
2418                 zone = icaltimezone_new ();
2419                 if (icaltimezone_set_component (zone, subcomp))
2420                         g_hash_table_insert (tzdata.zones, g_strdup (icaltimezone_get_tzid (zone)), NULL);
2421                 
2422                 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
2423         }       
2424
2425         /* First we make sure all the components are usuable */
2426         comps = del_comps = NULL;
2427         kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
2428
2429         subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
2430         while (subcomp) {
2431                 icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
2432                 
2433                 if (child_kind != kind) {
2434                         /* remove the component from the toplevel VCALENDAR */
2435                         if (child_kind != ICAL_VTIMEZONE_COMPONENT)
2436                                 del_comps = g_list_prepend (del_comps, subcomp);
2437
2438                         subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
2439                         continue;
2440                 }
2441                         
2442                 tzdata.found = TRUE;
2443                 icalcomponent_foreach_tzid (subcomp, check_tzids, &tzdata);
2444
2445                 if (!tzdata.found) {
2446                         status = GNOME_Evolution_Calendar_InvalidObject;
2447                         goto error;
2448                 }
2449                 
2450                 if (!icalcomponent_get_uid (subcomp)) {
2451                         if (toplevel_method == ICAL_METHOD_PUBLISH) {
2452
2453                                 char *new_uid = NULL;
2454
2455                                 new_uid = e_cal_component_gen_uid ();
2456                                 icalcomponent_set_uid (subcomp, new_uid);
2457                                 g_free (new_uid);
2458                         } else {
2459                                 status = GNOME_Evolution_Calendar_InvalidObject;
2460                                 goto error;
2461                         }
2462                         
2463                 }
2464                 
2465                 comps = g_list_prepend (comps, subcomp);
2466                 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
2467         }
2468
2469         /* Now we manipulate the components we care about */
2470         for (l = comps; l; l = l->next) {
2471                 const char *uid, *rid;
2472                 char *object, *old_object;
2473                 ECalBackendFileObject *obj_data;
2474
2475                 subcomp = l->data;
2476         
2477                 /* Create the cal component */
2478                 comp = e_cal_component_new ();
2479                 e_cal_component_set_icalcomponent (comp, subcomp);
2480
2481                 /* Set the created and last modified times on the component */
2482                 current = icaltime_from_timet (time (NULL), 0);
2483                 e_cal_component_set_created (comp, &current);
2484                 e_cal_component_set_last_modified (comp, &current);
2485
2486                 e_cal_component_get_uid (comp, &uid);
2487                 rid = e_cal_component_get_recurid_as_string (comp);
2488
2489                 if (icalcomponent_get_first_property (subcomp, ICAL_METHOD_PROPERTY))
2490                         method = icalcomponent_get_method (subcomp);
2491                 else
2492                         method = toplevel_method;
2493
2494                 switch (method) {
2495                 case ICAL_METHOD_PUBLISH:
2496                 case ICAL_METHOD_REQUEST:
2497                 case ICAL_METHOD_REPLY:
2498                         /* handle attachments */
2499                         if (e_cal_component_has_attachments (comp))
2500                                 fetch_attachments (backend, comp);
2501                         obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2502                         if (obj_data) {
2503                                 old_object = e_cal_component_get_as_string (obj_data->full_object);
2504                                 if (rid)
2505                                         remove_instance (cbfile, obj_data, rid);
2506                                 else
2507                                         remove_component (cbfile, uid, obj_data);
2508                                 add_component (cbfile, comp, FALSE);
2509
2510                                 object = e_cal_component_get_as_string (comp);
2511                                 e_cal_backend_notify_object_modified (E_CAL_BACKEND (backend), old_object, object);
2512                                 g_free (object);
2513                                 g_free (old_object);
2514                         } else {
2515                                 add_component (cbfile, comp, FALSE);
2516
2517                                 object = e_cal_component_get_as_string (comp);
2518                                 e_cal_backend_notify_object_created (E_CAL_BACKEND (backend), object);
2519                                 g_free (object);
2520                         }
2521                         break;
2522                 case ICAL_METHOD_ADD:
2523                         /* FIXME This should be doable once all the recurid stuff is done */
2524                         status = GNOME_Evolution_Calendar_UnsupportedMethod;
2525                         goto error;
2526                         break;
2527                 case ICAL_METHOD_COUNTER:
2528                         status = GNOME_Evolution_Calendar_UnsupportedMethod;
2529                         goto error;
2530                         break;                  
2531                 case ICAL_METHOD_DECLINECOUNTER:                        
2532                         status = GNOME_Evolution_Calendar_UnsupportedMethod;
2533                         goto error;
2534                         break;
2535                 case ICAL_METHOD_CANCEL:
2536                         if (cancel_received_object (cbfile, subcomp)) {
2537                                 ECalComponentId *id;
2538                                 object = (char *) icalcomponent_as_ical_string (subcomp);
2539                                 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2540                                 if (obj_data)
2541                                         old_object = e_cal_component_get_as_string (obj_data->full_object);
2542                                 else
2543                                         old_object = NULL;
2544
2545                                 id = e_cal_component_get_id (comp);
2546
2547                                 e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend), id, old_object, object);
2548
2549                                 /* remove the component from the toplevel VCALENDAR */
2550                                 icalcomponent_remove_component (toplevel_comp, subcomp);
2551                                 icalcomponent_free (subcomp);
2552                                 e_cal_component_free_id (id);
2553
2554                                 g_free (old_object);
2555                         }
2556                         break;
2557                 default:
2558                         status = GNOME_Evolution_Calendar_UnsupportedMethod;
2559                         goto error;
2560                 }
2561         }
2562
2563         g_list_free (comps);
2564         
2565         /* Now we remove the components we don't care about */
2566         for (l = del_comps; l; l = l->next) {
2567                 subcomp = l->data;
2568                 
2569                 icalcomponent_remove_component (toplevel_comp, subcomp);
2570                 icalcomponent_free (subcomp);           
2571         }
2572         
2573         g_list_free (del_comps);
2574
2575         /* Merge the iCalendar components with our existing VCALENDAR,
2576            resolving any conflicting TZIDs. */
2577         icalcomponent_merge_component (priv->icalcomp, toplevel_comp);
2578
2579         save (cbfile);
2580
2581  error:
2582         g_hash_table_destroy (tzdata.zones);
2583         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2584         return status;
2585 }
2586
2587 static ECalBackendSyncStatus
2588 e_cal_backend_file_send_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj, GList **users,
2589                                  char **modified_calobj)
2590 {
2591         *users = NULL;
2592         *modified_calobj = g_strdup (calobj);
2593
2594         return GNOME_Evolution_Calendar_Success;
2595 }
2596
2597 /* Object initialization function for the file backend */
2598 static void
2599 e_cal_backend_file_init (ECalBackendFile *cbfile)
2600 {
2601         ECalBackendFilePrivate *priv;
2602
2603         priv = g_new0 (ECalBackendFilePrivate, 1);
2604         cbfile->priv = priv;
2605
2606         priv->uri = NULL;
2607         priv->file_name = g_strdup ("calendar.ics");
2608         priv->read_only = FALSE;
2609         priv->is_dirty = FALSE;
2610         priv->dirty_idle_id = 0;
2611         g_static_rec_mutex_init (&priv->idle_save_rmutex);
2612         priv->icalcomp = NULL;
2613         priv->comp_uid_hash = NULL;
2614         priv->comp = NULL;
2615
2616         /* The timezone defaults to UTC. */
2617         priv->default_zone = icaltimezone_get_utc_timezone ();
2618
2619         /*
2620          * data access is serialized via idle_save_rmutex, so locking at the
2621          * backend method level is not needed
2622          */
2623         e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbfile), FALSE);
2624 }
2625
2626 /* Class initialization function for the file backend */
2627 static void
2628 e_cal_backend_file_class_init (ECalBackendFileClass *class)
2629 {
2630         GObjectClass *object_class;
2631         ECalBackendClass *backend_class;
2632         ECalBackendSyncClass *sync_class;
2633
2634         object_class = (GObjectClass *) class;
2635         backend_class = (ECalBackendClass *) class;
2636         sync_class = (ECalBackendSyncClass *) class;
2637
2638         parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
2639
2640         object_class->dispose = e_cal_backend_file_dispose;
2641         object_class->finalize = e_cal_backend_file_finalize;
2642
2643         sync_class->is_read_only_sync = e_cal_backend_file_is_read_only;
2644         sync_class->get_cal_address_sync = e_cal_backend_file_get_cal_address;
2645         sync_class->get_alarm_email_address_sync = e_cal_backend_file_get_alarm_email_address;
2646         sync_class->get_ldap_attribute_sync = e_cal_backend_file_get_ldap_attribute;
2647         sync_class->get_static_capabilities_sync = e_cal_backend_file_get_static_capabilities;
2648         sync_class->open_sync = e_cal_backend_file_open;
2649         sync_class->remove_sync = e_cal_backend_file_remove;
2650         sync_class->create_object_sync = e_cal_backend_file_create_object;
2651         sync_class->modify_object_sync = e_cal_backend_file_modify_object;
2652         sync_class->remove_object_sync = e_cal_backend_file_remove_object;
2653         sync_class->discard_alarm_sync = e_cal_backend_file_discard_alarm;
2654         sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
2655         sync_class->send_objects_sync = e_cal_backend_file_send_objects;
2656         sync_class->get_default_object_sync = e_cal_backend_file_get_default_object;
2657         sync_class->get_object_sync = e_cal_backend_file_get_object;
2658         sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
2659         sync_class->get_attachment_list_sync = e_cal_backend_file_get_attachment_list;
2660         sync_class->get_timezone_sync = e_cal_backend_file_get_timezone;
2661         sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
2662         sync_class->set_default_zone_sync = e_cal_backend_file_set_default_zone;
2663         sync_class->get_freebusy_sync = e_cal_backend_file_get_free_busy;
2664         sync_class->get_changes_sync = e_cal_backend_file_get_changes;
2665
2666         backend_class->is_loaded = e_cal_backend_file_is_loaded;
2667         backend_class->start_query = e_cal_backend_file_start_query;
2668         backend_class->get_mode = e_cal_backend_file_get_mode;
2669         backend_class->set_mode = e_cal_backend_file_set_mode;
2670
2671         backend_class->internal_get_default_timezone = e_cal_backend_file_internal_get_default_timezone;
2672         backend_class->internal_get_timezone = e_cal_backend_file_internal_get_timezone;
2673 }
2674
2675
2676 /**
2677  * e_cal_backend_file_get_type:
2678  * @void: 
2679  * 
2680  * Registers the #ECalBackendFile class if necessary, and returns the type ID
2681  * associated to it.
2682  * 
2683  * Return value: The type ID of the #ECalBackendFile class.
2684  **/
2685 GType
2686 e_cal_backend_file_get_type (void)
2687 {
2688         static GType e_cal_backend_file_type = 0;
2689
2690         if (!e_cal_backend_file_type) {
2691                 static GTypeInfo info = {
2692                         sizeof (ECalBackendFileClass),
2693                         (GBaseInitFunc) NULL,
2694                         (GBaseFinalizeFunc) NULL,
2695                         (GClassInitFunc) e_cal_backend_file_class_init,
2696                         NULL, NULL,
2697                         sizeof (ECalBackendFile),
2698                         0,
2699                         (GInstanceInitFunc) e_cal_backend_file_init
2700                 };
2701                 e_cal_backend_file_type = g_type_register_static (E_TYPE_CAL_BACKEND_SYNC,
2702                                                                 "ECalBackendFile", &info, 0);
2703         }
2704
2705         return e_cal_backend_file_type;
2706 }
2707
2708 void
2709 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile, const char *file_name)
2710 {
2711         ECalBackendFilePrivate *priv;
2712         
2713         g_return_if_fail (cbfile != NULL);
2714         g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
2715         g_return_if_fail (file_name != NULL);
2716
2717         priv = cbfile->priv;
2718         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2719
2720         if (priv->file_name)
2721                 g_free (priv->file_name);
2722         
2723         priv->file_name = g_strdup (file_name);
2724
2725         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2726 }
2727
2728 const char *
2729 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
2730 {
2731         ECalBackendFilePrivate *priv;
2732
2733         g_return_val_if_fail (cbfile != NULL, NULL);
2734         g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
2735
2736         priv = cbfile->priv;    
2737
2738         return priv->file_name;
2739 }
2740
2741 ECalBackendSyncStatus
2742 e_cal_backend_file_reload (ECalBackendFile *cbfile)
2743 {
2744         ECalBackendFilePrivate *priv;
2745         char *str_uri;
2746         ECalBackendSyncStatus status;
2747         
2748         priv = cbfile->priv;
2749         g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2750
2751         str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
2752         if (!str_uri) {
2753                 status = GNOME_Evolution_Calendar_OtherError;
2754                 goto done;
2755         }
2756
2757         if (g_access (str_uri, R_OK) == 0) {
2758                 status = reload_cal (cbfile, str_uri);
2759                 if (g_access (str_uri, W_OK) != 0)
2760                         priv->read_only = TRUE;
2761         } else {
2762                 status = GNOME_Evolution_Calendar_NoSuchCal;
2763         }
2764
2765         g_free (str_uri);
2766
2767   done:
2768         g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2769         return status;
2770 }