1 /* Evolution calendar - iCalendar file backend
3 * Copyright (C) 2000-2003 Ximian, Inc.
5 * Authors: Federico Mena-Quintero <federico@ximian.com>
6 * Rodrigo Moya <rodrigo@ximian.com>
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.
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.
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.
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"
43 /* Placeholder for each component and its recurrences */
45 ECalComponent *full_object;
46 GHashTable *recurrences;
47 GList *recurrences_list;
48 } ECalBackendFileObject;
50 /* Private part of the ECalBackendFile structure */
51 struct _ECalBackendFilePrivate {
52 /* URI where the calendar data is stored */
55 /* Filename in the dir */
59 /* Toplevel VCALENDAR component */
60 icalcomponent *icalcomp;
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.
67 GHashTable *comp_uid_hash;
71 /* The calendar's default timezone, used for resolving DATE and
72 floating DATE-TIME values. */
73 icaltimezone *default_zone;
75 /* The list of live queries */
83 static void e_cal_backend_file_dispose (GObject *object);
84 static void e_cal_backend_file_finalize (GObject *object);
86 static ECalBackendSyncClass *parent_class;
90 /* g_hash_table_foreach() callback to destroy recurrences in the hash table */
92 free_recurrence (gpointer key, gpointer value, gpointer data)
95 ECalComponent *comp = value;
98 g_object_unref (comp);
101 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
103 free_object (gpointer key, gpointer value, gpointer data)
105 ECalBackendFileObject *obj_data = value;
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);
115 /* Saves the calendar data */
117 save (ECalBackendFile *cbfile)
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;
128 g_assert (priv->uri != NULL);
129 g_assert (priv->icalcomp != NULL);
131 uri = gnome_vfs_uri_new (priv->uri);
133 goto error_malformed_uri;
135 /* save calendar to backup file */
136 tmp = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
138 gnome_vfs_uri_unref (uri);
139 goto error_malformed_uri;
142 backup_uristr = g_strconcat (tmp, "~", NULL);
143 backup_uri = gnome_vfs_uri_new (backup_uristr);
146 g_free (backup_uristr);
149 gnome_vfs_uri_unref (uri);
150 goto error_malformed_uri;
153 result = gnome_vfs_create_uri (&handle, backup_uri,
154 GNOME_VFS_OPEN_WRITE,
156 if (result != GNOME_VFS_OK) {
157 gnome_vfs_uri_unref (uri);
158 gnome_vfs_uri_unref (backup_uri);
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);
171 /* now copy the temporary file to the real file */
172 result = gnome_vfs_move_uri (backup_uri, uri, TRUE);
174 gnome_vfs_uri_unref (uri);
175 gnome_vfs_uri_unref (backup_uri);
176 if (result != GNOME_VFS_OK)
182 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
183 _("Can't save calendar data: Malformed URI."));
187 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), gnome_vfs_result_to_string (result));
192 free_calendar_components (GHashTable *comp_uid_hash, icalcomponent *top_icomp)
195 g_hash_table_foreach (comp_uid_hash, (GHFunc) free_object, NULL);
196 g_hash_table_destroy (comp_uid_hash);
200 icalcomponent_free (top_icomp);
205 free_calendar_data (ECalBackendFile *cbfile)
207 ECalBackendFilePrivate *priv;
211 free_calendar_components (priv->comp_uid_hash, priv->icalcomp);
212 priv->comp_uid_hash = NULL;
213 priv->icalcomp = NULL;
215 g_list_free (priv->comp);
219 /* Dispose handler for the file backend */
221 e_cal_backend_file_dispose (GObject *object)
223 ECalBackendFile *cbfile;
224 ECalBackendFilePrivate *priv;
226 cbfile = E_CAL_BACKEND_FILE (object);
229 /* Save if necessary */
231 free_calendar_data (cbfile);
233 if (G_OBJECT_CLASS (parent_class)->dispose)
234 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
237 /* Finalize handler for the file backend */
239 e_cal_backend_file_finalize (GObject *object)
241 ECalBackendFile *cbfile;
242 ECalBackendFilePrivate *priv;
244 g_return_if_fail (object != NULL);
245 g_return_if_fail (E_IS_CAL_BACKEND_FILE (object));
247 cbfile = E_CAL_BACKEND_FILE (object);
260 if (G_OBJECT_CLASS (parent_class)->finalize)
261 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
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)
270 ECalBackendFilePrivate *priv;
271 ECalBackendFileObject *obj_data;
275 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
276 return obj_data ? obj_data->full_object : NULL;
281 /* Calendar backend methods */
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)
287 ECalBackendFile *cbfile = (ECalBackendFile *) backend;
289 *read_only = cbfile->priv->read_only;
291 return GNOME_Evolution_Calendar_Success;
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)
298 /* A file backend has no particular email address associated
299 * with it (although that would be a useful feature some day).
303 return GNOME_Evolution_Calendar_Success;
306 static ECalBackendSyncStatus
307 e_cal_backend_file_get_ldap_attribute (ECalBackendSync *backend, EDataCal *cal, char **attribute)
311 return GNOME_Evolution_Calendar_Success;
314 static ECalBackendSyncStatus
315 e_cal_backend_file_get_alarm_email_address (ECalBackendSync *backend, EDataCal *cal, char **address)
317 /* A file backend has no particular email address associated
318 * with it (although that would be a useful feature some day).
322 return GNOME_Evolution_Calendar_Success;
325 static ECalBackendSyncStatus
326 e_cal_backend_file_get_static_capabilities (ECalBackendSync *backend, EDataCal *cal, char **capabilities)
328 *capabilities = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS);
330 return GNOME_Evolution_Calendar_Success;
333 /* function to resolve timezones */
334 static icaltimezone *
335 resolve_tzid (const char *tzid, gpointer user_data)
337 icalcomponent *vcalendar_comp = user_data;
339 if (!tzid || !tzid[0])
341 else if (!strcmp (tzid, "UTC"))
342 return icaltimezone_get_utc_timezone ();
344 return icalcomponent_get_timezone (vcalendar_comp, tzid);
347 /* Checks if the specified component has a duplicated UID and if so changes it */
349 check_dup_uid (ECalBackendFile *cbfile, ECalComponent *comp)
351 ECalBackendFilePrivate *priv;
352 ECalBackendFileObject *obj_data;
358 e_cal_component_get_uid (comp, &uid);
360 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
362 return; /* Everything is fine */
364 d(g_message (G_STRLOC ": Got object with duplicated UID `%s', changing it...", uid));
366 new_uid = e_cal_component_gen_uid ();
367 e_cal_component_set_uid (comp, new_uid);
370 /* FIXME: I think we need to reset the SEQUENCE property and reset the
371 * CREATED/DTSTAMP/LAST-MODIFIED.
377 static struct icaltimetype
378 get_rid_icaltime (ECalComponent *comp)
380 ECalComponentRange range;
381 struct icaltimetype tt;
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);
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.
397 add_component (ECalBackendFile *cbfile, ECalComponent *comp, gboolean add_to_toplevel)
399 ECalBackendFilePrivate *priv;
400 ECalBackendFileObject *obj_data;
406 if (e_cal_component_is_instance (comp)) { /* FIXME: more checks needed, to detect detached instances */
409 e_cal_component_get_uid (comp, &uid);
411 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
413 g_warning (G_STRLOC ": Got an instance of a non-existing component");
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");
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);
427 /* Ensure that the UID is unique; some broken implementations spit
428 * components with duplicated UIDs.
430 check_dup_uid (cbfile, comp);
431 e_cal_component_get_uid (comp, &uid);
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);
437 g_hash_table_insert (priv->comp_uid_hash, (gpointer) uid, obj_data);
440 priv->comp = g_list_prepend (priv->comp, comp);
442 /* Put the object in the toplevel component if required */
444 if (add_to_toplevel) {
445 icalcomponent *icalcomp;
447 icalcomp = e_cal_component_get_icalcomponent (comp);
448 g_assert (icalcomp != NULL);
450 icalcomponent_add_component (priv->icalcomp, icalcomp);
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);
459 /* g_hash_table_foreach() callback to remove recurrences from the calendar */
461 remove_recurrence_cb (gpointer key, gpointer value, gpointer data)
465 icalcomponent *icalcomp;
466 ECalBackendFilePrivate *priv;
467 ECalComponent *comp = value;
468 ECalBackendFile *cbfile = data;
472 /* remove the recurrence from the top-level calendar */
473 icalcomp = e_cal_component_get_icalcomponent (comp);
474 g_assert (icalcomp != NULL);
476 icalcomponent_remove_component (priv->icalcomp, icalcomp);
478 /* remove it from our mapping */
479 l = g_list_find (priv->comp, comp);
480 priv->comp = g_list_delete_link (priv->comp, l);
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);
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
493 remove_component (ECalBackendFile *cbfile, ECalComponent *comp)
495 ECalBackendFilePrivate *priv;
496 icalcomponent *icalcomp;
500 ECalBackendFileObject *obj_data;
504 /* Remove the icalcomp from the toplevel */
506 icalcomp = e_cal_component_get_icalcomponent (comp);
507 g_assert (icalcomp != NULL);
509 icalcomponent_remove_component (priv->icalcomp, icalcomp);
511 /* Remove it from our mapping */
513 e_cal_component_get_uid (comp, &uid);
514 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
518 g_hash_table_remove (priv->comp_uid_hash, uid);
520 l = g_list_find (priv->comp, comp);
521 g_assert (l != NULL);
522 priv->comp = g_list_delete_link (priv->comp, l);
524 /* remove the recurrences also */
525 g_hash_table_foreach (obj_data->recurrences, (GHFunc) remove_recurrence_cb, cbfile);
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);
532 free_object ((gpointer) uid, (gpointer) obj_data, NULL);
535 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
537 scan_vcalendar (ECalBackendFile *cbfile)
539 ECalBackendFilePrivate *priv;
543 g_assert (priv->icalcomp != NULL);
544 g_assert (priv->comp_uid_hash != NULL);
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;
553 icalcomp = icalcompiter_deref (&iter);
555 kind = icalcomponent_isa (icalcomp);
557 if (!(kind == ICAL_VEVENT_COMPONENT
558 || kind == ICAL_VTODO_COMPONENT
559 || kind == ICAL_VJOURNAL_COMPONENT))
562 comp = e_cal_component_new ();
564 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
567 add_component (cbfile, comp, FALSE);
572 get_uri_string_for_gnome_vfs (ECalBackend *backend)
574 ECalBackendFile *cbfile;
575 ECalBackendFilePrivate *priv;
576 const char *master_uri;
577 char *full_uri, *str_uri;
580 cbfile = E_CAL_BACKEND_FILE (backend);
583 master_uri = e_cal_backend_get_uri (backend);
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);
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);
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);
607 if (!str_uri || !strlen (str_uri)) {
616 /* Parses an open iCalendar file and loads it into the backend */
617 static ECalBackendSyncStatus
618 open_cal (ECalBackendFile *cbfile, const char *uristr)
620 ECalBackendFilePrivate *priv;
621 icalcomponent *icalcomp;
625 icalcomp = e_cal_util_parse_ics_file (uristr);
627 return GNOME_Evolution_Calendar_OtherError;
629 /* FIXME: should we try to demangle XROOT components and
630 * individual components as well?
633 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
634 icalcomponent_free (icalcomp);
636 return GNOME_Evolution_Calendar_OtherError;
639 priv->icalcomp = icalcomp;
640 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
642 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
643 scan_vcalendar (cbfile);
645 return GNOME_Evolution_Calendar_Success;
650 ECalBackend *backend;
651 GHashTable *old_uid_hash;
652 GHashTable *new_uid_hash;
657 notify_removals_cb (gpointer key, gpointer value, gpointer data)
659 BackendDeltaContext *context = data;
660 const gchar *uid = key;
661 ECalBackendFileObject *old_obj_data = value;
663 if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
664 icalcomponent *old_icomp;
667 /* Object was removed */
669 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
673 old_obj_str = icalcomponent_as_ical_string (old_icomp);
677 e_cal_backend_notify_object_removed (context->backend, uid, old_obj_str);
682 notify_adds_modifies_cb (gpointer key, gpointer value, gpointer data)
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;
691 old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
694 /* Object was added */
696 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
700 new_obj_str = icalcomponent_as_ical_string (new_icomp);
704 e_cal_backend_notify_object_created (context->backend, new_obj_str);
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)
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)
716 if (strcmp (old_obj_str, new_obj_str)) {
717 /* Object was modified */
719 e_cal_backend_notify_object_modified (context->backend, old_obj_str, new_obj_str);
725 notify_changes (ECalBackendFile *cbfile, GHashTable *old_uid_hash, GHashTable *new_uid_hash)
727 BackendDeltaContext context;
729 context.backend = E_CAL_BACKEND (cbfile);
730 context.old_uid_hash = old_uid_hash;
731 context.new_uid_hash = new_uid_hash;
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);
737 static ECalBackendSyncStatus
738 reload_cal (ECalBackendFile *cbfile, const char *uristr)
740 ECalBackendFilePrivate *priv;
741 icalcomponent *icalcomp, *icalcomp_old;
742 GHashTable *comp_uid_hash_old;
746 icalcomp = e_cal_util_parse_ics_file (uristr);
748 return GNOME_Evolution_Calendar_OtherError;
750 /* FIXME: should we try to demangle XROOT components and
751 * individual components as well?
754 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
755 icalcomponent_free (icalcomp);
757 return GNOME_Evolution_Calendar_OtherError;
760 /* Keep old data for comparison - free later */
762 icalcomp_old = priv->icalcomp;
763 priv->icalcomp = NULL;
765 comp_uid_hash_old = priv->comp_uid_hash;
766 priv->comp_uid_hash = NULL;
768 /* Load new calendar */
770 free_calendar_data (cbfile);
772 priv->icalcomp = icalcomp;
774 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
775 scan_vcalendar (cbfile);
777 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
779 /* Compare old and new versions of calendar */
781 notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
785 free_calendar_components (comp_uid_hash_old, icalcomp_old);
786 return GNOME_Evolution_Calendar_Success;
789 static ECalBackendSyncStatus
790 create_cal (ECalBackendFile *cbfile, const char *uristr)
793 ECalBackendFilePrivate *priv;
797 /* Create the directory to contain the file */
798 dirname = g_path_get_dirname (uristr);
799 if (e_util_mkdir_hier (dirname, 0700) != 0) {
801 return GNOME_Evolution_Calendar_NoSuchCal;
806 /* Create the new calendar information */
807 priv->icalcomp = e_cal_util_new_top_level ();
809 /* Create our internal data */
810 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
812 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
816 return GNOME_Evolution_Calendar_Success;
820 get_uri_string (ECalBackend *backend)
822 gchar *str_uri, *full_uri;
824 str_uri = get_uri_string_for_gnome_vfs (backend);
825 full_uri = gnome_vfs_unescape_string (str_uri, "");
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)
836 ECalBackendFile *cbfile;
837 ECalBackendFilePrivate *priv;
839 ECalBackendSyncStatus status;
841 cbfile = E_CAL_BACKEND_FILE (backend);
844 /* Claim a succesful open if we are already open */
845 if (priv->uri && priv->comp_uid_hash)
846 return GNOME_Evolution_Calendar_Success;
848 str_uri = get_uri_string (E_CAL_BACKEND (backend));
850 return GNOME_Evolution_Calendar_OtherError;
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;
858 status = GNOME_Evolution_Calendar_NoSuchCal;
860 status = create_cal (cbfile, str_uri);
868 static ECalBackendSyncStatus
869 e_cal_backend_file_remove (ECalBackendSync *backend, EDataCal *cal)
871 ECalBackendFile *cbfile;
872 ECalBackendFilePrivate *priv;
873 char *str_uri, *dirname;
876 GError *error = NULL;
879 cbfile = E_CAL_BACKEND_FILE (backend);
882 str_uri = get_uri_string (E_CAL_BACKEND (backend));
884 return GNOME_Evolution_Calendar_OtherError;
886 if (access (str_uri, W_OK) != 0) {
889 return GNOME_Evolution_Calendar_PermissionDenied;
892 /* remove all files in the directory */
893 dirname = g_path_get_dirname (str_uri);
894 dir = g_dir_open (dirname, 0, &error);
899 return GNOME_Evolution_Calendar_PermissionDenied;
902 while ((fname = g_dir_read_name (dir))) {
905 full_path = g_build_filename (dirname, fname, NULL);
906 if (unlink (full_path) != 0) {
912 return GNOME_Evolution_Calendar_OtherError;
918 /* remove the directory itself */
919 success = rmdir (dirname) == 0;
925 return success ? GNOME_Evolution_Calendar_Success : GNOME_Evolution_Calendar_OtherError;
928 /* is_loaded handler for the file backend */
930 e_cal_backend_file_is_loaded (ECalBackend *backend)
932 ECalBackendFile *cbfile;
933 ECalBackendFilePrivate *priv;
935 cbfile = E_CAL_BACKEND_FILE (backend);
938 return (priv->icalcomp != NULL);
941 /* is_remote handler for the file backend */
943 e_cal_backend_file_get_mode (ECalBackend *backend)
945 ECalBackendFile *cbfile;
946 ECalBackendFilePrivate *priv;
948 cbfile = E_CAL_BACKEND_FILE (backend);
951 return CAL_MODE_LOCAL;
954 /* Set_mode handler for the file backend */
956 e_cal_backend_file_set_mode (ECalBackend *backend, CalMode mode)
958 e_cal_backend_notify_mode (backend,
959 GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
960 GNOME_Evolution_Calendar_MODE_LOCAL);
964 static ECalBackendSyncStatus
965 e_cal_backend_file_get_default_object (ECalBackendSync *backend, EDataCal *cal, char **object)
969 comp = e_cal_component_new ();
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);
975 case ICAL_VTODO_COMPONENT:
976 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
978 case ICAL_VJOURNAL_COMPONENT:
979 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
982 g_object_unref (comp);
983 return GNOME_Evolution_Calendar_ObjectNotFound;
986 *object = e_cal_component_get_as_string (comp);
987 g_object_unref (comp);
989 return GNOME_Evolution_Calendar_Success;
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)
996 ECalBackendFile *cbfile;
997 ECalBackendFilePrivate *priv;
998 ECalBackendFileObject *obj_data;
999 ECalComponent *comp = NULL;
1000 gboolean free_comp = FALSE;
1002 cbfile = E_CAL_BACKEND_FILE (backend);
1003 priv = cbfile->priv;
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);
1009 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1011 return GNOME_Evolution_Calendar_ObjectNotFound;
1014 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1016 icalcomponent *icalcomp;
1017 struct icaltimetype itt;
1019 itt = icaltime_from_string (rid);
1020 icalcomp = e_cal_util_construct_instance (
1021 e_cal_component_get_icalcomponent (obj_data->full_object),
1024 return GNOME_Evolution_Calendar_ObjectNotFound;
1026 comp = e_cal_component_new ();
1028 e_cal_component_set_icalcomponent (comp, icalcomp);
1031 comp = obj_data->full_object;
1034 return GNOME_Evolution_Calendar_ObjectNotFound;
1036 *object = e_cal_component_get_as_string (comp);
1039 g_object_unref (comp);
1041 return GNOME_Evolution_Calendar_Success;
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)
1048 ECalBackendFile *cbfile;
1049 ECalBackendFilePrivate *priv;
1051 icalcomponent *icalcomp;
1053 cbfile = E_CAL_BACKEND_FILE (backend);
1054 priv = cbfile->priv;
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);
1059 if (!strcmp (tzid, "UTC")) {
1060 zone = icaltimezone_get_utc_timezone ();
1062 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1064 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1066 return GNOME_Evolution_Calendar_ObjectNotFound;
1070 icalcomp = icaltimezone_get_component (zone);
1072 return GNOME_Evolution_Calendar_InvalidObject;
1074 *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1076 return GNOME_Evolution_Calendar_Success;
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)
1083 icalcomponent *tz_comp;
1084 ECalBackendFile *cbfile;
1085 ECalBackendFilePrivate *priv;
1087 cbfile = (ECalBackendFile *) backend;
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);
1092 priv = cbfile->priv;
1094 tz_comp = icalparser_parse_string (tzobj);
1096 return GNOME_Evolution_Calendar_InvalidObject;
1098 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
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);
1109 icaltimezone_free (zone, 1);
1112 return GNOME_Evolution_Calendar_Success;
1116 static ECalBackendSyncStatus
1117 e_cal_backend_file_set_default_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid)
1119 ECalBackendFile *cbfile;
1120 ECalBackendFilePrivate *priv;
1123 cbfile = E_CAL_BACKEND_FILE (backend);
1124 priv = cbfile->priv;
1126 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1128 /* Look up the VTIMEZONE in our icalcomponent. */
1129 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1131 return GNOME_Evolution_Calendar_ObjectNotFound;
1133 /* Set the default timezone to it. */
1134 priv->default_zone = zone;
1136 return GNOME_Evolution_Calendar_Success;
1141 gboolean search_needed;
1143 ECalBackendSExp *obj_sexp;
1144 ECalBackend *backend;
1145 icaltimezone *default_zone;
1149 match_recurrence_sexp (gpointer key, gpointer value, gpointer data)
1151 ECalComponent *comp = value;
1152 MatchObjectData *match_data = data;
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));
1162 match_object_sexp (gpointer key, gpointer value, gpointer data)
1164 ECalBackendFileObject *obj_data = value;
1165 MatchObjectData *match_data = data;
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));
1172 /* match also recurrences */
1173 g_hash_table_foreach (obj_data->recurrences,
1174 (GHFunc) match_recurrence_sexp,
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)
1183 ECalBackendFile *cbfile;
1184 ECalBackendFilePrivate *priv;
1185 MatchObjectData match_data;
1187 cbfile = E_CAL_BACKEND_FILE (backend);
1188 priv = cbfile->priv;
1190 d(g_message (G_STRLOC ": Getting object list (%s)", sexp));
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;
1198 if (!strcmp (sexp, "#t"))
1199 match_data.search_needed = FALSE;
1201 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1202 if (!match_data.obj_sexp)
1203 return GNOME_Evolution_Calendar_InvalidQuery;
1205 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1207 *objects = match_data.obj_list;
1209 return GNOME_Evolution_Calendar_Success;
1212 /* get_query handler for the file backend */
1214 e_cal_backend_file_start_query (ECalBackend *backend, EDataCalView *query)
1216 ECalBackendFile *cbfile;
1217 ECalBackendFilePrivate *priv;
1218 MatchObjectData match_data;
1220 cbfile = E_CAL_BACKEND_FILE (backend);
1221 priv = cbfile->priv;
1223 d(g_message (G_STRLOC ": Starting query (%s)", e_data_cal_view_get_text (query)));
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;
1232 if (!strcmp (match_data.query, "#t"))
1233 match_data.search_needed = FALSE;
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);
1241 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
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);
1248 g_list_foreach (match_data.obj_list, (GFunc) g_free, NULL);
1249 g_list_free (match_data.obj_list);
1252 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
1256 free_busy_instance (ECalComponent *comp,
1257 time_t instance_start,
1258 time_t instance_end,
1261 icalcomponent *vfb = data;
1263 icalparameter *param;
1264 struct icalperiodtype ipt;
1265 icaltimezone *utc_zone;
1267 utc_zone = icaltimezone_get_utc_timezone ();
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 ();
1273 /* add busy information to the vfb component */
1274 prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
1275 icalproperty_set_freebusy (prop, ipt);
1277 param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
1278 icalproperty_add_parameter (prop, param);
1280 icalcomponent_add_property (vfb, prop);
1285 static icalcomponent *
1286 create_user_free_busy (ECalBackendFile *cbfile, const char *address, const char *cn,
1287 time_t start, time_t end)
1289 ECalBackendFilePrivate *priv;
1292 icaltimezone *utc_zone;
1293 ECalBackendSExp *obj_sexp;
1294 char *query, *iso_start, *iso_end;
1296 priv = cbfile->priv;
1298 /* create the (unique) VFREEBUSY object that we'll return */
1299 vfb = icalcomponent_new_vfreebusy ();
1300 if (address != NULL) {
1302 icalparameter *param;
1304 prop = icalproperty_new_organizer (address);
1305 if (prop != NULL && cn != NULL) {
1306 param = icalparameter_new_cn (cn);
1307 icalproperty_add_parameter (prop, param);
1310 icalcomponent_add_property (vfb, prop);
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));
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);
1329 for (l = priv->comp; l; l = l->next) {
1330 ECalComponent *comp = l->data;
1331 icalcomponent *icalcomp, *vcalendar_comp;
1334 icalcomp = e_cal_component_get_icalcomponent (comp);
1338 /* If the event is TRANSPARENT, skip it. */
1339 prop = icalcomponent_get_first_property (icalcomp,
1340 ICAL_TRANSP_PROPERTY);
1342 icalproperty_transp transp_val = icalproperty_get_transp (prop);
1343 if (transp_val == ICAL_TRANSP_TRANSPARENT ||
1344 transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
1348 if (!e_cal_backend_sexp_match_comp (obj_sexp, l->data, E_CAL_BACKEND (cbfile)))
1351 vcalendar_comp = icalcomponent_get_parent (icalcomp);
1352 e_cal_recur_generate_instances (comp, start, end,
1357 priv->default_zone);
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)
1368 ECalBackendFile *cbfile;
1369 ECalBackendFilePrivate *priv;
1370 gchar *address, *name;
1375 cbfile = E_CAL_BACKEND_FILE (backend);
1376 priv = cbfile->priv;
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);
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);
1394 for (l = users; l != NULL; l = l->next ) {
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);
1406 return GNOME_Evolution_Calendar_Success;
1411 ECalBackendFile *backend;
1412 icalcomponent_kind kind;
1415 } ECalBackendFileComputeChangesData;
1418 e_cal_backend_file_compute_changes_foreach_key (const char *key, gpointer value, gpointer data)
1420 ECalBackendFileComputeChangesData *be_data = data;
1422 if (!lookup_component (be_data->backend, key)) {
1423 ECalComponent *comp;
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);
1429 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
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));
1434 e_xmlhash_remove (be_data->ehash, key);
1438 static ECalBackendSyncStatus
1439 e_cal_backend_file_compute_changes (ECalBackendFile *cbfile, const char *change_id,
1440 GList **adds, GList **modifies, GList **deletes)
1442 ECalBackendFilePrivate *priv;
1445 ECalBackendFileComputeChangesData be_data;
1447 gchar *unescaped_uri;
1449 priv = cbfile->priv;
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))) {
1457 return GNOME_Evolution_Calendar_OtherError;
1462 /* Calculate adds and modifies */
1463 for (i = priv->comp; i != NULL; i = i->next) {
1467 e_cal_component_get_uid (i->data, &uid);
1468 calobj = e_cal_component_get_as_string (i->data);
1470 g_assert (calobj != NULL);
1472 /* check what type of change has occurred, if any */
1473 switch (e_xmlhash_compare (ehash, uid, calobj)) {
1474 case E_XMLHASH_STATUS_SAME:
1476 case E_XMLHASH_STATUS_NOT_FOUND:
1477 *adds = g_list_prepend (*adds, g_strdup (calobj));
1478 e_xmlhash_add (ehash, uid, calobj);
1480 case E_XMLHASH_STATUS_DIFFERENT:
1481 *modifies = g_list_prepend (*modifies, g_strdup (calobj));
1482 e_xmlhash_add (ehash, uid, calobj);
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;
1495 e_xmlhash_foreach_key (ehash, (EXmlHashFunc)e_cal_backend_file_compute_changes_foreach_key, &be_data);
1497 *deletes = be_data.deletes;
1499 e_xmlhash_write (ehash);
1500 e_xmlhash_destroy (ehash);
1502 return GNOME_Evolution_Calendar_Success;
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)
1510 ECalBackendFile *cbfile;
1511 ECalBackendFilePrivate *priv;
1513 cbfile = E_CAL_BACKEND_FILE (backend);
1514 priv = cbfile->priv;
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);
1519 return e_cal_backend_file_compute_changes (cbfile, change_id, adds, modifies, deletes);
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)
1526 /* we just do nothing with the alarm */
1527 return GNOME_Evolution_Calendar_Success;
1530 static icaltimezone *
1531 e_cal_backend_file_internal_get_default_timezone (ECalBackend *backend)
1533 ECalBackendFile *cbfile;
1534 ECalBackendFilePrivate *priv;
1536 cbfile = E_CAL_BACKEND_FILE (backend);
1537 priv = cbfile->priv;
1539 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1541 return priv->default_zone;
1544 static icaltimezone *
1545 e_cal_backend_file_internal_get_timezone (ECalBackend *backend, const char *tzid)
1547 ECalBackendFile *cbfile;
1548 ECalBackendFilePrivate *priv;
1551 cbfile = E_CAL_BACKEND_FILE (backend);
1552 priv = cbfile->priv;
1554 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1556 if (!strcmp (tzid, "UTC"))
1557 zone = icaltimezone_get_utc_timezone ();
1559 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1561 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1568 sanitize_component (ECalBackendFile *cbfile, ECalComponent *comp)
1570 ECalComponentDateTime dt;
1571 icaltimezone *zone, *default_zone;
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
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);
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);
1586 e_cal_component_free_datetime (&dt);
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);
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);
1598 e_cal_component_free_datetime (&dt);
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);
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);
1610 e_cal_component_free_datetime (&dt);
1611 e_cal_component_abort_sequence (comp);
1616 static ECalBackendSyncStatus
1617 e_cal_backend_file_create_object (ECalBackendSync *backend, EDataCal *cal, char **calobj, char **uid)
1619 ECalBackendFile *cbfile;
1620 ECalBackendFilePrivate *priv;
1621 icalcomponent *icalcomp;
1622 ECalComponent *comp;
1623 const char *comp_uid;
1624 struct icaltimetype current;
1626 cbfile = E_CAL_BACKEND_FILE (backend);
1627 priv = cbfile->priv;
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);
1632 /* Parse the icalendar text */
1633 icalcomp = icalparser_parse_string (*calobj);
1635 return GNOME_Evolution_Calendar_InvalidObject;
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;
1644 comp_uid = icalcomponent_get_uid (icalcomp);
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;
1652 /* Create the cal component */
1653 comp = e_cal_component_new ();
1654 e_cal_component_set_icalcomponent (comp, icalcomp);
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, ¤t);
1659 e_cal_component_set_last_modified (comp, ¤t);
1661 /* sanitize the component*/
1662 sanitize_component (cbfile, comp);
1664 /* Add the object */
1665 add_component (cbfile, comp, TRUE);
1670 /* Return the UID and the modified component */
1672 *uid = g_strdup (comp_uid);
1673 *calobj = e_cal_component_get_as_string (comp);
1675 return GNOME_Evolution_Calendar_Success;
1678 static ECalBackendSyncStatus
1679 e_cal_backend_file_modify_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj,
1680 CalObjModType mod, char **old_object)
1682 ECalBackendFile *cbfile;
1683 ECalBackendFilePrivate *priv;
1684 icalcomponent *icalcomp;
1685 const char *comp_uid, *rid;
1687 ECalComponent *comp, *recurrence;
1688 ECalBackendFileObject *obj_data;
1689 struct icaltimetype current;
1691 cbfile = E_CAL_BACKEND_FILE (backend);
1692 priv = cbfile->priv;
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);
1697 /* Parse the icalendar text */
1698 icalcomp = icalparser_parse_string ((char *) calobj);
1700 return GNOME_Evolution_Calendar_InvalidObject;
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;
1709 comp_uid = icalcomponent_get_uid (icalcomp);
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;
1717 /* Create the cal component */
1718 comp = e_cal_component_new ();
1719 e_cal_component_set_icalcomponent (comp, icalcomp);
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, ¤t);
1725 /* sanitize the component*/
1726 sanitize_component (cbfile, comp);
1728 /* handle mod_type */
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;
1737 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
1738 (void **) &real_rid, (void **) &recurrence)) {
1740 *old_object = e_cal_component_get_as_string (recurrence);
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);
1751 g_object_unref (recurrence);
1755 old = e_cal_component_get_as_string (obj_data->full_object);
1757 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
1758 get_rid_icaltime (comp),
1761 new = e_cal_component_get_as_string (obj_data->full_object);
1763 e_cal_backend_notify_object_modified (E_CAL_BACKEND (backend), old, new);
1772 /* add the detached instance */
1773 g_hash_table_insert (obj_data->recurrences,
1774 g_strdup (e_cal_component_get_recurid_as_string (comp)),
1776 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
1778 case CALOBJ_MOD_THISANDPRIOR :
1780 case CALOBJ_MOD_THISANDFUTURE :
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 */
1787 *old_object = e_cal_component_get_as_string (obj_data->full_object);
1789 remove_component (cbfile, obj_data->full_object);
1791 /* Add the new object */
1792 add_component (cbfile, comp, TRUE);
1798 return GNOME_Evolution_Calendar_Success;
1802 remove_instance (ECalBackendFile *cbfile, ECalBackendFileObject *obj_data, const char *rid)
1805 ECalComponent *comp;
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);
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);
1826 g_object_unref (comp);
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);
1836 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
1837 icaltime_from_string (rid), CALOBJ_MOD_THIS);
1839 /* add the modified object to the beginning of the list,
1840 so that it's always before any detached instance we
1842 cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
1846 ECalBackendFile *cbfile;
1847 ECalBackendFileObject *obj_data;
1850 } RemoveRecurrenceData;
1853 remove_object_instance_cb (gpointer key, gpointer value, gpointer user_data)
1855 time_t fromtt, instancett;
1858 ECalComponent *instance = value;
1859 RemoveRecurrenceData *rrdata = user_data;
1861 fromtt = icaltime_as_timet (icaltime_from_string (rrdata->rid));
1862 instancett = icaltime_as_timet (get_rid_icaltime (instance));
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);
1872 rrdata->obj_data->recurrences_list = g_list_remove (rrdata->obj_data->recurrences_list, instance);
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);
1881 g_object_unref (instance);
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)
1896 ECalBackendFile *cbfile;
1897 ECalBackendFilePrivate *priv;
1898 ECalBackendFileObject *obj_data;
1899 ECalComponent *comp;
1901 RemoveRecurrenceData rrdata;
1903 cbfile = E_CAL_BACKEND_FILE (backend);
1904 priv = cbfile->priv;
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);
1909 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1911 return GNOME_Evolution_Calendar_ObjectNotFound;
1913 comp = obj_data->full_object;
1916 case CALOBJ_MOD_ALL :
1917 *object = e_cal_component_get_as_string (comp);
1918 remove_component (cbfile, comp);
1920 case CALOBJ_MOD_THIS :
1922 return GNOME_Evolution_Calendar_ObjectNotFound;
1924 remove_instance (cbfile, obj_data, rid);
1926 case CALOBJ_MOD_THISANDPRIOR :
1927 case CALOBJ_MOD_THISANDFUTURE :
1929 return GNOME_Evolution_Calendar_ObjectNotFound;
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);
1936 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (comp),
1937 icaltime_from_string (rid), mod);
1939 /* now remove all detached instances */
1940 rrdata.cbfile = cbfile;
1941 rrdata.obj_data = obj_data;
1944 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
1946 /* add the modified object to the beginning of the list,
1947 so that it's always before any detached instance we
1949 priv->comp = g_list_prepend (priv->comp, comp);
1955 return GNOME_Evolution_Calendar_Success;
1959 cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp)
1961 ECalComponent *old_comp;
1963 /* Find the old version of the component. */
1964 old_comp = lookup_component (cbfile, icalcomponent_get_uid (icalcomp));
1969 remove_component (cbfile, old_comp);
1978 } ECalBackendFileTzidData;
1981 check_tzids (icalparameter *param, void *data)
1983 ECalBackendFileTzidData *tzdata = data;
1986 tzid = icalparameter_get_tzid (param);
1987 if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
1988 tzdata->found = FALSE;
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)
1995 ECalBackendFile *cbfile;
1996 ECalBackendFilePrivate *priv;
1997 icalcomponent *toplevel_comp, *icalcomp = NULL;
1998 icalcomponent_kind kind;
1999 icalproperty_method method;
2000 icalcomponent *subcomp;
2002 ECalComponent *comp;
2003 struct icaltimetype current;
2004 ECalBackendFileTzidData tzdata;
2005 ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;
2007 cbfile = E_CAL_BACKEND_FILE (backend);
2008 priv = cbfile->priv;
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);
2013 /* Pull the component from the string and ensure that it is sane */
2014 toplevel_comp = icalparser_parse_string ((char *) calobj);
2016 return GNOME_Evolution_Calendar_InvalidObject;
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);
2026 method = icalcomponent_get_method (toplevel_comp);
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);
2031 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
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);
2039 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
2042 /* First we make sure all the components are usuable */
2044 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
2046 /* We ignore anything except VEVENT, VTODO and VJOURNAL
2048 icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
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);
2057 if (!tzdata.found) {
2058 status = GNOME_Evolution_Calendar_InvalidObject;
2062 if (!icalcomponent_get_uid (subcomp)) {
2063 status = GNOME_Evolution_Calendar_InvalidObject;
2067 comps = g_list_prepend (comps, subcomp);
2074 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
2077 /* Now we manipulate the components we care about */
2078 for (l = comps; l; l = l->next) {
2079 const char *uid, *rid;
2084 /* Create the cal component */
2085 comp = e_cal_component_new ();
2086 e_cal_component_set_icalcomponent (comp, subcomp);
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, ¤t);
2091 e_cal_component_set_last_modified (comp, ¤t);
2093 /* sanitize the component*/
2094 sanitize_component (cbfile, comp);
2096 e_cal_component_get_uid (comp, &uid);
2097 rid = e_cal_component_get_recurid_as_string (comp);
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) {
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)
2112 e_cal_backend_notify_object_modified (E_CAL_BACKEND (backend), old_object, calobj);
2114 g_free (old_object);
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)
2123 e_cal_backend_notify_object_created (E_CAL_BACKEND (backend), calobj);
2126 case ICAL_METHOD_ADD:
2127 /* FIXME This should be doable once all the recurid stuff is done */
2129 case ICAL_METHOD_COUNTER:
2130 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2133 case ICAL_METHOD_DECLINECOUNTER:
2134 status = GNOME_Evolution_Calendar_UnsupportedMethod;
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);
2142 /* remove the component from the toplevel VCALENDAR */
2143 icalcomponent_remove_component (toplevel_comp, subcomp);
2144 icalcomponent_free (subcomp);
2148 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2153 g_list_free (comps);
2155 /* Merge the iCalendar components with our existing VCALENDAR,
2156 resolving any conflicting TZIDs. */
2157 icalcomponent_merge_component (priv->icalcomp, toplevel_comp);
2162 g_hash_table_destroy (tzdata.zones);
2167 static ECalBackendSyncStatus
2168 e_cal_backend_file_send_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj, GList **users,
2169 char **modified_calobj)
2172 *modified_calobj = g_strdup (calobj);
2174 return GNOME_Evolution_Calendar_Success;
2177 /* Object initialization function for the file backend */
2179 e_cal_backend_file_init (ECalBackendFile *cbfile, ECalBackendFileClass *class)
2181 ECalBackendFilePrivate *priv;
2183 priv = g_new0 (ECalBackendFilePrivate, 1);
2184 cbfile->priv = priv;
2187 priv->file_name = g_strdup ("calendar.ics");
2188 priv->read_only = FALSE;
2189 priv->icalcomp = NULL;
2190 priv->comp_uid_hash = NULL;
2193 /* The timezone defaults to UTC. */
2194 priv->default_zone = icaltimezone_get_utc_timezone ();
2197 /* Class initialization function for the file backend */
2199 e_cal_backend_file_class_init (ECalBackendFileClass *class)
2201 GObjectClass *object_class;
2202 ECalBackendClass *backend_class;
2203 ECalBackendSyncClass *sync_class;
2205 object_class = (GObjectClass *) class;
2206 backend_class = (ECalBackendClass *) class;
2207 sync_class = (ECalBackendSyncClass *) class;
2209 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
2211 object_class->dispose = e_cal_backend_file_dispose;
2212 object_class->finalize = e_cal_backend_file_finalize;
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;
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;
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;
2247 * e_cal_backend_file_get_type:
2250 * Registers the #ECalBackendFile class if necessary, and returns the type ID
2253 * Return value: The type ID of the #ECalBackendFile class.
2256 e_cal_backend_file_get_type (void)
2258 static GType e_cal_backend_file_type = 0;
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,
2267 sizeof (ECalBackendFile),
2269 (GInstanceInitFunc) e_cal_backend_file_init
2271 e_cal_backend_file_type = g_type_register_static (E_TYPE_CAL_BACKEND_SYNC,
2272 "ECalBackendFile", &info, 0);
2275 return e_cal_backend_file_type;
2279 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile, const char *file_name)
2281 ECalBackendFilePrivate *priv;
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);
2287 priv = cbfile->priv;
2289 if (priv->file_name)
2290 g_free (priv->file_name);
2292 priv->file_name = g_strdup (file_name);
2296 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
2298 ECalBackendFilePrivate *priv;
2300 g_return_val_if_fail (cbfile != NULL, NULL);
2301 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
2303 priv = cbfile->priv;
2305 return priv->file_name;
2308 ECalBackendSyncStatus
2309 e_cal_backend_file_reload (ECalBackendFile *cbfile)
2311 ECalBackendFilePrivate *priv;
2313 ECalBackendSyncStatus status;
2315 priv = cbfile->priv;
2317 str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
2319 return GNOME_Evolution_Calendar_OtherError;
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;
2326 status = GNOME_Evolution_Calendar_NoSuchCal;