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