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