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