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 */
81 static void e_cal_backend_file_dispose (GObject *object);
82 static void e_cal_backend_file_finalize (GObject *object);
84 static ECalBackendSyncClass *parent_class;
88 /* g_hash_table_foreach() callback to destroy recurrences in the hash table */
90 free_recurrence (gpointer key, gpointer value, gpointer data)
93 ECalComponent *comp = value;
96 g_object_unref (comp);
99 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
101 free_object (gpointer key, gpointer value, gpointer data)
103 ECalBackendFileObject *obj_data = value;
105 g_object_unref (obj_data->full_object);
106 g_hash_table_foreach (obj_data->recurrences, (GHFunc) free_recurrence, NULL);
107 g_hash_table_destroy (obj_data->recurrences);
108 g_list_free (obj_data->recurrences_list);
113 /* Saves the calendar data */
115 save (ECalBackendFile *cbfile)
117 ECalBackendFilePrivate *priv;
118 GnomeVFSURI *uri, *backup_uri;
119 GnomeVFSHandle *handle = NULL;
120 GnomeVFSResult result = GNOME_VFS_ERROR_BAD_FILE;
121 GnomeVFSFileSize out;
122 gchar *tmp, *backup_uristr;
126 g_assert (priv->uri != NULL);
127 g_assert (priv->icalcomp != NULL);
129 uri = gnome_vfs_uri_new (priv->uri);
131 goto error_malformed_uri;
133 /* save calendar to backup file */
134 tmp = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
136 gnome_vfs_uri_unref (uri);
137 goto error_malformed_uri;
140 backup_uristr = g_strconcat (tmp, "~", NULL);
141 backup_uri = gnome_vfs_uri_new (backup_uristr);
144 g_free (backup_uristr);
147 gnome_vfs_uri_unref (uri);
148 goto error_malformed_uri;
151 result = gnome_vfs_create_uri (&handle, backup_uri,
152 GNOME_VFS_OPEN_WRITE,
154 if (result != GNOME_VFS_OK) {
155 gnome_vfs_uri_unref (uri);
156 gnome_vfs_uri_unref (backup_uri);
160 buf = icalcomponent_as_ical_string (priv->icalcomp);
161 result = gnome_vfs_write (handle, buf, strlen (buf) * sizeof (char), &out);
162 gnome_vfs_close (handle);
163 if (result != GNOME_VFS_OK) {
164 gnome_vfs_uri_unref (uri);
165 gnome_vfs_uri_unref (backup_uri);
169 /* now copy the temporary file to the real file */
170 result = gnome_vfs_move_uri (backup_uri, uri, TRUE);
172 gnome_vfs_uri_unref (uri);
173 gnome_vfs_uri_unref (backup_uri);
174 if (result != GNOME_VFS_OK)
180 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
181 _("Can't save calendar data: Malformed URI."));
185 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), gnome_vfs_result_to_string (result));
190 free_calendar_components (GHashTable *comp_uid_hash, icalcomponent *top_icomp)
193 g_hash_table_foreach (comp_uid_hash, (GHFunc) free_object, NULL);
194 g_hash_table_destroy (comp_uid_hash);
198 icalcomponent_free (top_icomp);
203 free_calendar_data (ECalBackendFile *cbfile)
205 ECalBackendFilePrivate *priv;
209 free_calendar_components (priv->comp_uid_hash, priv->icalcomp);
210 priv->comp_uid_hash = NULL;
211 priv->icalcomp = NULL;
213 g_list_free (priv->comp);
217 /* Dispose handler for the file backend */
219 e_cal_backend_file_dispose (GObject *object)
221 ECalBackendFile *cbfile;
222 ECalBackendFilePrivate *priv;
224 cbfile = E_CAL_BACKEND_FILE (object);
227 /* Save if necessary */
229 free_calendar_data (cbfile);
231 if (G_OBJECT_CLASS (parent_class)->dispose)
232 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
235 /* Finalize handler for the file backend */
237 e_cal_backend_file_finalize (GObject *object)
239 ECalBackendFile *cbfile;
240 ECalBackendFilePrivate *priv;
242 g_return_if_fail (object != NULL);
243 g_return_if_fail (E_IS_CAL_BACKEND_FILE (object));
245 cbfile = E_CAL_BACKEND_FILE (object);
258 if (G_OBJECT_CLASS (parent_class)->finalize)
259 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
264 /* Looks up a component by its UID on the backend's component hash table */
265 static ECalComponent *
266 lookup_component (ECalBackendFile *cbfile, const char *uid)
268 ECalBackendFilePrivate *priv;
269 ECalBackendFileObject *obj_data;
273 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
274 return obj_data ? obj_data->full_object : NULL;
279 /* Calendar backend methods */
281 /* Is_read_only handler for the file backend */
282 static ECalBackendSyncStatus
283 e_cal_backend_file_is_read_only (ECalBackendSync *backend, EDataCal *cal, gboolean *read_only)
285 ECalBackendFile *cbfile = (ECalBackendFile *) backend;
287 *read_only = cbfile->priv->read_only;
289 return GNOME_Evolution_Calendar_Success;
292 /* Get_email_address handler for the file backend */
293 static ECalBackendSyncStatus
294 e_cal_backend_file_get_cal_address (ECalBackendSync *backend, EDataCal *cal, char **address)
296 /* A file backend has no particular email address associated
297 * with it (although that would be a useful feature some day).
301 return GNOME_Evolution_Calendar_Success;
304 static ECalBackendSyncStatus
305 e_cal_backend_file_get_ldap_attribute (ECalBackendSync *backend, EDataCal *cal, char **attribute)
309 return GNOME_Evolution_Calendar_Success;
312 static ECalBackendSyncStatus
313 e_cal_backend_file_get_alarm_email_address (ECalBackendSync *backend, EDataCal *cal, char **address)
315 /* A file backend has no particular email address associated
316 * with it (although that would be a useful feature some day).
320 return GNOME_Evolution_Calendar_Success;
323 static ECalBackendSyncStatus
324 e_cal_backend_file_get_static_capabilities (ECalBackendSync *backend, EDataCal *cal, char **capabilities)
326 *capabilities = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS);
328 return GNOME_Evolution_Calendar_Success;
331 /* function to resolve timezones */
332 static icaltimezone *
333 resolve_tzid (const char *tzid, gpointer user_data)
335 icalcomponent *vcalendar_comp = user_data;
337 if (!tzid || !tzid[0])
339 else if (!strcmp (tzid, "UTC"))
340 return icaltimezone_get_utc_timezone ();
342 return icalcomponent_get_timezone (vcalendar_comp, tzid);
345 /* Checks if the specified component has a duplicated UID and if so changes it */
347 check_dup_uid (ECalBackendFile *cbfile, ECalComponent *comp)
349 ECalBackendFilePrivate *priv;
350 ECalBackendFileObject *obj_data;
356 e_cal_component_get_uid (comp, &uid);
358 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
360 return; /* Everything is fine */
362 g_message ("check_dup_uid(): Got object with duplicated UID `%s', changing it...", uid);
364 new_uid = e_cal_component_gen_uid ();
365 e_cal_component_set_uid (comp, new_uid);
368 /* FIXME: I think we need to reset the SEQUENCE property and reset the
369 * CREATED/DTSTAMP/LAST-MODIFIED.
375 static struct icaltimetype
376 get_rid_icaltime (ECalComponent *comp)
378 ECalComponentRange range;
379 struct icaltimetype tt;
381 e_cal_component_get_recurid (comp, &range);
382 if (!range.datetime.value)
383 return icaltime_null_time ();
384 tt = *range.datetime.value;
385 e_cal_component_free_range (&range);
390 /* Tries to add an icalcomponent to the file backend. We only store the objects
391 * of the types we support; all others just remain in the toplevel component so
392 * that we don't lose them.
395 add_component (ECalBackendFile *cbfile, ECalComponent *comp, gboolean add_to_toplevel)
397 ECalBackendFilePrivate *priv;
398 ECalBackendFileObject *obj_data;
404 if (e_cal_component_is_instance (comp)) { /* FIXME: more checks needed, to detect detached instances */
407 e_cal_component_get_uid (comp, &uid);
409 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
411 g_warning (G_STRLOC ": Got an instance of a non-existing component");
415 rid = e_cal_component_get_recurid_as_string (comp);
416 if (g_hash_table_lookup (obj_data->recurrences, rid)) {
417 g_warning (G_STRLOC ": Tried to adding an already existing recurrence");
421 g_hash_table_insert (obj_data->recurrences, g_strdup (rid), comp);
422 /* FIXME: sort the recurrences */
423 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
425 /* Ensure that the UID is unique; some broken implementations spit
426 * components with duplicated UIDs.
428 check_dup_uid (cbfile, comp);
429 e_cal_component_get_uid (comp, &uid);
431 obj_data = g_new0 (ECalBackendFileObject, 1);
432 obj_data->full_object = comp;
433 obj_data->recurrences = g_hash_table_new (g_str_hash, g_str_equal);
435 g_hash_table_insert (priv->comp_uid_hash, (gpointer) uid, obj_data);
438 priv->comp = g_list_prepend (priv->comp, comp);
440 /* Put the object in the toplevel component if required */
442 if (add_to_toplevel) {
443 icalcomponent *icalcomp;
445 icalcomp = e_cal_component_get_icalcomponent (comp);
446 g_assert (icalcomp != NULL);
448 icalcomponent_add_component (priv->icalcomp, icalcomp);
451 /* Update the set of categories */
452 e_cal_component_get_categories_list (comp, &categories);
453 e_cal_backend_ref_categories (E_CAL_BACKEND (cbfile), categories);
454 e_cal_component_free_categories_list (categories);
457 /* g_hash_table_foreach() callback to remove recurrences from the calendar */
459 remove_recurrence_cb (gpointer key, gpointer value, gpointer data)
463 icalcomponent *icalcomp;
464 ECalBackendFilePrivate *priv;
465 ECalComponent *comp = value;
466 ECalBackendFile *cbfile = data;
470 /* remove the recurrence from the top-level calendar */
471 icalcomp = e_cal_component_get_icalcomponent (comp);
472 g_assert (icalcomp != NULL);
474 icalcomponent_remove_component (priv->icalcomp, icalcomp);
476 /* remove it from our mapping */
477 l = g_list_find (priv->comp, comp);
478 priv->comp = g_list_delete_link (priv->comp, l);
480 /* update the set of categories */
481 e_cal_component_get_categories_list (comp, &categories);
482 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
483 e_cal_component_free_categories_list (categories);
486 /* Removes a component from the backend's hash and lists. Does not perform
487 * notification on the clients. Also removes the component from the toplevel
491 remove_component (ECalBackendFile *cbfile, ECalComponent *comp)
493 ECalBackendFilePrivate *priv;
494 icalcomponent *icalcomp;
498 ECalBackendFileObject *obj_data;
502 /* Remove the icalcomp from the toplevel */
504 icalcomp = e_cal_component_get_icalcomponent (comp);
505 g_assert (icalcomp != NULL);
507 icalcomponent_remove_component (priv->icalcomp, icalcomp);
509 /* Remove it from our mapping */
511 e_cal_component_get_uid (comp, &uid);
512 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
516 g_hash_table_remove (priv->comp_uid_hash, uid);
518 l = g_list_find (priv->comp, comp);
519 g_assert (l != NULL);
520 priv->comp = g_list_delete_link (priv->comp, l);
522 /* remove the recurrences also */
523 g_hash_table_foreach (obj_data->recurrences, (GHFunc) remove_recurrence_cb, cbfile);
525 /* Update the set of categories */
526 e_cal_component_get_categories_list (comp, &categories);
527 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
528 e_cal_component_free_categories_list (categories);
530 free_object ((gpointer) uid, (gpointer) obj_data, NULL);
533 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
535 scan_vcalendar (ECalBackendFile *cbfile)
537 ECalBackendFilePrivate *priv;
541 g_assert (priv->icalcomp != NULL);
542 g_assert (priv->comp_uid_hash != NULL);
544 for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_ANY_COMPONENT);
545 icalcompiter_deref (&iter) != NULL;
546 icalcompiter_next (&iter)) {
547 icalcomponent *icalcomp;
548 icalcomponent_kind kind;
551 icalcomp = icalcompiter_deref (&iter);
553 kind = icalcomponent_isa (icalcomp);
555 if (!(kind == ICAL_VEVENT_COMPONENT
556 || kind == ICAL_VTODO_COMPONENT
557 || kind == ICAL_VJOURNAL_COMPONENT))
560 comp = e_cal_component_new ();
562 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
565 add_component (cbfile, comp, FALSE);
570 get_uri_string_for_gnome_vfs (ECalBackend *backend)
572 ECalBackendFile *cbfile;
573 ECalBackendFilePrivate *priv;
574 const char *master_uri;
575 char *full_uri, *str_uri;
578 cbfile = E_CAL_BACKEND_FILE (backend);
581 master_uri = e_cal_backend_get_uri (backend);
583 /* FIXME Check the error conditions a little more elegantly here */
584 if (g_strrstr ("tasks.ics", master_uri) || g_strrstr ("calendar.ics", master_uri)) {
585 g_warning (G_STRLOC ": Existing file name %s", master_uri);
590 full_uri = g_strdup_printf ("%s%s%s", master_uri, G_DIR_SEPARATOR_S, priv->file_name);
591 uri = gnome_vfs_uri_new (full_uri);
597 str_uri = gnome_vfs_uri_to_string (uri,
598 (GNOME_VFS_URI_HIDE_USER_NAME
599 | GNOME_VFS_URI_HIDE_PASSWORD
600 | GNOME_VFS_URI_HIDE_HOST_NAME
601 | GNOME_VFS_URI_HIDE_HOST_PORT
602 | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD));
603 gnome_vfs_uri_unref (uri);
605 if (!str_uri || !strlen (str_uri)) {
614 /* Parses an open iCalendar file and loads it into the backend */
615 static ECalBackendSyncStatus
616 open_cal (ECalBackendFile *cbfile, const char *uristr)
618 ECalBackendFilePrivate *priv;
619 icalcomponent *icalcomp;
623 icalcomp = e_cal_util_parse_ics_file (uristr);
625 return GNOME_Evolution_Calendar_OtherError;
627 /* FIXME: should we try to demangle XROOT components and
628 * individual components as well?
631 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
632 icalcomponent_free (icalcomp);
634 return GNOME_Evolution_Calendar_OtherError;
637 priv->icalcomp = icalcomp;
638 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
640 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
641 scan_vcalendar (cbfile);
643 return GNOME_Evolution_Calendar_Success;
648 ECalBackend *backend;
649 GHashTable *old_uid_hash;
650 GHashTable *new_uid_hash;
655 notify_removals_cb (gpointer key, gpointer value, gpointer data)
657 BackendDeltaContext *context = data;
658 const gchar *uid = key;
659 ECalBackendFileObject *old_obj_data = value;
661 if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
662 icalcomponent *old_icomp;
665 /* Object was removed */
667 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
671 old_obj_str = icalcomponent_as_ical_string (old_icomp);
675 e_cal_backend_notify_object_removed (context->backend, uid, old_obj_str);
680 notify_adds_modifies_cb (gpointer key, gpointer value, gpointer data)
682 BackendDeltaContext *context = data;
683 const gchar *uid = key;
684 ECalBackendFileObject *new_obj_data = value;
685 ECalBackendFileObject *old_obj_data;
686 icalcomponent *old_icomp, *new_icomp;
687 gchar *old_obj_str, *new_obj_str;
689 old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
692 /* Object was added */
694 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
698 new_obj_str = icalcomponent_as_ical_string (new_icomp);
702 e_cal_backend_notify_object_created (context->backend, new_obj_str);
704 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
705 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
706 if (!old_icomp || !new_icomp)
709 old_obj_str = icalcomponent_as_ical_string (old_icomp);
710 new_obj_str = icalcomponent_as_ical_string (new_icomp);
711 if (!old_obj_str || !new_obj_str)
714 if (strcmp (old_obj_str, new_obj_str)) {
715 /* Object was modified */
717 e_cal_backend_notify_object_modified (context->backend, old_obj_str, new_obj_str);
723 notify_changes (ECalBackendFile *cbfile, GHashTable *old_uid_hash, GHashTable *new_uid_hash)
725 BackendDeltaContext context;
727 context.backend = E_CAL_BACKEND (cbfile);
728 context.old_uid_hash = old_uid_hash;
729 context.new_uid_hash = new_uid_hash;
731 g_hash_table_foreach (old_uid_hash, (GHFunc) notify_removals_cb, &context);
732 g_hash_table_foreach (new_uid_hash, (GHFunc) notify_adds_modifies_cb, &context);
735 static ECalBackendSyncStatus
736 reload_cal (ECalBackendFile *cbfile, const char *uristr)
738 ECalBackendFilePrivate *priv;
739 icalcomponent *icalcomp, *icalcomp_old;
740 GHashTable *comp_uid_hash_old;
744 icalcomp = e_cal_util_parse_ics_file (uristr);
746 return GNOME_Evolution_Calendar_OtherError;
748 /* FIXME: should we try to demangle XROOT components and
749 * individual components as well?
752 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
753 icalcomponent_free (icalcomp);
755 return GNOME_Evolution_Calendar_OtherError;
758 /* Keep old data for comparison - free later */
760 icalcomp_old = priv->icalcomp;
761 priv->icalcomp = NULL;
763 comp_uid_hash_old = priv->comp_uid_hash;
764 priv->comp_uid_hash = NULL;
766 /* Load new calendar */
768 free_calendar_data (cbfile);
770 priv->icalcomp = icalcomp;
772 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
773 scan_vcalendar (cbfile);
775 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
777 /* Compare old and new versions of calendar */
779 notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
783 free_calendar_components (comp_uid_hash_old, icalcomp_old);
784 return GNOME_Evolution_Calendar_Success;
787 static ECalBackendSyncStatus
788 create_cal (ECalBackendFile *cbfile, const char *uristr)
791 ECalBackendFilePrivate *priv;
795 /* Create the directory to contain the file */
796 dirname = g_path_get_dirname (uristr);
797 if (e_util_mkdir_hier (dirname, 0700) != 0) {
799 return GNOME_Evolution_Calendar_NoSuchCal;
804 /* Create the new calendar information */
805 priv->icalcomp = e_cal_util_new_top_level ();
807 /* Create our internal data */
808 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
810 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
814 return GNOME_Evolution_Calendar_Success;
818 get_uri_string (ECalBackend *backend)
820 gchar *str_uri, *full_uri;
822 str_uri = get_uri_string_for_gnome_vfs (backend);
823 full_uri = gnome_vfs_unescape_string (str_uri, "");
829 /* Open handler for the file backend */
830 static ECalBackendSyncStatus
831 e_cal_backend_file_open (ECalBackendSync *backend, EDataCal *cal, gboolean only_if_exists,
832 const char *username, const char *password)
834 ECalBackendFile *cbfile;
835 ECalBackendFilePrivate *priv;
837 ECalBackendSyncStatus status;
839 cbfile = E_CAL_BACKEND_FILE (backend);
842 /* Claim a succesful open if we are already open */
843 if (priv->uri && priv->comp_uid_hash)
844 return GNOME_Evolution_Calendar_Success;
846 str_uri = get_uri_string (E_CAL_BACKEND (backend));
848 return GNOME_Evolution_Calendar_OtherError;
850 if (access (str_uri, R_OK) == 0) {
851 status = open_cal (cbfile, str_uri);
852 if (access (str_uri, W_OK) != 0)
853 priv->read_only = TRUE;
856 status = GNOME_Evolution_Calendar_NoSuchCal;
858 status = create_cal (cbfile, str_uri);
866 static ECalBackendSyncStatus
867 e_cal_backend_file_remove (ECalBackendSync *backend, EDataCal *cal)
869 ECalBackendFile *cbfile;
870 ECalBackendFilePrivate *priv;
871 char *str_uri, *dirname;
874 GError *error = NULL;
877 cbfile = E_CAL_BACKEND_FILE (backend);
880 str_uri = get_uri_string (E_CAL_BACKEND (backend));
882 return GNOME_Evolution_Calendar_OtherError;
884 if (access (str_uri, W_OK) != 0) {
887 return GNOME_Evolution_Calendar_PermissionDenied;
890 /* remove all files in the directory */
891 dirname = g_path_get_dirname (str_uri);
892 dir = g_dir_open (dirname, 0, &error);
897 return GNOME_Evolution_Calendar_PermissionDenied;
900 while ((fname = g_dir_read_name (dir))) {
903 full_path = g_build_filename (dirname, fname, NULL);
904 if (unlink (full_path) != 0) {
910 return GNOME_Evolution_Calendar_OtherError;
916 /* remove the directory itself */
917 success = rmdir (dirname) == 0;
923 return success ? GNOME_Evolution_Calendar_Success : GNOME_Evolution_Calendar_OtherError;
926 /* is_loaded handler for the file backend */
928 e_cal_backend_file_is_loaded (ECalBackend *backend)
930 ECalBackendFile *cbfile;
931 ECalBackendFilePrivate *priv;
933 cbfile = E_CAL_BACKEND_FILE (backend);
936 return (priv->icalcomp != NULL);
939 /* is_remote handler for the file backend */
941 e_cal_backend_file_get_mode (ECalBackend *backend)
943 ECalBackendFile *cbfile;
944 ECalBackendFilePrivate *priv;
946 cbfile = E_CAL_BACKEND_FILE (backend);
949 return CAL_MODE_LOCAL;
952 /* Set_mode handler for the file backend */
954 e_cal_backend_file_set_mode (ECalBackend *backend, CalMode mode)
956 e_cal_backend_notify_mode (backend,
957 GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
958 GNOME_Evolution_Calendar_MODE_LOCAL);
962 static ECalBackendSyncStatus
963 e_cal_backend_file_get_default_object (ECalBackendSync *backend, EDataCal *cal, char **object)
967 comp = e_cal_component_new ();
969 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
970 case ICAL_VEVENT_COMPONENT:
971 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
973 case ICAL_VTODO_COMPONENT:
974 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
976 case ICAL_VJOURNAL_COMPONENT:
977 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
980 g_object_unref (comp);
981 return GNOME_Evolution_Calendar_ObjectNotFound;
984 *object = e_cal_component_get_as_string (comp);
985 g_object_unref (comp);
987 return GNOME_Evolution_Calendar_Success;
990 /* Get_object_component handler for the file backend */
991 static ECalBackendSyncStatus
992 e_cal_backend_file_get_object (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *rid, char **object)
994 ECalBackendFile *cbfile;
995 ECalBackendFilePrivate *priv;
996 ECalBackendFileObject *obj_data;
997 ECalComponent *comp = NULL;
998 gboolean free_comp = FALSE;
1000 cbfile = E_CAL_BACKEND_FILE (backend);
1001 priv = cbfile->priv;
1003 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
1004 g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1005 g_assert (priv->comp_uid_hash != NULL);
1007 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1009 return GNOME_Evolution_Calendar_ObjectNotFound;
1012 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1014 icalcomponent *icalcomp;
1015 struct icaltimetype itt;
1017 itt = icaltime_from_string (rid);
1018 icalcomp = e_cal_util_construct_instance (
1019 e_cal_component_get_icalcomponent (obj_data->full_object),
1022 return GNOME_Evolution_Calendar_ObjectNotFound;
1024 comp = e_cal_component_new ();
1026 e_cal_component_set_icalcomponent (comp, icalcomp);
1029 comp = obj_data->full_object;
1032 return GNOME_Evolution_Calendar_ObjectNotFound;
1034 *object = e_cal_component_get_as_string (comp);
1037 g_object_unref (comp);
1039 return GNOME_Evolution_Calendar_Success;
1042 /* Get_timezone_object handler for the file backend */
1043 static ECalBackendSyncStatus
1044 e_cal_backend_file_get_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid, char **object)
1046 ECalBackendFile *cbfile;
1047 ECalBackendFilePrivate *priv;
1049 icalcomponent *icalcomp;
1051 cbfile = E_CAL_BACKEND_FILE (backend);
1052 priv = cbfile->priv;
1054 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1055 g_return_val_if_fail (tzid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1057 if (!strcmp (tzid, "UTC")) {
1058 zone = icaltimezone_get_utc_timezone ();
1060 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1062 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1064 return GNOME_Evolution_Calendar_ObjectNotFound;
1068 icalcomp = icaltimezone_get_component (zone);
1070 return GNOME_Evolution_Calendar_InvalidObject;
1072 *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1074 return GNOME_Evolution_Calendar_Success;
1077 /* Add_timezone handler for the file backend */
1078 static ECalBackendSyncStatus
1079 e_cal_backend_file_add_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj)
1081 icalcomponent *tz_comp;
1082 ECalBackendFile *cbfile;
1083 ECalBackendFilePrivate *priv;
1085 cbfile = (ECalBackendFile *) backend;
1087 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), GNOME_Evolution_Calendar_OtherError);
1088 g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
1090 priv = cbfile->priv;
1092 tz_comp = icalparser_parse_string (tzobj);
1094 return GNOME_Evolution_Calendar_InvalidObject;
1096 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
1099 zone = icaltimezone_new ();
1100 icaltimezone_set_component (zone, tz_comp);
1101 if (!icalcomponent_get_timezone (priv->icalcomp,
1102 icaltimezone_get_tzid (zone))) {
1103 icalcomponent_add_component (priv->icalcomp, tz_comp);
1107 icaltimezone_free (zone, 1);
1110 return GNOME_Evolution_Calendar_Success;
1114 static ECalBackendSyncStatus
1115 e_cal_backend_file_set_default_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid)
1117 ECalBackendFile *cbfile;
1118 ECalBackendFilePrivate *priv;
1121 cbfile = E_CAL_BACKEND_FILE (backend);
1122 priv = cbfile->priv;
1124 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1126 /* Look up the VTIMEZONE in our icalcomponent. */
1127 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1129 return GNOME_Evolution_Calendar_ObjectNotFound;
1131 /* Set the default timezone to it. */
1132 priv->default_zone = zone;
1134 return GNOME_Evolution_Calendar_Success;
1139 gboolean search_needed;
1141 ECalBackendSExp *obj_sexp;
1142 ECalBackend *backend;
1143 icaltimezone *default_zone;
1147 match_recurrence_sexp (gpointer key, gpointer value, gpointer data)
1149 ECalComponent *comp = value;
1150 MatchObjectData *match_data = data;
1152 if ((!match_data->search_needed) ||
1153 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
1154 match_data->obj_list = g_list_append (match_data->obj_list,
1155 e_cal_component_get_as_string (comp));
1160 match_object_sexp (gpointer key, gpointer value, gpointer data)
1162 ECalBackendFileObject *obj_data = value;
1163 MatchObjectData *match_data = data;
1165 if ((!match_data->search_needed) ||
1166 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, obj_data->full_object, match_data->backend))) {
1167 match_data->obj_list = g_list_append (match_data->obj_list,
1168 e_cal_component_get_as_string (obj_data->full_object));
1170 /* match also recurrences */
1171 g_hash_table_foreach (obj_data->recurrences,
1172 (GHFunc) match_recurrence_sexp,
1177 /* Get_objects_in_range handler for the file backend */
1178 static ECalBackendSyncStatus
1179 e_cal_backend_file_get_object_list (ECalBackendSync *backend, EDataCal *cal, const char *sexp, GList **objects)
1181 ECalBackendFile *cbfile;
1182 ECalBackendFilePrivate *priv;
1183 MatchObjectData match_data;
1185 cbfile = E_CAL_BACKEND_FILE (backend);
1186 priv = cbfile->priv;
1188 g_message (G_STRLOC ": Getting object list (%s)", sexp);
1190 match_data.search_needed = TRUE;
1191 match_data.query = sexp;
1192 match_data.obj_list = NULL;
1193 match_data.backend = E_CAL_BACKEND (backend);
1194 match_data.default_zone = priv->default_zone;
1196 if (!strcmp (sexp, "#t"))
1197 match_data.search_needed = FALSE;
1199 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1200 if (!match_data.obj_sexp)
1201 return GNOME_Evolution_Calendar_InvalidQuery;
1203 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1205 *objects = match_data.obj_list;
1207 return GNOME_Evolution_Calendar_Success;
1210 /* get_query handler for the file backend */
1212 e_cal_backend_file_start_query (ECalBackend *backend, EDataCalView *query)
1214 ECalBackendFile *cbfile;
1215 ECalBackendFilePrivate *priv;
1216 MatchObjectData match_data;
1218 cbfile = E_CAL_BACKEND_FILE (backend);
1219 priv = cbfile->priv;
1221 g_message (G_STRLOC ": Starting query (%s)", e_data_cal_view_get_text (query));
1223 /* try to match all currently existing objects */
1224 match_data.search_needed = TRUE;
1225 match_data.query = e_data_cal_view_get_text (query);
1226 match_data.obj_list = NULL;
1227 match_data.backend = backend;
1228 match_data.default_zone = priv->default_zone;
1230 if (!strcmp (match_data.query, "#t"))
1231 match_data.search_needed = FALSE;
1233 match_data.obj_sexp = e_data_cal_view_get_object_sexp (query);
1234 if (!match_data.obj_sexp) {
1235 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_InvalidQuery);
1239 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1241 /* notify listeners of all objects */
1242 if (match_data.obj_list) {
1243 e_data_cal_view_notify_objects_added (query, (const GList *) match_data.obj_list);
1246 g_list_foreach (match_data.obj_list, (GFunc) g_free, NULL);
1247 g_list_free (match_data.obj_list);
1250 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
1254 free_busy_instance (ECalComponent *comp,
1255 time_t instance_start,
1256 time_t instance_end,
1259 icalcomponent *vfb = data;
1261 icalparameter *param;
1262 struct icalperiodtype ipt;
1263 icaltimezone *utc_zone;
1265 utc_zone = icaltimezone_get_utc_timezone ();
1267 ipt.start = icaltime_from_timet_with_zone (instance_start, FALSE, utc_zone);
1268 ipt.end = icaltime_from_timet_with_zone (instance_end, FALSE, utc_zone);
1269 ipt.duration = icaldurationtype_null_duration ();
1271 /* add busy information to the vfb component */
1272 prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
1273 icalproperty_set_freebusy (prop, ipt);
1275 param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
1276 icalproperty_add_parameter (prop, param);
1278 icalcomponent_add_property (vfb, prop);
1283 static icalcomponent *
1284 create_user_free_busy (ECalBackendFile *cbfile, const char *address, const char *cn,
1285 time_t start, time_t end)
1287 ECalBackendFilePrivate *priv;
1290 icaltimezone *utc_zone;
1291 ECalBackendSExp *obj_sexp;
1292 char *query, *iso_start, *iso_end;
1294 priv = cbfile->priv;
1296 /* create the (unique) VFREEBUSY object that we'll return */
1297 vfb = icalcomponent_new_vfreebusy ();
1298 if (address != NULL) {
1300 icalparameter *param;
1302 prop = icalproperty_new_organizer (address);
1303 if (prop != NULL && cn != NULL) {
1304 param = icalparameter_new_cn (cn);
1305 icalproperty_add_parameter (prop, param);
1308 icalcomponent_add_property (vfb, prop);
1310 utc_zone = icaltimezone_get_utc_timezone ();
1311 icalcomponent_set_dtstart (vfb, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
1312 icalcomponent_set_dtend (vfb, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
1314 /* add all objects in the given interval */
1315 iso_start = isodate_from_time_t (start);
1316 iso_end = isodate_from_time_t (end);
1317 query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") (make-time \"%s\")",
1318 iso_start, iso_end);
1319 obj_sexp = e_cal_backend_sexp_new (query);
1327 for (l = priv->comp; l; l = l->next) {
1328 ECalComponent *comp = l->data;
1329 icalcomponent *icalcomp, *vcalendar_comp;
1332 icalcomp = e_cal_component_get_icalcomponent (comp);
1336 /* If the event is TRANSPARENT, skip it. */
1337 prop = icalcomponent_get_first_property (icalcomp,
1338 ICAL_TRANSP_PROPERTY);
1340 icalproperty_transp transp_val = icalproperty_get_transp (prop);
1341 if (transp_val == ICAL_TRANSP_TRANSPARENT ||
1342 transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
1346 if (!e_cal_backend_sexp_match_comp (obj_sexp, l->data, E_CAL_BACKEND (cbfile)))
1349 vcalendar_comp = icalcomponent_get_parent (icalcomp);
1350 e_cal_recur_generate_instances (comp, start, end,
1355 priv->default_zone);
1361 /* Get_free_busy handler for the file backend */
1362 static ECalBackendSyncStatus
1363 e_cal_backend_file_get_free_busy (ECalBackendSync *backend, EDataCal *cal, GList *users,
1364 time_t start, time_t end, GList **freebusy)
1366 ECalBackendFile *cbfile;
1367 ECalBackendFilePrivate *priv;
1368 gchar *address, *name;
1373 cbfile = E_CAL_BACKEND_FILE (backend);
1374 priv = cbfile->priv;
1376 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1377 g_return_val_if_fail (start != -1 && end != -1, GNOME_Evolution_Calendar_InvalidRange);
1378 g_return_val_if_fail (start <= end, GNOME_Evolution_Calendar_InvalidRange);
1382 if (users == NULL) {
1383 if (e_cal_backend_mail_account_get_default (&address, &name)) {
1384 vfb = create_user_free_busy (cbfile, address, name, start, end);
1385 calobj = icalcomponent_as_ical_string (vfb);
1386 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1387 icalcomponent_free (vfb);
1392 for (l = users; l != NULL; l = l->next ) {
1394 if (e_cal_backend_mail_account_is_valid (address, &name)) {
1395 vfb = create_user_free_busy (cbfile, address, name, start, end);
1396 calobj = icalcomponent_as_ical_string (vfb);
1397 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1398 icalcomponent_free (vfb);
1404 return GNOME_Evolution_Calendar_Success;
1409 ECalBackendFile *backend;
1410 icalcomponent_kind kind;
1413 } ECalBackendFileComputeChangesData;
1416 e_cal_backend_file_compute_changes_foreach_key (const char *key, gpointer data)
1418 ECalBackendFileComputeChangesData *be_data = data;
1420 if (!lookup_component (be_data->backend, key)) {
1421 ECalComponent *comp;
1423 comp = e_cal_component_new ();
1424 if (be_data->kind == ICAL_VTODO_COMPONENT)
1425 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
1427 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
1429 e_cal_component_set_uid (comp, key);
1430 be_data->deletes = g_list_prepend (be_data->deletes, e_cal_component_get_as_string (comp));
1432 e_xmlhash_remove (be_data->ehash, key);
1436 static ECalBackendSyncStatus
1437 e_cal_backend_file_compute_changes (ECalBackendFile *cbfile, const char *change_id,
1438 GList **adds, GList **modifies, GList **deletes)
1440 ECalBackendFilePrivate *priv;
1443 ECalBackendFileComputeChangesData be_data;
1445 gchar *unescaped_uri;
1447 priv = cbfile->priv;
1449 /* FIXME Will this always work? */
1450 unescaped_uri = gnome_vfs_unescape_string (priv->uri, "");
1451 filename = g_strdup_printf ("%s-%s.db", unescaped_uri, change_id);
1452 ehash = e_xmlhash_new (filename);
1454 g_free (unescaped_uri);
1456 /* Calculate adds and modifies */
1457 for (i = priv->comp; i != NULL; i = i->next) {
1461 e_cal_component_get_uid (i->data, &uid);
1462 calobj = e_cal_component_get_as_string (i->data);
1464 g_assert (calobj != NULL);
1466 /* check what type of change has occurred, if any */
1467 switch (e_xmlhash_compare (ehash, uid, calobj)) {
1468 case E_XMLHASH_STATUS_SAME:
1470 case E_XMLHASH_STATUS_NOT_FOUND:
1471 *adds = g_list_prepend (*adds, g_strdup (calobj));
1472 e_xmlhash_add (ehash, uid, calobj);
1474 case E_XMLHASH_STATUS_DIFFERENT:
1475 *modifies = g_list_prepend (*modifies, g_strdup (calobj));
1476 e_xmlhash_add (ehash, uid, calobj);
1483 /* Calculate deletions */
1484 be_data.backend = cbfile;
1485 be_data.kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbfile));
1486 be_data.deletes = NULL;
1487 be_data.ehash = ehash;
1488 e_xmlhash_foreach_key (ehash, (EXmlHashFunc)e_cal_backend_file_compute_changes_foreach_key, &be_data);
1490 *deletes = be_data.deletes;
1492 e_xmlhash_write (ehash);
1493 e_xmlhash_destroy (ehash);
1495 return GNOME_Evolution_Calendar_Success;
1498 /* Get_changes handler for the file backend */
1499 static ECalBackendSyncStatus
1500 e_cal_backend_file_get_changes (ECalBackendSync *backend, EDataCal *cal, const char *change_id,
1501 GList **adds, GList **modifies, GList **deletes)
1503 ECalBackendFile *cbfile;
1504 ECalBackendFilePrivate *priv;
1506 cbfile = E_CAL_BACKEND_FILE (backend);
1507 priv = cbfile->priv;
1509 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1510 g_return_val_if_fail (change_id != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1512 return e_cal_backend_file_compute_changes (cbfile, change_id, adds, modifies, deletes);
1515 /* Discard_alarm handler for the file backend */
1516 static ECalBackendSyncStatus
1517 e_cal_backend_file_discard_alarm (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *auid)
1519 /* we just do nothing with the alarm */
1520 return GNOME_Evolution_Calendar_Success;
1523 static icaltimezone *
1524 e_cal_backend_file_internal_get_default_timezone (ECalBackend *backend)
1526 ECalBackendFile *cbfile;
1527 ECalBackendFilePrivate *priv;
1529 cbfile = E_CAL_BACKEND_FILE (backend);
1530 priv = cbfile->priv;
1532 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1534 return priv->default_zone;
1537 static icaltimezone *
1538 e_cal_backend_file_internal_get_timezone (ECalBackend *backend, const char *tzid)
1540 ECalBackendFile *cbfile;
1541 ECalBackendFilePrivate *priv;
1544 cbfile = E_CAL_BACKEND_FILE (backend);
1545 priv = cbfile->priv;
1547 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1549 if (!strcmp (tzid, "UTC"))
1550 zone = icaltimezone_get_utc_timezone ();
1552 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1554 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1561 sanitize_component (ECalBackendFile *cbfile, ECalComponent *comp)
1563 ECalComponentDateTime dt;
1564 icaltimezone *zone, *default_zone;
1566 /* Check dtstart, dtend and due's timezone, and convert it to local
1567 * default timezone if the timezone is not in our builtin timezone
1569 e_cal_component_get_dtstart (comp, &dt);
1570 if (dt.value && dt.tzid) {
1571 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1573 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1574 g_free ((char *)dt.tzid);
1575 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1576 e_cal_component_set_dtstart (comp, &dt);
1579 e_cal_component_free_datetime (&dt);
1581 e_cal_component_get_dtend (comp, &dt);
1582 if (dt.value && dt.tzid) {
1583 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1585 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1586 g_free ((char *)dt.tzid);
1587 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1588 e_cal_component_set_dtend (comp, &dt);
1591 e_cal_component_free_datetime (&dt);
1593 e_cal_component_get_due (comp, &dt);
1594 if (dt.value && dt.tzid) {
1595 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1597 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1598 g_free ((char *)dt.tzid);
1599 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1600 e_cal_component_set_due (comp, &dt);
1603 e_cal_component_free_datetime (&dt);
1604 e_cal_component_abort_sequence (comp);
1609 static ECalBackendSyncStatus
1610 e_cal_backend_file_create_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj, char **uid)
1612 ECalBackendFile *cbfile;
1613 ECalBackendFilePrivate *priv;
1614 icalcomponent *icalcomp;
1615 ECalComponent *comp;
1616 const char *comp_uid;
1617 struct icaltimetype current;
1619 cbfile = E_CAL_BACKEND_FILE (backend);
1620 priv = cbfile->priv;
1622 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1623 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1625 /* Parse the icalendar text */
1626 icalcomp = icalparser_parse_string ((char *) calobj);
1628 return GNOME_Evolution_Calendar_InvalidObject;
1630 /* Check kind with the parent */
1631 if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
1632 icalcomponent_free (icalcomp);
1633 return GNOME_Evolution_Calendar_InvalidObject;
1637 comp_uid = icalcomponent_get_uid (icalcomp);
1639 /* check the object is not in our cache */
1640 if (lookup_component (cbfile, comp_uid)) {
1641 icalcomponent_free (icalcomp);
1642 return GNOME_Evolution_Calendar_ObjectIdAlreadyExists;
1645 /* Create the cal component */
1646 comp = e_cal_component_new ();
1647 e_cal_component_set_icalcomponent (comp, icalcomp);
1649 /* Set the created and last modified times on the component */
1650 current = icaltime_from_timet (time (NULL), 0);
1651 e_cal_component_set_created (comp, ¤t);
1652 e_cal_component_set_last_modified (comp, ¤t);
1654 /* sanitize the component*/
1655 sanitize_component (cbfile, comp);
1657 /* Add the object */
1658 add_component (cbfile, comp, TRUE);
1663 /* Return the UID */
1665 *uid = g_strdup (comp_uid);
1667 return GNOME_Evolution_Calendar_Success;
1670 static ECalBackendSyncStatus
1671 e_cal_backend_file_modify_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj,
1672 CalObjModType mod, char **old_object)
1674 ECalBackendFile *cbfile;
1675 ECalBackendFilePrivate *priv;
1676 icalcomponent *icalcomp;
1677 const char *comp_uid, *rid;
1679 ECalComponent *comp, *recurrence;
1680 ECalBackendFileObject *obj_data;
1681 struct icaltimetype current;
1683 cbfile = E_CAL_BACKEND_FILE (backend);
1684 priv = cbfile->priv;
1686 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1687 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1689 /* Parse the icalendar text */
1690 icalcomp = icalparser_parse_string ((char *) calobj);
1692 return GNOME_Evolution_Calendar_InvalidObject;
1694 /* Check kind with the parent */
1695 if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
1696 icalcomponent_free (icalcomp);
1697 return GNOME_Evolution_Calendar_InvalidObject;
1701 comp_uid = icalcomponent_get_uid (icalcomp);
1703 /* Get the object from our cache */
1704 if (!(obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid))) {
1705 icalcomponent_free (icalcomp);
1706 return GNOME_Evolution_Calendar_ObjectNotFound;
1709 /* Create the cal component */
1710 comp = e_cal_component_new ();
1711 e_cal_component_set_icalcomponent (comp, icalcomp);
1713 /* Set the last modified time on the component */
1714 current = icaltime_from_timet (time (NULL), 0);
1715 e_cal_component_set_last_modified (comp, ¤t);
1717 /* sanitize the component*/
1718 sanitize_component (cbfile, comp);
1720 /* handle mod_type */
1722 case CALOBJ_MOD_THIS :
1723 rid = e_cal_component_get_recurid_as_string (comp);
1724 if (!rid || !*rid) {
1725 g_object_unref (comp);
1726 return GNOME_Evolution_Calendar_ObjectNotFound;
1729 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
1730 &real_rid, &recurrence)) {
1732 *old_object = e_cal_component_get_as_string (recurrence);
1734 /* remove the component from our data */
1735 icalcomponent_remove_component (priv->icalcomp,
1736 e_cal_component_get_icalcomponent (recurrence));
1737 priv->comp = g_list_remove (priv->comp, recurrence);
1738 g_hash_table_remove (obj_data->recurrences, rid);
1739 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
1743 g_object_unref (recurrence);
1747 old = e_cal_component_get_as_string (obj_data->full_object);
1749 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
1750 get_rid_icaltime (comp),
1753 new = e_cal_component_get_as_string (obj_data->full_object);
1755 e_cal_backend_notify_object_modified (E_CAL_BACKEND (backend), old, new);
1764 /* add the detached instance */
1765 g_hash_table_insert (obj_data->recurrences,
1766 g_strdup (e_cal_component_get_recurid_as_string (comp)),
1768 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
1770 case CALOBJ_MOD_THISANDPRIOR :
1772 case CALOBJ_MOD_THISANDFUTURE :
1774 case CALOBJ_MOD_ALL :
1775 /* in this case, we blow away all recurrences, and start over
1776 with a clean component */
1777 /* Remove the old version */
1779 *old_object = e_cal_component_get_as_string (obj_data->full_object);
1781 remove_component (cbfile, obj_data->full_object);
1783 /* Add the new object */
1784 add_component (cbfile, comp, TRUE);
1790 return GNOME_Evolution_Calendar_Success;
1794 remove_instance (ECalBackendFile *cbfile, ECalBackendFileObject *obj_data, const char *rid)
1797 ECalComponent *comp;
1803 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, &hash_rid, &comp)) {
1804 /* remove the component from our data */
1805 icalcomponent_remove_component (cbfile->priv->icalcomp,
1806 e_cal_component_get_icalcomponent (comp));
1807 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
1808 g_hash_table_remove (obj_data->recurrences, rid);
1809 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp);
1811 /* update the set of categories */
1812 e_cal_component_get_categories_list (comp, &categories);
1813 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
1814 e_cal_component_free_categories_list (categories);
1818 g_object_unref (comp);
1823 /* remove the component from our data, temporarily */
1824 icalcomponent_remove_component (cbfile->priv->icalcomp,
1825 e_cal_component_get_icalcomponent (obj_data->full_object));
1826 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
1828 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
1829 icaltime_from_string (rid), CALOBJ_MOD_THIS);
1831 /* add the modified object to the beginning of the list,
1832 so that it's always before any detached instance we
1834 cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
1838 ECalBackendFile *cbfile;
1839 ECalBackendFileObject *obj_data;
1842 } RemoveRecurrenceData;
1845 remove_object_instance_cb (gpointer key, gpointer value, gpointer user_data)
1847 time_t fromtt, instancett;
1850 ECalComponent *instance = value;
1851 RemoveRecurrenceData *rrdata = user_data;
1853 fromtt = icaltime_as_timet (icaltime_from_string (rrdata->rid));
1854 instancett = icaltime_as_timet (get_rid_icaltime (instance));
1856 if (fromtt > 0 && instancett > 0) {
1857 if ((rrdata->mod == CALOBJ_MOD_THISANDPRIOR && instancett <= fromtt) ||
1858 (rrdata->mod == CALOBJ_MOD_THISANDFUTURE && instancett >= fromtt)) {
1859 /* remove the component from our data */
1860 icalcomponent_remove_component (rrdata->cbfile->priv->icalcomp,
1861 e_cal_component_get_icalcomponent (instance));
1862 rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
1864 rrdata->obj_data->recurrences_list = g_list_remove (rrdata->obj_data->recurrences_list, instance);
1866 /* update the set of categories */
1867 e_cal_component_get_categories_list (instance, &categories);
1868 e_cal_backend_unref_categories (E_CAL_BACKEND (rrdata->cbfile), categories);
1869 e_cal_component_free_categories_list (categories);
1873 g_object_unref (instance);
1882 /* Remove_object handler for the file backend */
1883 static ECalBackendSyncStatus
1884 e_cal_backend_file_remove_object (ECalBackendSync *backend, EDataCal *cal,
1885 const char *uid, const char *rid,
1886 CalObjModType mod, char **object)
1888 ECalBackendFile *cbfile;
1889 ECalBackendFilePrivate *priv;
1890 ECalBackendFileObject *obj_data;
1891 ECalComponent *comp;
1893 RemoveRecurrenceData rrdata;
1895 cbfile = E_CAL_BACKEND_FILE (backend);
1896 priv = cbfile->priv;
1898 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1899 g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1901 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1903 return GNOME_Evolution_Calendar_ObjectNotFound;
1905 comp = obj_data->full_object;
1908 case CALOBJ_MOD_ALL :
1909 *object = e_cal_component_get_as_string (comp);
1910 remove_component (cbfile, comp);
1912 case CALOBJ_MOD_THIS :
1914 return GNOME_Evolution_Calendar_ObjectNotFound;
1916 remove_instance (cbfile, obj_data, rid);
1918 case CALOBJ_MOD_THISANDPRIOR :
1919 case CALOBJ_MOD_THISANDFUTURE :
1921 return GNOME_Evolution_Calendar_ObjectNotFound;
1923 /* remove the component from our data, temporarily */
1924 icalcomponent_remove_component (priv->icalcomp,
1925 e_cal_component_get_icalcomponent (comp));
1926 priv->comp = g_list_remove (priv->comp, comp);
1928 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (comp),
1929 icaltime_from_string (rid), mod);
1931 /* now remove all detached instances */
1932 rrdata.cbfile = cbfile;
1933 rrdata.obj_data = obj_data;
1936 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
1938 /* add the modified object to the beginning of the list,
1939 so that it's always before any detached instance we
1941 priv->comp = g_list_prepend (priv->comp, comp);
1947 return GNOME_Evolution_Calendar_Success;
1951 cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp)
1953 ECalComponent *old_comp;
1955 /* Find the old version of the component. */
1956 old_comp = lookup_component (cbfile, icalcomponent_get_uid (icalcomp));
1961 remove_component (cbfile, old_comp);
1970 } ECalBackendFileTzidData;
1973 check_tzids (icalparameter *param, void *data)
1975 ECalBackendFileTzidData *tzdata = data;
1978 tzid = icalparameter_get_tzid (param);
1979 if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
1980 tzdata->found = FALSE;
1983 /* Update_objects handler for the file backend. */
1984 static ECalBackendSyncStatus
1985 e_cal_backend_file_receive_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj)
1987 ECalBackendFile *cbfile;
1988 ECalBackendFilePrivate *priv;
1989 icalcomponent *toplevel_comp, *icalcomp = NULL;
1990 icalcomponent_kind kind;
1991 icalproperty_method method;
1992 icalcomponent *subcomp;
1994 ECalComponent *comp;
1995 struct icaltimetype current;
1996 ECalBackendFileTzidData tzdata;
1997 ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;
1999 cbfile = E_CAL_BACKEND_FILE (backend);
2000 priv = cbfile->priv;
2002 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
2003 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_InvalidObject);
2005 /* Pull the component from the string and ensure that it is sane */
2006 toplevel_comp = icalparser_parse_string ((char *) calobj);
2008 return GNOME_Evolution_Calendar_InvalidObject;
2010 kind = icalcomponent_isa (toplevel_comp);
2011 if (kind != ICAL_VCALENDAR_COMPONENT) {
2012 /* If its not a VCALENDAR, make it one to simplify below */
2013 icalcomp = toplevel_comp;
2014 toplevel_comp = e_cal_util_new_top_level ();
2015 icalcomponent_add_component (toplevel_comp, icalcomp);
2018 method = icalcomponent_get_method (toplevel_comp);
2020 /* Build a list of timezones so we can make sure all the objects have valid info */
2021 tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2023 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
2027 zone = icaltimezone_new ();
2028 if (icaltimezone_set_component (zone, subcomp))
2029 g_hash_table_insert (tzdata.zones, g_strdup (icaltimezone_get_tzid (zone)), NULL);
2031 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
2034 /* First we make sure all the components are usuable */
2036 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
2038 /* We ignore anything except VEVENT, VTODO and VJOURNAL
2040 icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
2042 switch (child_kind) {
2043 case ICAL_VEVENT_COMPONENT:
2044 case ICAL_VTODO_COMPONENT:
2045 case ICAL_VJOURNAL_COMPONENT:
2046 tzdata.found = TRUE;
2047 icalcomponent_foreach_tzid (subcomp, check_tzids, &tzdata);
2049 if (!tzdata.found) {
2050 status = GNOME_Evolution_Calendar_InvalidObject;
2054 if (!icalcomponent_get_uid (subcomp)) {
2055 status = GNOME_Evolution_Calendar_InvalidObject;
2059 comps = g_list_prepend (comps, subcomp);
2066 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
2069 /* Now we manipulate the components we care about */
2070 for (l = comps; l; l = l->next) {
2071 const char *uid, *rid;
2076 /* Create the cal component */
2077 comp = e_cal_component_new ();
2078 e_cal_component_set_icalcomponent (comp, subcomp);
2080 /* Set the created and last modified times on the component */
2081 current = icaltime_from_timet (time (NULL), 0);
2082 e_cal_component_set_created (comp, ¤t);
2083 e_cal_component_set_last_modified (comp, ¤t);
2085 /* sanitize the component*/
2086 sanitize_component (cbfile, comp);
2088 e_cal_component_get_uid (comp, &uid);
2089 rid = e_cal_component_get_recurid_as_string (comp);
2092 case ICAL_METHOD_PUBLISH:
2093 case ICAL_METHOD_REQUEST:
2094 case ICAL_METHOD_REPLY:
2095 if (e_cal_backend_file_get_object (backend, cal, uid, rid, &calobj)
2096 == GNOME_Evolution_Calendar_Success) {
2099 calobj = (char *) icalcomponent_as_ical_string (subcomp);
2100 status = e_cal_backend_file_modify_object (backend, cal, calobj, CALOBJ_MOD_THIS, &old_object);
2101 if (status != GNOME_Evolution_Calendar_Success)
2104 e_cal_backend_notify_object_modified (E_CAL_BACKEND (backend), old_object, calobj);
2106 g_free (old_object);
2110 calobj = (char *) icalcomponent_as_ical_string (subcomp);
2111 status = e_cal_backend_file_create_object (backend, cal, calobj, &returned_uid);
2112 if (status != GNOME_Evolution_Calendar_Success)
2115 e_cal_backend_notify_object_created (E_CAL_BACKEND (backend), calobj);
2118 case ICAL_METHOD_ADD:
2119 /* FIXME This should be doable once all the recurid stuff is done */
2121 case ICAL_METHOD_COUNTER:
2122 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2125 case ICAL_METHOD_DECLINECOUNTER:
2126 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2129 case ICAL_METHOD_CANCEL:
2130 if (cancel_received_object (cbfile, subcomp)) {
2131 calobj = (char *) icalcomponent_as_ical_string (subcomp);
2132 e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend), icalcomponent_get_uid (subcomp), calobj);
2134 /* remove the component from the toplevel VCALENDAR */
2135 icalcomponent_remove_component (toplevel_comp, subcomp);
2136 icalcomponent_free (subcomp);
2140 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2145 g_list_free (comps);
2147 /* Merge the iCalendar components with our existing VCALENDAR,
2148 resolving any conflicting TZIDs. */
2149 icalcomponent_merge_component (priv->icalcomp, toplevel_comp);
2154 g_hash_table_destroy (tzdata.zones);
2159 static ECalBackendSyncStatus
2160 e_cal_backend_file_send_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj, GList **users,
2161 char **modified_calobj)
2164 *modified_calobj = NULL;
2166 return GNOME_Evolution_Calendar_Success;
2169 /* Object initialization function for the file backend */
2171 e_cal_backend_file_init (ECalBackendFile *cbfile, ECalBackendFileClass *class)
2173 ECalBackendFilePrivate *priv;
2175 priv = g_new0 (ECalBackendFilePrivate, 1);
2176 cbfile->priv = priv;
2179 priv->file_name = g_strdup ("calendar.ics");
2180 priv->read_only = FALSE;
2181 priv->icalcomp = NULL;
2182 priv->comp_uid_hash = NULL;
2185 /* The timezone defaults to UTC. */
2186 priv->default_zone = icaltimezone_get_utc_timezone ();
2189 /* Class initialization function for the file backend */
2191 e_cal_backend_file_class_init (ECalBackendFileClass *class)
2193 GObjectClass *object_class;
2194 ECalBackendClass *backend_class;
2195 ECalBackendSyncClass *sync_class;
2197 object_class = (GObjectClass *) class;
2198 backend_class = (ECalBackendClass *) class;
2199 sync_class = (ECalBackendSyncClass *) class;
2201 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
2203 object_class->dispose = e_cal_backend_file_dispose;
2204 object_class->finalize = e_cal_backend_file_finalize;
2206 sync_class->is_read_only_sync = e_cal_backend_file_is_read_only;
2207 sync_class->get_cal_address_sync = e_cal_backend_file_get_cal_address;
2208 sync_class->get_alarm_email_address_sync = e_cal_backend_file_get_alarm_email_address;
2209 sync_class->get_ldap_attribute_sync = e_cal_backend_file_get_ldap_attribute;
2210 sync_class->get_static_capabilities_sync = e_cal_backend_file_get_static_capabilities;
2211 sync_class->open_sync = e_cal_backend_file_open;
2212 sync_class->remove_sync = e_cal_backend_file_remove;
2213 sync_class->create_object_sync = e_cal_backend_file_create_object;
2214 sync_class->modify_object_sync = e_cal_backend_file_modify_object;
2215 sync_class->remove_object_sync = e_cal_backend_file_remove_object;
2216 sync_class->discard_alarm_sync = e_cal_backend_file_discard_alarm;
2217 sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
2218 sync_class->send_objects_sync = e_cal_backend_file_send_objects;
2219 sync_class->get_default_object_sync = e_cal_backend_file_get_default_object;
2220 sync_class->get_object_sync = e_cal_backend_file_get_object;
2221 sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
2222 sync_class->get_timezone_sync = e_cal_backend_file_get_timezone;
2223 sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
2224 sync_class->set_default_timezone_sync = e_cal_backend_file_set_default_timezone;
2225 sync_class->get_freebusy_sync = e_cal_backend_file_get_free_busy;
2226 sync_class->get_changes_sync = e_cal_backend_file_get_changes;
2228 backend_class->is_loaded = e_cal_backend_file_is_loaded;
2229 backend_class->start_query = e_cal_backend_file_start_query;
2230 backend_class->get_mode = e_cal_backend_file_get_mode;
2231 backend_class->set_mode = e_cal_backend_file_set_mode;
2233 backend_class->internal_get_default_timezone = e_cal_backend_file_internal_get_default_timezone;
2234 backend_class->internal_get_timezone = e_cal_backend_file_internal_get_timezone;
2239 * e_cal_backend_file_get_type:
2242 * Registers the #ECalBackendFile class if necessary, and returns the type ID
2245 * Return value: The type ID of the #ECalBackendFile class.
2248 e_cal_backend_file_get_type (void)
2250 static GType e_cal_backend_file_type = 0;
2252 if (!e_cal_backend_file_type) {
2253 static GTypeInfo info = {
2254 sizeof (ECalBackendFileClass),
2255 (GBaseInitFunc) NULL,
2256 (GBaseFinalizeFunc) NULL,
2257 (GClassInitFunc) e_cal_backend_file_class_init,
2259 sizeof (ECalBackendFile),
2261 (GInstanceInitFunc) e_cal_backend_file_init
2263 e_cal_backend_file_type = g_type_register_static (E_TYPE_CAL_BACKEND_SYNC,
2264 "ECalBackendFile", &info, 0);
2267 return e_cal_backend_file_type;
2271 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile, const char *file_name)
2273 ECalBackendFilePrivate *priv;
2275 g_return_if_fail (cbfile != NULL);
2276 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
2277 g_return_if_fail (file_name != NULL);
2279 priv = cbfile->priv;
2281 if (priv->file_name)
2282 g_free (priv->file_name);
2284 priv->file_name = g_strdup (file_name);
2288 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
2290 ECalBackendFilePrivate *priv;
2292 g_return_val_if_fail (cbfile != NULL, NULL);
2293 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
2295 priv = cbfile->priv;
2297 return priv->file_name;
2300 ECalBackendSyncStatus
2301 e_cal_backend_file_reload (ECalBackendFile *cbfile)
2303 ECalBackendFilePrivate *priv;
2305 ECalBackendSyncStatus status;
2307 priv = cbfile->priv;
2309 str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
2311 return GNOME_Evolution_Calendar_OtherError;
2313 if (access (str_uri, R_OK) == 0) {
2314 status = reload_cal (cbfile, str_uri);
2315 if (access (str_uri, W_OK) != 0)
2316 priv->read_only = TRUE;
2318 status = GNOME_Evolution_Calendar_NoSuchCal;