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-xml-hash-utils.h>
33 #include <libecal/e-cal-recur.h>
34 #include <libecal/e-cal-time-util.h>
35 #include <libecal/e-cal-util.h>
36 #include <libedata-cal/e-cal-backend-util.h>
37 #include <libedata-cal/e-cal-backend-sexp.h>
38 #include "e-cal-backend-file-events.h"
42 /* Placeholder for each component and its recurrences */
44 ECalComponent *full_object;
45 GHashTable *recurrences;
46 } ECalBackendFileObject;
48 /* Private part of the ECalBackendFile structure */
49 struct _ECalBackendFilePrivate {
50 /* URI where the calendar data is stored */
53 /* Filename in the dir */
57 /* Toplevel VCALENDAR component */
58 icalcomponent *icalcomp;
60 /* All the objects in the calendar, hashed by UID. The
61 * hash key *is* the uid returned by cal_component_get_uid(); it is not
62 * copied, so don't free it when you remove an object from the hash
63 * table. Each item in the hash table is a ECalBackendFileObject.
65 GHashTable *comp_uid_hash;
69 /* The calendar's default timezone, used for resolving DATE and
70 floating DATE-TIME values. */
71 icaltimezone *default_zone;
73 /* The list of live queries */
79 static void e_cal_backend_file_dispose (GObject *object);
80 static void e_cal_backend_file_finalize (GObject *object);
82 static ECalBackendSyncClass *parent_class;
86 /* g_hash_table_foreach() callback to destroy recurrences in the hash table */
88 free_recurrence (gpointer key, gpointer value, gpointer data)
91 ECalComponent *comp = value;
94 g_object_unref (comp);
97 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
99 free_object (gpointer key, gpointer value, gpointer data)
101 ECalBackendFileObject *obj_data = value;
103 g_object_unref (obj_data->full_object);
104 g_hash_table_foreach (obj_data->recurrences, (GHFunc) free_recurrence, NULL);
105 g_hash_table_destroy (obj_data->recurrences);
108 /* Saves the calendar data */
110 save (ECalBackendFile *cbfile)
112 ECalBackendFilePrivate *priv;
113 GnomeVFSURI *uri, *backup_uri;
114 GnomeVFSHandle *handle = NULL;
115 GnomeVFSResult result = GNOME_VFS_ERROR_BAD_FILE;
116 GnomeVFSFileSize out;
117 gchar *tmp, *backup_uristr;
121 g_assert (priv->uri != NULL);
122 g_assert (priv->icalcomp != NULL);
124 uri = gnome_vfs_uri_new (priv->uri);
126 goto error_malformed_uri;
128 /* save calendar to backup file */
129 tmp = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
131 gnome_vfs_uri_unref (uri);
132 goto error_malformed_uri;
135 backup_uristr = g_strconcat (tmp, "~", NULL);
136 backup_uri = gnome_vfs_uri_new (backup_uristr);
139 g_free (backup_uristr);
142 gnome_vfs_uri_unref (uri);
143 goto error_malformed_uri;
146 result = gnome_vfs_create_uri (&handle, backup_uri,
147 GNOME_VFS_OPEN_WRITE,
149 if (result != GNOME_VFS_OK) {
150 gnome_vfs_uri_unref (uri);
151 gnome_vfs_uri_unref (backup_uri);
155 buf = icalcomponent_as_ical_string (priv->icalcomp);
156 result = gnome_vfs_write (handle, buf, strlen (buf) * sizeof (char), &out);
157 gnome_vfs_close (handle);
158 if (result != GNOME_VFS_OK) {
159 gnome_vfs_uri_unref (uri);
160 gnome_vfs_uri_unref (backup_uri);
164 /* now copy the temporary file to the real file */
165 result = gnome_vfs_move_uri (backup_uri, uri, TRUE);
167 gnome_vfs_uri_unref (uri);
168 gnome_vfs_uri_unref (backup_uri);
169 if (result != GNOME_VFS_OK)
175 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
176 _("Can't save calendar data: Malformed URI."));
180 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), gnome_vfs_result_to_string (result));
185 free_calendar_components (GHashTable *comp_uid_hash, icalcomponent *top_icomp)
188 g_hash_table_foreach (comp_uid_hash, (GHFunc) free_object, NULL);
189 g_hash_table_destroy (comp_uid_hash);
193 icalcomponent_free (top_icomp);
198 free_calendar_data (ECalBackendFile *cbfile)
200 ECalBackendFilePrivate *priv;
204 free_calendar_components (priv->comp_uid_hash, priv->icalcomp);
205 priv->comp_uid_hash = NULL;
206 priv->icalcomp = NULL;
208 g_list_free (priv->comp);
212 /* Dispose handler for the file backend */
214 e_cal_backend_file_dispose (GObject *object)
216 ECalBackendFile *cbfile;
217 ECalBackendFilePrivate *priv;
219 cbfile = E_CAL_BACKEND_FILE (object);
222 /* Save if necessary */
224 free_calendar_data (cbfile);
226 if (G_OBJECT_CLASS (parent_class)->dispose)
227 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
230 /* Finalize handler for the file backend */
232 e_cal_backend_file_finalize (GObject *object)
234 ECalBackendFile *cbfile;
235 ECalBackendFilePrivate *priv;
237 g_return_if_fail (object != NULL);
238 g_return_if_fail (E_IS_CAL_BACKEND_FILE (object));
240 cbfile = E_CAL_BACKEND_FILE (object);
253 if (G_OBJECT_CLASS (parent_class)->finalize)
254 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
259 /* Looks up a component by its UID on the backend's component hash table */
260 static ECalComponent *
261 lookup_component (ECalBackendFile *cbfile, const char *uid)
263 ECalBackendFilePrivate *priv;
264 ECalBackendFileObject *obj_data;
268 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
269 return obj_data ? obj_data->full_object : NULL;
274 /* Calendar backend methods */
276 /* Is_read_only handler for the file backend */
277 static ECalBackendSyncStatus
278 e_cal_backend_file_is_read_only (ECalBackendSync *backend, EDataCal *cal, gboolean *read_only)
280 ECalBackendFile *cbfile = (ECalBackendFile *) backend;
282 *read_only = cbfile->priv->read_only;
284 return GNOME_Evolution_Calendar_Success;
287 /* Get_email_address handler for the file backend */
288 static ECalBackendSyncStatus
289 e_cal_backend_file_get_cal_address (ECalBackendSync *backend, EDataCal *cal, char **address)
291 /* A file backend has no particular email address associated
292 * with it (although that would be a useful feature some day).
296 return GNOME_Evolution_Calendar_Success;
299 static ECalBackendSyncStatus
300 e_cal_backend_file_get_ldap_attribute (ECalBackendSync *backend, EDataCal *cal, char **attribute)
304 return GNOME_Evolution_Calendar_Success;
307 static ECalBackendSyncStatus
308 e_cal_backend_file_get_alarm_email_address (ECalBackendSync *backend, EDataCal *cal, char **address)
310 /* A file backend has no particular email address associated
311 * with it (although that would be a useful feature some day).
315 return GNOME_Evolution_Calendar_Success;
318 static ECalBackendSyncStatus
319 e_cal_backend_file_get_static_capabilities (ECalBackendSync *backend, EDataCal *cal, char **capabilities)
321 *capabilities = CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS;
323 return GNOME_Evolution_Calendar_Success;
326 /* function to resolve timezones */
327 static icaltimezone *
328 resolve_tzid (const char *tzid, gpointer user_data)
330 icalcomponent *vcalendar_comp = user_data;
332 if (!tzid || !tzid[0])
334 else if (!strcmp (tzid, "UTC"))
335 return icaltimezone_get_utc_timezone ();
337 return icalcomponent_get_timezone (vcalendar_comp, tzid);
340 /* Checks if the specified component has a duplicated UID and if so changes it */
342 check_dup_uid (ECalBackendFile *cbfile, ECalComponent *comp)
344 ECalBackendFilePrivate *priv;
345 ECalBackendFileObject *obj_data;
351 e_cal_component_get_uid (comp, &uid);
353 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
355 return; /* Everything is fine */
357 g_message ("check_dup_uid(): Got object with duplicated UID `%s', changing it...", uid);
359 new_uid = e_cal_component_gen_uid ();
360 e_cal_component_set_uid (comp, new_uid);
363 /* FIXME: I think we need to reset the SEQUENCE property and reset the
364 * CREATED/DTSTAMP/LAST-MODIFIED.
371 get_rid_string (ECalComponent *comp)
373 ECalComponentRange range;
374 struct icaltimetype tt;
376 e_cal_component_get_recurid (comp, &range);
377 if (!range.datetime.value)
379 tt = *range.datetime.value;
380 e_cal_component_free_range (&range);
382 return icaltime_is_valid_time (tt) && !icaltime_is_null_time (tt) ?
383 icaltime_as_ical_string (tt) : "0";
386 static struct icaltimetype
387 get_rid_icaltime (ECalComponent *comp)
389 ECalComponentRange range;
390 struct icaltimetype tt;
392 e_cal_component_get_recurid (comp, &range);
393 if (!range.datetime.value)
394 return icaltime_null_time ();
395 tt = *range.datetime.value;
396 e_cal_component_free_range (&range);
401 /* Tries to add an icalcomponent to the file backend. We only store the objects
402 * of the types we support; all others just remain in the toplevel component so
403 * that we don't lose them.
406 add_component (ECalBackendFile *cbfile, ECalComponent *comp, gboolean add_to_toplevel)
408 ECalBackendFilePrivate *priv;
409 ECalBackendFileObject *obj_data;
415 if (e_cal_component_is_instance (comp)) { /* FIXME: more checks needed, to detect detached instances */
418 e_cal_component_get_uid (comp, &uid);
420 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
422 g_warning (G_STRLOC ": Got an instance of a non-existing component");
426 rid = get_rid_string (comp);
427 if (g_hash_table_lookup (obj_data->recurrences, rid)) {
428 g_warning (G_STRLOC ": Tried to adding an already existing recurrence");
432 g_hash_table_insert (obj_data->recurrences, g_strdup (rid), comp);
434 /* Ensure that the UID is unique; some broken implementations spit
435 * components with duplicated UIDs.
437 check_dup_uid (cbfile, comp);
438 e_cal_component_get_uid (comp, &uid);
440 obj_data = g_new0 (ECalBackendFileObject, 1);
441 obj_data->full_object = comp;
442 obj_data->recurrences = g_hash_table_new (g_str_hash, g_str_equal);
444 g_hash_table_insert (priv->comp_uid_hash, (gpointer) uid, obj_data);
447 priv->comp = g_list_prepend (priv->comp, comp);
449 /* Put the object in the toplevel component if required */
451 if (add_to_toplevel) {
452 icalcomponent *icalcomp;
454 icalcomp = e_cal_component_get_icalcomponent (comp);
455 g_assert (icalcomp != NULL);
457 icalcomponent_add_component (priv->icalcomp, icalcomp);
460 /* Update the set of categories */
461 e_cal_component_get_categories_list (comp, &categories);
462 e_cal_backend_ref_categories (E_CAL_BACKEND (cbfile), categories);
463 e_cal_component_free_categories_list (categories);
466 /* g_hash_table_foreach() callback to remove recurrences from the calendar */
468 remove_recurrence_cb (gpointer key, gpointer value, gpointer data)
472 icalcomponent *icalcomp;
473 ECalBackendFilePrivate *priv;
474 ECalComponent *comp = value;
475 ECalBackendFile *cbfile = data;
479 /* remove the recurrence from the top-level calendar */
480 icalcomp = e_cal_component_get_icalcomponent (comp);
481 g_assert (icalcomp != NULL);
483 icalcomponent_remove_component (priv->icalcomp, icalcomp);
485 /* remove it from our mapping */
486 l = g_list_find (priv->comp, comp);
487 priv->comp = g_list_delete_link (priv->comp, l);
489 /* update the set of categories */
490 e_cal_component_get_categories_list (comp, &categories);
491 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
492 e_cal_component_free_categories_list (categories);
495 /* Removes a component from the backend's hash and lists. Does not perform
496 * notification on the clients. Also removes the component from the toplevel
500 remove_component (ECalBackendFile *cbfile, ECalComponent *comp)
502 ECalBackendFilePrivate *priv;
503 icalcomponent *icalcomp;
507 ECalBackendFileObject *obj_data;
511 /* Remove the icalcomp from the toplevel */
513 icalcomp = e_cal_component_get_icalcomponent (comp);
514 g_assert (icalcomp != NULL);
516 icalcomponent_remove_component (priv->icalcomp, icalcomp);
518 /* Remove it from our mapping */
520 e_cal_component_get_uid (comp, &uid);
521 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
525 g_hash_table_remove (priv->comp_uid_hash, uid);
527 l = g_list_find (priv->comp, comp);
528 g_assert (l != NULL);
529 priv->comp = g_list_delete_link (priv->comp, l);
531 /* remove the recurrences also */
532 g_hash_table_foreach (obj_data->recurrences, (GHFunc) remove_recurrence_cb, cbfile);
534 /* Update the set of categories */
535 e_cal_component_get_categories_list (comp, &categories);
536 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
537 e_cal_component_free_categories_list (categories);
539 free_object ((gpointer) uid, (gpointer) obj_data, NULL);
542 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
544 scan_vcalendar (ECalBackendFile *cbfile)
546 ECalBackendFilePrivate *priv;
550 g_assert (priv->icalcomp != NULL);
551 g_assert (priv->comp_uid_hash != NULL);
553 for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_ANY_COMPONENT);
554 icalcompiter_deref (&iter) != NULL;
555 icalcompiter_next (&iter)) {
556 icalcomponent *icalcomp;
557 icalcomponent_kind kind;
560 icalcomp = icalcompiter_deref (&iter);
562 kind = icalcomponent_isa (icalcomp);
564 if (!(kind == ICAL_VEVENT_COMPONENT
565 || kind == ICAL_VTODO_COMPONENT
566 || kind == ICAL_VJOURNAL_COMPONENT))
569 comp = e_cal_component_new ();
571 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
574 add_component (cbfile, comp, FALSE);
579 get_uri_string_for_gnome_vfs (ECalBackend *backend)
581 ECalBackendFile *cbfile;
582 ECalBackendFilePrivate *priv;
583 const char *master_uri;
584 char *full_uri, *str_uri;
587 cbfile = E_CAL_BACKEND_FILE (backend);
590 master_uri = e_cal_backend_get_uri (backend);
592 /* FIXME Check the error conditions a little more elegantly here */
593 if (g_strrstr ("tasks.ics", master_uri) || g_strrstr ("calendar.ics", master_uri)) {
594 g_warning (G_STRLOC ": Existing file name %s", master_uri);
599 full_uri = g_strdup_printf ("%s%s%s", master_uri, G_DIR_SEPARATOR_S, priv->file_name);
600 uri = gnome_vfs_uri_new (full_uri);
606 str_uri = gnome_vfs_uri_to_string (uri,
607 (GNOME_VFS_URI_HIDE_USER_NAME
608 | GNOME_VFS_URI_HIDE_PASSWORD
609 | GNOME_VFS_URI_HIDE_HOST_NAME
610 | GNOME_VFS_URI_HIDE_HOST_PORT
611 | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD));
612 gnome_vfs_uri_unref (uri);
614 if (!str_uri || !strlen (str_uri)) {
623 /* Parses an open iCalendar file and loads it into the backend */
624 static ECalBackendSyncStatus
625 open_cal (ECalBackendFile *cbfile, const char *uristr)
627 ECalBackendFilePrivate *priv;
628 icalcomponent *icalcomp;
632 icalcomp = e_cal_util_parse_ics_file (uristr);
634 return GNOME_Evolution_Calendar_OtherError;
636 /* FIXME: should we try to demangle XROOT components and
637 * individual components as well?
640 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
641 icalcomponent_free (icalcomp);
643 return GNOME_Evolution_Calendar_OtherError;
646 priv->icalcomp = icalcomp;
648 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
649 scan_vcalendar (cbfile);
651 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
653 return GNOME_Evolution_Calendar_Success;
658 ECalBackend *backend;
659 GHashTable *old_uid_hash;
660 GHashTable *new_uid_hash;
665 notify_removals_cb (gpointer key, gpointer value, gpointer data)
667 BackendDeltaContext *context = data;
668 const gchar *uid = key;
669 ECalBackendFileObject *old_obj_data = value;
671 if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
672 icalcomponent *old_icomp;
675 /* Object was removed */
677 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
681 old_obj_str = icalcomponent_as_ical_string (old_icomp);
685 e_cal_backend_notify_object_removed (context->backend, uid, old_obj_str);
690 notify_adds_modifies_cb (gpointer key, gpointer value, gpointer data)
692 BackendDeltaContext *context = data;
693 const gchar *uid = key;
694 ECalBackendFileObject *new_obj_data = value;
695 ECalBackendFileObject *old_obj_data;
696 icalcomponent *old_icomp, *new_icomp;
697 gchar *old_obj_str, *new_obj_str;
699 old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
702 /* Object was added */
704 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
708 new_obj_str = icalcomponent_as_ical_string (new_icomp);
712 e_cal_backend_notify_object_created (context->backend, new_obj_str);
714 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
715 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
716 if (!old_icomp || !new_icomp)
719 old_obj_str = icalcomponent_as_ical_string (old_icomp);
720 new_obj_str = icalcomponent_as_ical_string (new_icomp);
721 if (!old_obj_str || !new_obj_str)
724 if (strcmp (old_obj_str, new_obj_str)) {
725 /* Object was modified */
727 e_cal_backend_notify_object_modified (context->backend, old_obj_str, new_obj_str);
733 notify_changes (ECalBackendFile *cbfile, GHashTable *old_uid_hash, GHashTable *new_uid_hash)
735 BackendDeltaContext context;
737 context.backend = E_CAL_BACKEND (cbfile);
738 context.old_uid_hash = old_uid_hash;
739 context.new_uid_hash = new_uid_hash;
741 g_hash_table_foreach (old_uid_hash, (GHFunc) notify_removals_cb, &context);
742 g_hash_table_foreach (new_uid_hash, (GHFunc) notify_adds_modifies_cb, &context);
745 static ECalBackendSyncStatus
746 reload_cal (ECalBackendFile *cbfile, const char *uristr)
748 ECalBackendFilePrivate *priv;
749 icalcomponent *icalcomp, *icalcomp_old;
750 GHashTable *comp_uid_hash_old;
754 icalcomp = e_cal_util_parse_ics_file (uristr);
756 return GNOME_Evolution_Calendar_OtherError;
758 /* FIXME: should we try to demangle XROOT components and
759 * individual components as well?
762 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
763 icalcomponent_free (icalcomp);
765 return GNOME_Evolution_Calendar_OtherError;
768 /* Keep old data for comparison - free later */
770 icalcomp_old = priv->icalcomp;
771 priv->icalcomp = NULL;
773 comp_uid_hash_old = priv->comp_uid_hash;
774 priv->comp_uid_hash = NULL;
776 /* Load new calendar */
778 free_calendar_data (cbfile);
780 priv->icalcomp = icalcomp;
782 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
783 scan_vcalendar (cbfile);
785 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
787 /* Compare old and new versions of calendar */
789 notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
793 free_calendar_components (comp_uid_hash_old, icalcomp_old);
794 return GNOME_Evolution_Calendar_Success;
797 static ECalBackendSyncStatus
798 create_cal (ECalBackendFile *cbfile, const char *uristr)
800 ECalBackendFilePrivate *priv;
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)
833 ECalBackendFile *cbfile;
834 ECalBackendFilePrivate *priv;
836 ECalBackendSyncStatus status;
838 cbfile = E_CAL_BACKEND_FILE (backend);
841 /* Claim a succesful open if we are already open */
842 if (priv->uri && priv->comp_uid_hash)
843 return GNOME_Evolution_Calendar_Success;
845 str_uri = get_uri_string (E_CAL_BACKEND (backend));
847 return GNOME_Evolution_Calendar_OtherError;
849 if (access (str_uri, R_OK) == 0) {
850 status = open_cal (cbfile, str_uri);
851 if (access (str_uri, W_OK) != 0)
852 priv->read_only = TRUE;
855 status = GNOME_Evolution_Calendar_NoSuchCal;
857 status = create_cal (cbfile, str_uri);
865 static ECalBackendSyncStatus
866 e_cal_backend_file_remove (ECalBackendSync *backend, EDataCal *cal)
868 ECalBackendFile *cbfile;
869 ECalBackendFilePrivate *priv;
872 cbfile = E_CAL_BACKEND_FILE (backend);
875 str_uri = get_uri_string (E_CAL_BACKEND (backend));
877 return GNOME_Evolution_Calendar_OtherError;
879 if (access (str_uri, W_OK) != 0) {
882 return GNOME_Evolution_Calendar_PermissionDenied;
885 /* FIXME Remove backup file and whole directory too? */
886 if (unlink (str_uri) != 0) {
889 return GNOME_Evolution_Calendar_OtherError;
894 return GNOME_Evolution_Calendar_Success;
897 /* is_loaded handler for the file backend */
899 e_cal_backend_file_is_loaded (ECalBackend *backend)
901 ECalBackendFile *cbfile;
902 ECalBackendFilePrivate *priv;
904 cbfile = E_CAL_BACKEND_FILE (backend);
907 return (priv->icalcomp != NULL);
910 /* is_remote handler for the file backend */
912 e_cal_backend_file_get_mode (ECalBackend *backend)
914 ECalBackendFile *cbfile;
915 ECalBackendFilePrivate *priv;
917 cbfile = E_CAL_BACKEND_FILE (backend);
920 return CAL_MODE_LOCAL;
923 /* Set_mode handler for the file backend */
925 e_cal_backend_file_set_mode (ECalBackend *backend, CalMode mode)
927 e_cal_backend_notify_mode (backend,
928 GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
929 GNOME_Evolution_Calendar_MODE_LOCAL);
933 static ECalBackendSyncStatus
934 e_cal_backend_file_get_default_object (ECalBackendSync *backend, EDataCal *cal, char **object)
938 comp = e_cal_component_new ();
940 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
941 case ICAL_VEVENT_COMPONENT:
942 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
944 case ICAL_VTODO_COMPONENT:
945 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
947 case ICAL_VJOURNAL_COMPONENT:
948 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
951 g_object_unref (comp);
952 return GNOME_Evolution_Calendar_ObjectNotFound;
955 *object = e_cal_component_get_as_string (comp);
956 g_object_unref (comp);
958 return GNOME_Evolution_Calendar_Success;
961 /* Get_object_component handler for the file backend */
962 static ECalBackendSyncStatus
963 e_cal_backend_file_get_object (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *rid, char **object)
965 ECalBackendFile *cbfile;
966 ECalBackendFilePrivate *priv;
967 ECalBackendFileObject *obj_data;
968 ECalComponent *comp = NULL;
969 gboolean free_comp = FALSE;
971 cbfile = E_CAL_BACKEND_FILE (backend);
974 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
975 g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
976 g_assert (priv->comp_uid_hash != NULL);
978 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
980 return GNOME_Evolution_Calendar_ObjectNotFound;
983 comp = g_hash_table_lookup (obj_data->recurrences, rid);
985 icalcomponent *icalcomp;
986 struct icaltimetype itt;
988 itt = icaltime_from_string (rid);
989 icalcomp = e_cal_util_construct_instance (
990 e_cal_component_get_icalcomponent (obj_data->full_object),
993 return GNOME_Evolution_Calendar_ObjectNotFound;
995 comp = e_cal_component_new ();
997 e_cal_component_set_icalcomponent (comp, icalcomp);
1000 comp = obj_data->full_object;
1003 return GNOME_Evolution_Calendar_ObjectNotFound;
1005 *object = e_cal_component_get_as_string (comp);
1008 g_object_unref (comp);
1010 return GNOME_Evolution_Calendar_Success;
1013 /* Get_timezone_object handler for the file backend */
1014 static ECalBackendSyncStatus
1015 e_cal_backend_file_get_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid, char **object)
1017 ECalBackendFile *cbfile;
1018 ECalBackendFilePrivate *priv;
1020 icalcomponent *icalcomp;
1022 cbfile = E_CAL_BACKEND_FILE (backend);
1023 priv = cbfile->priv;
1025 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1026 g_return_val_if_fail (tzid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1028 if (!strcmp (tzid, "UTC")) {
1029 zone = icaltimezone_get_utc_timezone ();
1031 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1033 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1035 return GNOME_Evolution_Calendar_ObjectNotFound;
1039 icalcomp = icaltimezone_get_component (zone);
1041 return GNOME_Evolution_Calendar_InvalidObject;
1043 *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1045 return GNOME_Evolution_Calendar_Success;
1048 /* Add_timezone handler for the file backend */
1049 static ECalBackendSyncStatus
1050 e_cal_backend_file_add_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj)
1052 icalcomponent *tz_comp;
1053 ECalBackendFile *cbfile;
1054 ECalBackendFilePrivate *priv;
1056 cbfile = (ECalBackendFile *) backend;
1058 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), GNOME_Evolution_Calendar_OtherError);
1059 g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
1061 priv = cbfile->priv;
1063 tz_comp = icalparser_parse_string (tzobj);
1065 return GNOME_Evolution_Calendar_InvalidObject;
1067 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
1070 zone = icaltimezone_new ();
1071 icaltimezone_set_component (zone, tz_comp);
1072 if (!icalcomponent_get_timezone (priv->icalcomp,
1073 icaltimezone_get_tzid (zone))) {
1074 icalcomponent_add_component (priv->icalcomp, tz_comp);
1078 icaltimezone_free (zone, 1);
1081 return GNOME_Evolution_Calendar_Success;
1085 static ECalBackendSyncStatus
1086 e_cal_backend_file_set_default_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid)
1088 ECalBackendFile *cbfile;
1089 ECalBackendFilePrivate *priv;
1092 cbfile = E_CAL_BACKEND_FILE (backend);
1093 priv = cbfile->priv;
1095 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1097 /* Look up the VTIMEZONE in our icalcomponent. */
1098 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1100 return GNOME_Evolution_Calendar_ObjectNotFound;
1102 /* Set the default timezone to it. */
1103 priv->default_zone = zone;
1105 return GNOME_Evolution_Calendar_Success;
1110 gboolean search_needed;
1112 ECalBackendSExp *obj_sexp;
1113 ECalBackend *backend;
1114 icaltimezone *default_zone;
1118 match_recurrence_sexp (gpointer key, gpointer value, gpointer data)
1120 ECalComponent *comp = value;
1121 MatchObjectData *match_data = data;
1123 if ((!match_data->search_needed) ||
1124 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
1125 match_data->obj_list = g_list_append (match_data->obj_list,
1126 e_cal_component_get_as_string (comp));
1131 match_object_sexp (gpointer key, gpointer value, gpointer data)
1133 ECalBackendFileObject *obj_data = value;
1134 MatchObjectData *match_data = data;
1136 if ((!match_data->search_needed) ||
1137 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, obj_data->full_object, match_data->backend))) {
1138 match_data->obj_list = g_list_append (match_data->obj_list,
1139 e_cal_component_get_as_string (obj_data->full_object));
1141 /* match also recurrences */
1142 g_hash_table_foreach (obj_data->recurrences,
1143 (GHFunc) match_recurrence_sexp,
1148 /* Get_objects_in_range handler for the file backend */
1149 static ECalBackendSyncStatus
1150 e_cal_backend_file_get_object_list (ECalBackendSync *backend, EDataCal *cal, const char *sexp, GList **objects)
1152 ECalBackendFile *cbfile;
1153 ECalBackendFilePrivate *priv;
1154 MatchObjectData match_data;
1156 cbfile = E_CAL_BACKEND_FILE (backend);
1157 priv = cbfile->priv;
1159 g_message (G_STRLOC ": Getting object list (%s)", sexp);
1161 match_data.search_needed = TRUE;
1162 match_data.query = sexp;
1163 match_data.obj_list = NULL;
1164 match_data.backend = E_CAL_BACKEND (backend);
1165 match_data.default_zone = priv->default_zone;
1167 if (!strcmp (sexp, "#t"))
1168 match_data.search_needed = FALSE;
1170 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1171 if (!match_data.obj_sexp)
1172 return GNOME_Evolution_Calendar_InvalidQuery;
1174 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1176 *objects = match_data.obj_list;
1178 return GNOME_Evolution_Calendar_Success;
1181 /* get_query handler for the file backend */
1183 e_cal_backend_file_start_query (ECalBackend *backend, EDataCalView *query)
1185 ECalBackendFile *cbfile;
1186 ECalBackendFilePrivate *priv;
1187 MatchObjectData match_data;
1189 cbfile = E_CAL_BACKEND_FILE (backend);
1190 priv = cbfile->priv;
1192 g_message (G_STRLOC ": Starting query (%s)", e_data_cal_view_get_text (query));
1194 /* try to match all currently existing objects */
1195 match_data.search_needed = TRUE;
1196 match_data.query = e_data_cal_view_get_text (query);
1197 match_data.obj_list = NULL;
1198 match_data.backend = backend;
1199 match_data.default_zone = priv->default_zone;
1201 if (!strcmp (match_data.query, "#t"))
1202 match_data.search_needed = FALSE;
1204 match_data.obj_sexp = e_data_cal_view_get_object_sexp (query);
1205 if (!match_data.obj_sexp) {
1206 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_InvalidQuery);
1210 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1212 /* notify listeners of all objects */
1213 if (match_data.obj_list) {
1214 e_data_cal_view_notify_objects_added (query, (const GList *) match_data.obj_list);
1217 g_list_foreach (match_data.obj_list, (GFunc) g_free, NULL);
1218 g_list_free (match_data.obj_list);
1221 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
1225 free_busy_instance (ECalComponent *comp,
1226 time_t instance_start,
1227 time_t instance_end,
1230 icalcomponent *vfb = data;
1232 icalparameter *param;
1233 struct icalperiodtype ipt;
1234 icaltimezone *utc_zone;
1236 utc_zone = icaltimezone_get_utc_timezone ();
1238 ipt.start = icaltime_from_timet_with_zone (instance_start, FALSE, utc_zone);
1239 ipt.end = icaltime_from_timet_with_zone (instance_end, FALSE, utc_zone);
1240 ipt.duration = icaldurationtype_null_duration ();
1242 /* add busy information to the vfb component */
1243 prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
1244 icalproperty_set_freebusy (prop, ipt);
1246 param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
1247 icalproperty_add_parameter (prop, param);
1249 icalcomponent_add_property (vfb, prop);
1254 static icalcomponent *
1255 create_user_free_busy (ECalBackendFile *cbfile, const char *address, const char *cn,
1256 time_t start, time_t end)
1258 ECalBackendFilePrivate *priv;
1261 icaltimezone *utc_zone;
1262 ECalBackendSExp *obj_sexp;
1263 char *query, *iso_start, *iso_end;
1265 priv = cbfile->priv;
1267 /* create the (unique) VFREEBUSY object that we'll return */
1268 vfb = icalcomponent_new_vfreebusy ();
1269 if (address != NULL) {
1271 icalparameter *param;
1273 prop = icalproperty_new_organizer (address);
1274 if (prop != NULL && cn != NULL) {
1275 param = icalparameter_new_cn (cn);
1276 icalproperty_add_parameter (prop, param);
1279 icalcomponent_add_property (vfb, prop);
1281 utc_zone = icaltimezone_get_utc_timezone ();
1282 icalcomponent_set_dtstart (vfb, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
1283 icalcomponent_set_dtend (vfb, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
1285 /* add all objects in the given interval */
1286 iso_start = isodate_from_time_t (start);
1287 iso_end = isodate_from_time_t (end);
1288 query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") (make-time \"%s\")",
1289 iso_start, iso_end);
1290 obj_sexp = e_cal_backend_sexp_new (query);
1298 for (l = priv->comp; l; l = l->next) {
1299 ECalComponent *comp = l->data;
1300 icalcomponent *icalcomp, *vcalendar_comp;
1303 icalcomp = e_cal_component_get_icalcomponent (comp);
1307 /* If the event is TRANSPARENT, skip it. */
1308 prop = icalcomponent_get_first_property (icalcomp,
1309 ICAL_TRANSP_PROPERTY);
1311 icalproperty_transp transp_val = icalproperty_get_transp (prop);
1312 if (transp_val == ICAL_TRANSP_TRANSPARENT ||
1313 transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
1317 if (!e_cal_backend_sexp_match_comp (obj_sexp, l->data, E_CAL_BACKEND (cbfile)))
1320 vcalendar_comp = icalcomponent_get_parent (icalcomp);
1321 e_cal_recur_generate_instances (comp, start, end,
1326 priv->default_zone);
1332 /* Get_free_busy handler for the file backend */
1333 static ECalBackendSyncStatus
1334 e_cal_backend_file_get_free_busy (ECalBackendSync *backend, EDataCal *cal, GList *users,
1335 time_t start, time_t end, GList **freebusy)
1337 ECalBackendFile *cbfile;
1338 ECalBackendFilePrivate *priv;
1339 gchar *address, *name;
1344 cbfile = E_CAL_BACKEND_FILE (backend);
1345 priv = cbfile->priv;
1347 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1348 g_return_val_if_fail (start != -1 && end != -1, GNOME_Evolution_Calendar_InvalidRange);
1349 g_return_val_if_fail (start <= end, GNOME_Evolution_Calendar_InvalidRange);
1353 if (users == NULL) {
1354 if (e_cal_backend_mail_account_get_default (&address, &name)) {
1355 vfb = create_user_free_busy (cbfile, address, name, start, end);
1356 calobj = icalcomponent_as_ical_string (vfb);
1357 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1358 icalcomponent_free (vfb);
1363 for (l = users; l != NULL; l = l->next ) {
1365 if (e_cal_backend_mail_account_is_valid (address, &name)) {
1366 vfb = create_user_free_busy (cbfile, address, name, start, end);
1367 calobj = icalcomponent_as_ical_string (vfb);
1368 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1369 icalcomponent_free (vfb);
1375 return GNOME_Evolution_Calendar_Success;
1380 ECalBackendFile *backend;
1381 icalcomponent_kind kind;
1384 } ECalBackendFileComputeChangesData;
1387 e_cal_backend_file_compute_changes_foreach_key (const char *key, gpointer data)
1389 ECalBackendFileComputeChangesData *be_data = data;
1391 if (!lookup_component (be_data->backend, key)) {
1392 ECalComponent *comp;
1394 comp = e_cal_component_new ();
1395 if (be_data->kind == ICAL_VTODO_COMPONENT)
1396 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
1398 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
1400 e_cal_component_set_uid (comp, key);
1401 be_data->deletes = g_list_prepend (be_data->deletes, e_cal_component_get_as_string (comp));
1403 e_xmlhash_remove (be_data->ehash, key);
1407 static ECalBackendSyncStatus
1408 e_cal_backend_file_compute_changes (ECalBackendFile *cbfile, const char *change_id,
1409 GList **adds, GList **modifies, GList **deletes)
1411 ECalBackendFilePrivate *priv;
1414 ECalBackendFileComputeChangesData be_data;
1416 gchar *unescaped_uri;
1418 priv = cbfile->priv;
1420 /* FIXME Will this always work? */
1421 unescaped_uri = gnome_vfs_unescape_string (priv->uri, "");
1422 filename = g_strdup_printf ("%s-%s.db", unescaped_uri, change_id);
1423 ehash = e_xmlhash_new (filename);
1425 g_free (unescaped_uri);
1427 /* Calculate adds and modifies */
1428 for (i = priv->comp; i != NULL; i = i->next) {
1432 e_cal_component_get_uid (i->data, &uid);
1433 calobj = e_cal_component_get_as_string (i->data);
1435 g_assert (calobj != NULL);
1437 /* check what type of change has occurred, if any */
1438 switch (e_xmlhash_compare (ehash, uid, calobj)) {
1439 case E_XMLHASH_STATUS_SAME:
1441 case E_XMLHASH_STATUS_NOT_FOUND:
1442 *adds = g_list_prepend (*adds, g_strdup (calobj));
1443 e_xmlhash_add (ehash, uid, calobj);
1445 case E_XMLHASH_STATUS_DIFFERENT:
1446 *modifies = g_list_prepend (*modifies, g_strdup (calobj));
1447 e_xmlhash_add (ehash, uid, calobj);
1454 /* Calculate deletions */
1455 be_data.backend = cbfile;
1456 be_data.kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbfile));
1457 be_data.deletes = NULL;
1458 be_data.ehash = ehash;
1459 e_xmlhash_foreach_key (ehash, (EXmlHashFunc)e_cal_backend_file_compute_changes_foreach_key, &be_data);
1461 *deletes = be_data.deletes;
1463 e_xmlhash_write (ehash);
1464 e_xmlhash_destroy (ehash);
1466 return GNOME_Evolution_Calendar_Success;
1469 /* Get_changes handler for the file backend */
1470 static ECalBackendSyncStatus
1471 e_cal_backend_file_get_changes (ECalBackendSync *backend, EDataCal *cal, const char *change_id,
1472 GList **adds, GList **modifies, GList **deletes)
1474 ECalBackendFile *cbfile;
1475 ECalBackendFilePrivate *priv;
1477 cbfile = E_CAL_BACKEND_FILE (backend);
1478 priv = cbfile->priv;
1480 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1481 g_return_val_if_fail (change_id != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1483 return e_cal_backend_file_compute_changes (cbfile, change_id, adds, modifies, deletes);
1486 /* Discard_alarm handler for the file backend */
1487 static ECalBackendSyncStatus
1488 e_cal_backend_file_discard_alarm (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *auid)
1490 /* we just do nothing with the alarm */
1491 return GNOME_Evolution_Calendar_Success;
1494 static icaltimezone *
1495 e_cal_backend_file_internal_get_default_timezone (ECalBackend *backend)
1497 ECalBackendFile *cbfile;
1498 ECalBackendFilePrivate *priv;
1500 cbfile = E_CAL_BACKEND_FILE (backend);
1501 priv = cbfile->priv;
1503 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1505 return priv->default_zone;
1508 static icaltimezone *
1509 e_cal_backend_file_internal_get_timezone (ECalBackend *backend, const char *tzid)
1511 ECalBackendFile *cbfile;
1512 ECalBackendFilePrivate *priv;
1515 cbfile = E_CAL_BACKEND_FILE (backend);
1516 priv = cbfile->priv;
1518 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1520 if (!strcmp (tzid, "UTC"))
1521 zone = icaltimezone_get_utc_timezone ();
1523 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1525 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1532 sanitize_component (ECalBackendFile *cbfile, ECalComponent *comp)
1534 ECalComponentDateTime dt;
1535 icaltimezone *zone, *default_zone;
1537 /* Check dtstart, dtend and due's timezone, and convert it to local
1538 * default timezone if the timezone is not in our builtin timezone
1540 e_cal_component_get_dtstart (comp, &dt);
1541 if (dt.value && dt.tzid) {
1542 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1544 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1545 g_free ((char *)dt.tzid);
1546 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1547 e_cal_component_set_dtstart (comp, &dt);
1550 e_cal_component_free_datetime (&dt);
1552 e_cal_component_get_dtend (comp, &dt);
1553 if (dt.value && dt.tzid) {
1554 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1556 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1557 g_free ((char *)dt.tzid);
1558 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1559 e_cal_component_set_dtend (comp, &dt);
1562 e_cal_component_free_datetime (&dt);
1564 e_cal_component_get_due (comp, &dt);
1565 if (dt.value && dt.tzid) {
1566 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1568 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1569 g_free ((char *)dt.tzid);
1570 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1571 e_cal_component_set_due (comp, &dt);
1574 e_cal_component_free_datetime (&dt);
1575 e_cal_component_abort_sequence (comp);
1580 static ECalBackendSyncStatus
1581 e_cal_backend_file_create_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj, char **uid)
1583 ECalBackendFile *cbfile;
1584 ECalBackendFilePrivate *priv;
1585 icalcomponent *icalcomp;
1586 icalcomponent_kind kind;
1587 ECalComponent *comp;
1588 const char *comp_uid;
1589 struct icaltimetype current;
1591 cbfile = E_CAL_BACKEND_FILE (backend);
1592 priv = cbfile->priv;
1594 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1595 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1597 icalcomp = icalparser_parse_string ((char *) calobj);
1599 return GNOME_Evolution_Calendar_InvalidObject;
1601 /* FIXME Check kind with the parent */
1602 kind = icalcomponent_isa (icalcomp);
1603 if (kind != ICAL_VEVENT_COMPONENT && kind != ICAL_VTODO_COMPONENT) {
1604 icalcomponent_free (icalcomp);
1605 return GNOME_Evolution_Calendar_InvalidObject;
1609 comp_uid = icalcomponent_get_uid (icalcomp);
1611 /* check the object is not in our cache */
1612 if (lookup_component (cbfile, comp_uid)) {
1613 icalcomponent_free (icalcomp);
1614 return GNOME_Evolution_Calendar_CardIdAlreadyExists;
1617 /* Create the cal component */
1618 comp = e_cal_component_new ();
1619 e_cal_component_set_icalcomponent (comp, icalcomp);
1621 /* Set the created and last modified times on the component */
1622 current = icaltime_from_timet (time (NULL), 0);
1623 e_cal_component_set_created (comp, ¤t);
1624 e_cal_component_set_last_modified (comp, ¤t);
1626 /* sanitize the component*/
1627 sanitize_component (cbfile, comp);
1629 /* Add the object */
1630 add_component (cbfile, comp, TRUE);
1635 /* Return the UID */
1637 *uid = g_strdup (comp_uid);
1639 return GNOME_Evolution_Calendar_Success;
1642 static ECalBackendSyncStatus
1643 e_cal_backend_file_modify_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj,
1644 CalObjModType mod, char **old_object)
1646 ECalBackendFile *cbfile;
1647 ECalBackendFilePrivate *priv;
1648 icalcomponent *icalcomp;
1649 icalcomponent_kind kind;
1650 const char *comp_uid, *rid;
1652 ECalComponent *comp, *recurrence;
1653 ECalBackendFileObject *obj_data;
1654 struct icaltimetype current;
1656 cbfile = E_CAL_BACKEND_FILE (backend);
1657 priv = cbfile->priv;
1659 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1660 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1662 icalcomp = icalparser_parse_string ((char *) calobj);
1664 return GNOME_Evolution_Calendar_InvalidObject;
1666 /* check kind with the parent */
1667 kind = icalcomponent_isa (icalcomp);
1668 if (kind != ICAL_VEVENT_COMPONENT && kind != ICAL_VTODO_COMPONENT) {
1669 icalcomponent_free (icalcomp);
1670 return GNOME_Evolution_Calendar_InvalidObject;
1674 comp_uid = icalcomponent_get_uid (icalcomp);
1676 /* Get the object from our cache */
1677 if (!(obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid))) {
1678 icalcomponent_free (icalcomp);
1679 return GNOME_Evolution_Calendar_ObjectNotFound;
1682 /* Create the cal component */
1683 comp = e_cal_component_new ();
1684 e_cal_component_set_icalcomponent (comp, icalcomp);
1686 /* Set the last modified time on the component */
1687 current = icaltime_from_timet (time (NULL), 0);
1688 e_cal_component_set_last_modified (comp, ¤t);
1690 /* sanitize the component*/
1691 sanitize_component (cbfile, comp);
1693 /* handle mod_type */
1695 case CALOBJ_MOD_THIS :
1696 rid = get_rid_string (comp);
1697 if (!rid || !*rid) {
1698 g_object_unref (comp);
1699 return GNOME_Evolution_Calendar_ObjectNotFound;
1702 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
1703 &real_rid, &recurrence)) {
1704 /* remove the component from our data */
1705 icalcomponent_remove_component (priv->icalcomp,
1706 e_cal_component_get_icalcomponent (recurrence));
1707 priv->comp = g_list_remove (priv->comp, recurrence);
1708 g_hash_table_remove (obj_data->recurrences, rid);
1712 g_object_unref (recurrence);
1716 old = e_cal_component_get_as_string (obj_data->full_object);
1718 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
1719 get_rid_icaltime (comp),
1722 new = e_cal_component_get_as_string (obj_data->full_object);
1724 e_cal_backend_notify_object_modified (E_CAL_BACKEND (backend), old, new);
1730 /* add the detached instance */
1731 g_hash_table_insert (obj_data->recurrences, g_strdup (get_rid_string (comp)), comp);
1733 case CALOBJ_MOD_THISANDPRIOR :
1735 case CALOBJ_MOD_THISANDFUTURE :
1737 case CALOBJ_MOD_ALL :
1738 /* in this case, we blow away all recurrences, and start over
1739 with a clean component */
1740 /* Remove the old version */
1741 remove_component (cbfile, obj_data->full_object);
1743 /* Add the new object */
1744 add_component (cbfile, comp, TRUE);
1751 *old_object = e_cal_component_get_as_string (comp);
1753 return GNOME_Evolution_Calendar_Success;
1757 remove_instance (ECalBackendFile *cbfile, ECalBackendFileObject *obj_data, const char *rid)
1760 ECalComponent *comp;
1766 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, &hash_rid, &comp)) {
1767 /* remove the component from our data */
1768 icalcomponent_remove_component (cbfile->priv->icalcomp,
1769 e_cal_component_get_icalcomponent (comp));
1770 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
1771 g_hash_table_remove (obj_data->recurrences, rid);
1773 /* update the set of categories */
1774 e_cal_component_get_categories_list (comp, &categories);
1775 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
1776 e_cal_component_free_categories_list (categories);
1780 g_object_unref (comp);
1785 /* remove the component from our data, temporarily */
1786 icalcomponent_remove_component (cbfile->priv->icalcomp,
1787 e_cal_component_get_icalcomponent (obj_data->full_object));
1788 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
1790 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
1791 icaltime_from_string (rid), CALOBJ_MOD_THIS);
1793 /* add the modified object to the beginning of the list,
1794 so that it's always before any detached instance we
1796 cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
1800 ECalBackendFile *cbfile;
1801 ECalBackendFileObject *obj_data;
1804 } RemoveRecurrenceData;
1807 remove_object_instance_cb (gpointer key, gpointer value, gpointer user_data)
1809 time_t fromtt, instancett;
1812 ECalComponent *instance = value;
1813 RemoveRecurrenceData *rrdata = user_data;
1815 fromtt = icaltime_as_timet (icaltime_from_string (rrdata->rid));
1816 instancett = icaltime_as_timet (get_rid_icaltime (instance));
1818 if (fromtt > 0 && instancett > 0) {
1819 if ((rrdata->mod == CALOBJ_MOD_THISANDPRIOR && instancett <= fromtt) ||
1820 (rrdata->mod == CALOBJ_MOD_THISANDFUTURE && instancett >= fromtt)) {
1821 /* remove the component from our data */
1822 icalcomponent_remove_component (rrdata->cbfile->priv->icalcomp,
1823 e_cal_component_get_icalcomponent (instance));
1824 rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
1826 /* update the set of categories */
1827 e_cal_component_get_categories_list (instance, &categories);
1828 e_cal_backend_unref_categories (E_CAL_BACKEND (rrdata->cbfile), categories);
1829 e_cal_component_free_categories_list (categories);
1833 g_object_unref (instance);
1842 /* Remove_object handler for the file backend */
1843 static ECalBackendSyncStatus
1844 e_cal_backend_file_remove_object (ECalBackendSync *backend, EDataCal *cal,
1845 const char *uid, const char *rid,
1846 CalObjModType mod, char **object)
1848 ECalBackendFile *cbfile;
1849 ECalBackendFilePrivate *priv;
1850 ECalBackendFileObject *obj_data;
1851 ECalComponent *comp;
1853 RemoveRecurrenceData rrdata;
1855 cbfile = E_CAL_BACKEND_FILE (backend);
1856 priv = cbfile->priv;
1858 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1859 g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1861 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1863 return GNOME_Evolution_Calendar_ObjectNotFound;
1865 comp = obj_data->full_object;
1868 case CALOBJ_MOD_ALL :
1869 *object = e_cal_component_get_as_string (comp);
1870 remove_component (cbfile, comp);
1872 case CALOBJ_MOD_THIS :
1874 return GNOME_Evolution_Calendar_ObjectNotFound;
1876 remove_instance (cbfile, obj_data, rid);
1878 case CALOBJ_MOD_THISANDPRIOR :
1879 case CALOBJ_MOD_THISANDFUTURE :
1881 return GNOME_Evolution_Calendar_ObjectNotFound;
1883 /* remove the component from our data, temporarily */
1884 icalcomponent_remove_component (priv->icalcomp,
1885 e_cal_component_get_icalcomponent (comp));
1886 priv->comp = g_list_remove (priv->comp, comp);
1888 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (comp),
1889 icaltime_from_string (rid), mod);
1891 /* now remove all detached instances */
1892 rrdata.cbfile = cbfile;
1893 rrdata.obj_data = obj_data;
1896 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
1898 /* add the modified object to the beginning of the list,
1899 so that it's always before any detached instance we
1901 priv->comp = g_list_prepend (priv->comp, comp);
1907 return GNOME_Evolution_Calendar_Success;
1911 cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp)
1913 ECalComponent *old_comp;
1915 /* Find the old version of the component. */
1916 old_comp = lookup_component (cbfile, icalcomponent_get_uid (icalcomp));
1921 remove_component (cbfile, old_comp);
1930 } ECalBackendFileTzidData;
1933 check_tzids (icalparameter *param, void *data)
1935 ECalBackendFileTzidData *tzdata = data;
1938 tzid = icalparameter_get_tzid (param);
1939 if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
1940 tzdata->found = FALSE;
1943 /* Update_objects handler for the file backend. */
1944 static ECalBackendSyncStatus
1945 e_cal_backend_file_receive_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj)
1947 ECalBackendFile *cbfile;
1948 ECalBackendFilePrivate *priv;
1949 icalcomponent *toplevel_comp, *icalcomp = NULL;
1950 icalcomponent_kind kind;
1951 icalproperty_method method;
1952 icalcomponent *subcomp;
1954 ECalComponent *comp;
1955 struct icaltimetype current;
1956 ECalBackendFileTzidData tzdata;
1957 ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;
1959 cbfile = E_CAL_BACKEND_FILE (backend);
1960 priv = cbfile->priv;
1962 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
1963 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_InvalidObject);
1965 /* Pull the component from the string and ensure that it is sane */
1966 toplevel_comp = icalparser_parse_string ((char *) calobj);
1968 return GNOME_Evolution_Calendar_InvalidObject;
1970 kind = icalcomponent_isa (toplevel_comp);
1971 if (kind != ICAL_VCALENDAR_COMPONENT) {
1972 /* If its not a VCALENDAR, make it one to simplify below */
1973 icalcomp = toplevel_comp;
1974 toplevel_comp = e_cal_util_new_top_level ();
1975 icalcomponent_add_component (toplevel_comp, icalcomp);
1978 method = icalcomponent_get_method (toplevel_comp);
1980 /* Build a list of timezones so we can make sure all the objects have valid info */
1981 tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1983 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
1987 zone = icaltimezone_new ();
1988 if (icaltimezone_set_component (zone, subcomp))
1989 g_hash_table_insert (tzdata.zones, g_strdup (icaltimezone_get_tzid (zone)), NULL);
1991 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
1994 /* First we make sure all the components are usuable */
1996 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
1998 /* We ignore anything except VEVENT, VTODO and VJOURNAL
2000 icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
2002 switch (child_kind) {
2003 case ICAL_VEVENT_COMPONENT:
2004 case ICAL_VTODO_COMPONENT:
2005 case ICAL_VJOURNAL_COMPONENT:
2006 tzdata.found = TRUE;
2007 icalcomponent_foreach_tzid (subcomp, check_tzids, &tzdata);
2009 if (!tzdata.found) {
2010 status = GNOME_Evolution_Calendar_InvalidObject;
2014 if (!icalcomponent_get_uid (subcomp)) {
2015 status = GNOME_Evolution_Calendar_InvalidObject;
2019 comps = g_list_prepend (comps, subcomp);
2026 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
2029 /* Now we manipulate the components we care about */
2030 for (l = comps; l; l = l->next) {
2033 /* Create the cal component */
2034 comp = e_cal_component_new ();
2035 e_cal_component_set_icalcomponent (comp, subcomp);
2037 /* Set the created and last modified times on the component */
2038 current = icaltime_from_timet (time (NULL), 0);
2039 e_cal_component_set_created (comp, ¤t);
2040 e_cal_component_set_last_modified (comp, ¤t);
2042 /* sanitize the component*/
2043 sanitize_component (cbfile, comp);
2046 case ICAL_METHOD_PUBLISH:
2047 case ICAL_METHOD_REQUEST:
2048 /* FIXME Need to see the new create/modify stuff before we set this up */
2050 case ICAL_METHOD_REPLY:
2051 /* FIXME Update the status of the user, if we are the organizer */
2053 case ICAL_METHOD_ADD:
2054 /* FIXME This should be doable once all the recurid stuff is done */
2056 case ICAL_METHOD_COUNTER:
2057 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2060 case ICAL_METHOD_DECLINECOUNTER:
2061 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2064 case ICAL_METHOD_CANCEL:
2065 /* FIXME Do we need to remove the subcomp so it isn't merged? */
2066 if (cancel_received_object (cbfile, subcomp)) {
2067 const char *calobj = icalcomponent_as_ical_string (subcomp);
2068 e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend), icalcomponent_get_uid (subcomp), calobj);
2072 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2076 g_list_free (comps);
2078 /* Merge the iCalendar components with our existing VCALENDAR,
2079 resolving any conflicting TZIDs. */
2080 icalcomponent_merge_component (priv->icalcomp, toplevel_comp);
2085 g_hash_table_destroy (tzdata.zones);
2090 static ECalBackendSyncStatus
2091 e_cal_backend_file_send_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj)
2093 /* FIXME Put in a util routine to send stuff via email */
2095 return GNOME_Evolution_Calendar_Success;
2098 /* Object initialization function for the file backend */
2100 e_cal_backend_file_init (ECalBackendFile *cbfile, ECalBackendFileClass *class)
2102 ECalBackendFilePrivate *priv;
2104 priv = g_new0 (ECalBackendFilePrivate, 1);
2105 cbfile->priv = priv;
2108 priv->file_name = g_strdup ("calendar.ics");
2109 priv->read_only = FALSE;
2110 priv->icalcomp = NULL;
2111 priv->comp_uid_hash = NULL;
2114 /* The timezone defaults to UTC. */
2115 priv->default_zone = icaltimezone_get_utc_timezone ();
2118 /* Class initialization function for the file backend */
2120 e_cal_backend_file_class_init (ECalBackendFileClass *class)
2122 GObjectClass *object_class;
2123 ECalBackendClass *backend_class;
2124 ECalBackendSyncClass *sync_class;
2126 object_class = (GObjectClass *) class;
2127 backend_class = (ECalBackendClass *) class;
2128 sync_class = (ECalBackendSyncClass *) class;
2130 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
2132 object_class->dispose = e_cal_backend_file_dispose;
2133 object_class->finalize = e_cal_backend_file_finalize;
2135 sync_class->is_read_only_sync = e_cal_backend_file_is_read_only;
2136 sync_class->get_cal_address_sync = e_cal_backend_file_get_cal_address;
2137 sync_class->get_alarm_email_address_sync = e_cal_backend_file_get_alarm_email_address;
2138 sync_class->get_ldap_attribute_sync = e_cal_backend_file_get_ldap_attribute;
2139 sync_class->get_static_capabilities_sync = e_cal_backend_file_get_static_capabilities;
2140 sync_class->open_sync = e_cal_backend_file_open;
2141 sync_class->remove_sync = e_cal_backend_file_remove;
2142 sync_class->create_object_sync = e_cal_backend_file_create_object;
2143 sync_class->modify_object_sync = e_cal_backend_file_modify_object;
2144 sync_class->remove_object_sync = e_cal_backend_file_remove_object;
2145 sync_class->discard_alarm_sync = e_cal_backend_file_discard_alarm;
2146 sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
2147 sync_class->send_objects_sync = e_cal_backend_file_send_objects;
2148 sync_class->get_default_object_sync = e_cal_backend_file_get_default_object;
2149 sync_class->get_object_sync = e_cal_backend_file_get_object;
2150 sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
2151 sync_class->get_timezone_sync = e_cal_backend_file_get_timezone;
2152 sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
2153 sync_class->set_default_timezone_sync = e_cal_backend_file_set_default_timezone;
2154 sync_class->get_freebusy_sync = e_cal_backend_file_get_free_busy;
2155 sync_class->get_changes_sync = e_cal_backend_file_get_changes;
2157 backend_class->is_loaded = e_cal_backend_file_is_loaded;
2158 backend_class->start_query = e_cal_backend_file_start_query;
2159 backend_class->get_mode = e_cal_backend_file_get_mode;
2160 backend_class->set_mode = e_cal_backend_file_set_mode;
2162 backend_class->internal_get_default_timezone = e_cal_backend_file_internal_get_default_timezone;
2163 backend_class->internal_get_timezone = e_cal_backend_file_internal_get_timezone;
2168 * e_cal_backend_file_get_type:
2171 * Registers the #ECalBackendFile class if necessary, and returns the type ID
2174 * Return value: The type ID of the #ECalBackendFile class.
2177 e_cal_backend_file_get_type (void)
2179 static GType e_cal_backend_file_type = 0;
2181 if (!e_cal_backend_file_type) {
2182 static GTypeInfo info = {
2183 sizeof (ECalBackendFileClass),
2184 (GBaseInitFunc) NULL,
2185 (GBaseFinalizeFunc) NULL,
2186 (GClassInitFunc) e_cal_backend_file_class_init,
2188 sizeof (ECalBackendFile),
2190 (GInstanceInitFunc) e_cal_backend_file_init
2192 e_cal_backend_file_type = g_type_register_static (E_TYPE_CAL_BACKEND_SYNC,
2193 "ECalBackendFile", &info, 0);
2196 return e_cal_backend_file_type;
2200 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile, const char *file_name)
2202 ECalBackendFilePrivate *priv;
2204 g_return_if_fail (cbfile != NULL);
2205 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
2206 g_return_if_fail (file_name != NULL);
2208 priv = cbfile->priv;
2210 if (priv->file_name)
2211 g_free (priv->file_name);
2213 priv->file_name = g_strdup (file_name);
2217 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
2219 ECalBackendFilePrivate *priv;
2221 g_return_val_if_fail (cbfile != NULL, NULL);
2222 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
2224 priv = cbfile->priv;
2226 return priv->file_name;
2229 ECalBackendSyncStatus
2230 e_cal_backend_file_reload (ECalBackendFile *cbfile)
2232 ECalBackendFilePrivate *priv;
2234 ECalBackendSyncStatus status;
2236 priv = cbfile->priv;
2238 str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
2240 return GNOME_Evolution_Calendar_OtherError;
2242 if (access (str_uri, R_OK) == 0) {
2243 status = reload_cal (cbfile, str_uri);
2244 if (access (str_uri, W_OK) != 0)
2245 priv->read_only = TRUE;
2247 status = GNOME_Evolution_Calendar_NoSuchCal;