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