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