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 Lesser 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 Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28 #include <sys/types.h>
31 #include <bonobo/bonobo-exception.h>
32 #include <bonobo/bonobo-moniker-util.h>
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
35 #include <libgnomevfs/gnome-vfs.h>
36 #include "libedataserver/e-data-server-util.h"
37 #include "libedataserver/e-xml-hash-utils.h"
38 #include <libecal/e-cal-recur.h>
39 #include <libecal/e-cal-time-util.h>
40 #include <libecal/e-cal-util.h>
41 #include <libedata-cal/e-cal-backend-util.h>
42 #include <libedata-cal/e-cal-backend-sexp.h>
43 #include "e-cal-backend-file-events.h"
49 /* Placeholder for each component and its recurrences */
51 ECalComponent *full_object;
52 GHashTable *recurrences;
53 GList *recurrences_list;
54 } ECalBackendFileObject;
56 /* Private part of the ECalBackendFile structure */
57 struct _ECalBackendFilePrivate {
58 /* URI where the calendar data is stored */
61 /* Filename in the dir */
67 /* locked in high-level functions to ensure data is consistent
68 * in idle and CORBA thread(s?); because high-level functions
69 * may call other high-level functions the mutex must allow
72 GStaticRecMutex idle_save_rmutex;
74 /* Toplevel VCALENDAR component */
75 icalcomponent *icalcomp;
77 /* All the objects in the calendar, hashed by UID. The
78 * hash key *is* the uid returned by cal_component_get_uid(); it is not
79 * copied, so don't free it when you remove an object from the hash
80 * table. Each item in the hash table is a ECalBackendFileObject.
82 GHashTable *comp_uid_hash;
86 /* The calendar's default timezone, used for resolving DATE and
87 floating DATE-TIME values. */
88 icaltimezone *default_zone;
90 /* The list of live queries */
98 static void e_cal_backend_file_dispose (GObject *object);
99 static void e_cal_backend_file_finalize (GObject *object);
101 static ECalBackendSyncClass *parent_class;
103 static ECalBackendSyncStatus
104 e_cal_backend_file_add_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj);
106 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
108 free_object_data (gpointer data)
110 ECalBackendFileObject *obj_data = data;
112 if (obj_data->full_object)
113 g_object_unref (obj_data->full_object);
114 g_hash_table_destroy (obj_data->recurrences);
115 g_list_free (obj_data->recurrences_list);
120 /* Saves the calendar data */
122 save_file_when_idle (gpointer user_data)
124 ECalBackendFilePrivate *priv;
125 GnomeVFSURI *uri, *backup_uri;
126 GnomeVFSHandle *handle = NULL;
127 GnomeVFSResult result = GNOME_VFS_ERROR_BAD_FILE;
128 GnomeVFSFileSize out;
129 gchar *tmp, *backup_uristr;
131 ECalBackendFile *cbfile = user_data;
134 g_assert (priv->uri != NULL);
135 g_assert (priv->icalcomp != NULL);
137 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
138 if (!priv->is_dirty) {
139 priv->dirty_idle_id = 0;
140 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
144 uri = gnome_vfs_uri_new (priv->uri);
146 goto error_malformed_uri;
148 /* save calendar to backup file */
149 tmp = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
151 gnome_vfs_uri_unref (uri);
152 goto error_malformed_uri;
155 backup_uristr = g_strconcat (tmp, "~", NULL);
156 backup_uri = gnome_vfs_uri_new (backup_uristr);
159 g_free (backup_uristr);
162 gnome_vfs_uri_unref (uri);
163 goto error_malformed_uri;
166 result = gnome_vfs_create_uri (&handle, backup_uri,
167 GNOME_VFS_OPEN_WRITE,
169 if (result != GNOME_VFS_OK) {
170 gnome_vfs_uri_unref (uri);
171 gnome_vfs_uri_unref (backup_uri);
175 buf = icalcomponent_as_ical_string (priv->icalcomp);
176 result = gnome_vfs_write (handle, buf, strlen (buf) * sizeof (char), &out);
177 gnome_vfs_close (handle);
178 if (result != GNOME_VFS_OK) {
179 gnome_vfs_uri_unref (uri);
180 gnome_vfs_uri_unref (backup_uri);
184 /* now copy the temporary file to the real file */
185 result = gnome_vfs_move_uri (backup_uri, uri, TRUE);
187 gnome_vfs_uri_unref (uri);
188 gnome_vfs_uri_unref (backup_uri);
189 if (result != GNOME_VFS_OK)
192 priv->is_dirty = FALSE;
193 priv->dirty_idle_id = 0;
195 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
200 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
201 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
202 _("Cannot save calendar data: Malformed URI."));
206 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
207 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), gnome_vfs_result_to_string (result));
212 save (ECalBackendFile *cbfile)
214 ECalBackendFilePrivate *priv;
218 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
219 priv->is_dirty = TRUE;
221 if (!priv->dirty_idle_id)
222 priv->dirty_idle_id = g_idle_add ((GSourceFunc) save_file_when_idle, cbfile);
224 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
228 free_calendar_components (GHashTable *comp_uid_hash, icalcomponent *top_icomp)
231 g_hash_table_destroy (comp_uid_hash);
234 icalcomponent_free (top_icomp);
238 free_calendar_data (ECalBackendFile *cbfile)
240 ECalBackendFilePrivate *priv;
244 free_calendar_components (priv->comp_uid_hash, priv->icalcomp);
245 priv->comp_uid_hash = NULL;
246 priv->icalcomp = NULL;
248 g_list_free (priv->comp);
252 /* Dispose handler for the file backend */
254 e_cal_backend_file_dispose (GObject *object)
256 ECalBackendFile *cbfile;
257 ECalBackendFilePrivate *priv;
259 cbfile = E_CAL_BACKEND_FILE (object);
262 /* Save if necessary */
264 save_file_when_idle (cbfile);
266 free_calendar_data (cbfile);
268 if (G_OBJECT_CLASS (parent_class)->dispose)
269 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
272 /* Finalize handler for the file backend */
274 e_cal_backend_file_finalize (GObject *object)
276 ECalBackendFile *cbfile;
277 ECalBackendFilePrivate *priv;
279 g_return_if_fail (object != NULL);
280 g_return_if_fail (E_IS_CAL_BACKEND_FILE (object));
282 cbfile = E_CAL_BACKEND_FILE (object);
287 if (priv->dirty_idle_id) {
288 g_source_remove (priv->dirty_idle_id);
289 priv->dirty_idle_id = 0;
292 g_static_rec_mutex_free (&priv->idle_save_rmutex);
299 if (priv->default_zone && priv->default_zone != icaltimezone_get_utc_timezone ()) {
300 icaltimezone_free (priv->default_zone, 1);
302 priv->default_zone = NULL;
304 if (priv->file_name) {
305 g_free (priv->file_name);
306 priv->file_name = NULL;
311 if (G_OBJECT_CLASS (parent_class)->finalize)
312 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
317 /* Looks up a component by its UID on the backend's component hash table */
318 static ECalComponent *
319 lookup_component (ECalBackendFile *cbfile, const char *uid)
321 ECalBackendFilePrivate *priv;
322 ECalBackendFileObject *obj_data;
326 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
327 return obj_data ? obj_data->full_object : NULL;
332 /* Calendar backend methods */
334 /* Is_read_only handler for the file backend */
335 static ECalBackendSyncStatus
336 e_cal_backend_file_is_read_only (ECalBackendSync *backend, EDataCal *cal, gboolean *read_only)
338 ECalBackendFile *cbfile = (ECalBackendFile *) backend;
340 *read_only = cbfile->priv->read_only;
342 return GNOME_Evolution_Calendar_Success;
345 /* Get_email_address handler for the file backend */
346 static ECalBackendSyncStatus
347 e_cal_backend_file_get_cal_address (ECalBackendSync *backend, EDataCal *cal, char **address)
349 /* A file backend has no particular email address associated
350 * with it (although that would be a useful feature some day).
354 return GNOME_Evolution_Calendar_Success;
357 static ECalBackendSyncStatus
358 e_cal_backend_file_get_ldap_attribute (ECalBackendSync *backend, EDataCal *cal, char **attribute)
362 return GNOME_Evolution_Calendar_Success;
365 static ECalBackendSyncStatus
366 e_cal_backend_file_get_alarm_email_address (ECalBackendSync *backend, EDataCal *cal, char **address)
368 /* A file backend has no particular email address associated
369 * with it (although that would be a useful feature some day).
373 return GNOME_Evolution_Calendar_Success;
376 static ECalBackendSyncStatus
377 e_cal_backend_file_get_static_capabilities (ECalBackendSync *backend, EDataCal *cal, char **capabilities)
379 *capabilities = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS ","
380 CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
381 CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED ","
382 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR);
384 return GNOME_Evolution_Calendar_Success;
387 /* function to resolve timezones */
388 static icaltimezone *
389 resolve_tzid (const char *tzid, gpointer user_data)
391 icalcomponent *vcalendar_comp = user_data;
393 if (!tzid || !tzid[0])
395 else if (!strcmp (tzid, "UTC"))
396 return icaltimezone_get_utc_timezone ();
398 return icalcomponent_get_timezone (vcalendar_comp, tzid);
401 /* Checks if the specified component has a duplicated UID and if so changes it */
403 check_dup_uid (ECalBackendFile *cbfile, ECalComponent *comp)
405 ECalBackendFilePrivate *priv;
406 ECalBackendFileObject *obj_data;
407 const char *uid = NULL;
412 e_cal_component_get_uid (comp, &uid);
415 g_warning ("Checking for duplicate uid, the component does not have a valid UID skipping it\n");
419 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
421 return; /* Everything is fine */
423 d(g_message (G_STRLOC ": Got object with duplicated UID `%s', changing it...", uid));
425 new_uid = e_cal_component_gen_uid ();
426 e_cal_component_set_uid (comp, new_uid);
429 /* FIXME: I think we need to reset the SEQUENCE property and reset the
430 * CREATED/DTSTAMP/LAST-MODIFIED.
436 static struct icaltimetype
437 get_rid_icaltime (ECalComponent *comp)
439 ECalComponentRange range;
440 struct icaltimetype tt;
442 e_cal_component_get_recurid (comp, &range);
443 if (!range.datetime.value)
444 return icaltime_null_time ();
445 tt = *range.datetime.value;
446 e_cal_component_free_range (&range);
451 /* Tries to add an icalcomponent to the file backend. We only store the objects
452 * of the types we support; all others just remain in the toplevel component so
453 * that we don't lose them.
456 add_component (ECalBackendFile *cbfile, ECalComponent *comp, gboolean add_to_toplevel)
458 ECalBackendFilePrivate *priv;
459 ECalBackendFileObject *obj_data;
460 const char *uid = NULL;
464 e_cal_component_get_uid (comp, &uid);
467 g_warning ("The component does not have a valid UID skipping it\n");
471 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
472 if (e_cal_component_is_instance (comp)) {
475 rid = e_cal_component_get_recurid_as_string (comp);
477 if (g_hash_table_lookup (obj_data->recurrences, rid)) {
478 g_warning (G_STRLOC ": Tried to add an already existing recurrence");
482 obj_data = g_new0 (ECalBackendFileObject, 1);
483 obj_data->full_object = NULL;
484 obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
485 g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
488 g_hash_table_insert (obj_data->recurrences, g_strdup (rid), comp);
489 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
491 /* Ensure that the UID is unique; some broken implementations spit
492 * components with duplicated UIDs.
494 check_dup_uid (cbfile, comp);
497 if (obj_data->full_object) {
498 g_warning (G_STRLOC ": Tried to add an already existing object");
502 obj_data->full_object = comp;
504 obj_data = g_new0 (ECalBackendFileObject, 1);
505 obj_data->full_object = comp;
506 obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
508 g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
512 priv->comp = g_list_prepend (priv->comp, comp);
514 /* Put the object in the toplevel component if required */
516 if (add_to_toplevel) {
517 icalcomponent *icalcomp;
519 icalcomp = e_cal_component_get_icalcomponent (comp);
520 g_assert (icalcomp != NULL);
522 icalcomponent_add_component (priv->icalcomp, icalcomp);
528 /* g_hash_table_foreach_remove() callback to remove recurrences from the calendar */
530 remove_recurrence_cb (gpointer key, gpointer value, gpointer data)
533 icalcomponent *icalcomp;
534 ECalBackendFilePrivate *priv;
535 ECalComponent *comp = value;
536 ECalBackendFile *cbfile = data;
540 /* remove the recurrence from the top-level calendar */
541 icalcomp = e_cal_component_get_icalcomponent (comp);
542 g_assert (icalcomp != NULL);
544 icalcomponent_remove_component (priv->icalcomp, icalcomp);
546 /* remove it from our mapping */
547 l = g_list_find (priv->comp, comp);
548 priv->comp = g_list_delete_link (priv->comp, l);
553 /* Removes a component from the backend's hash and lists. Does not perform
554 * notification on the clients. Also removes the component from the toplevel
558 remove_component (ECalBackendFile *cbfile, const char *uid, ECalBackendFileObject *obj_data)
560 ECalBackendFilePrivate *priv;
561 icalcomponent *icalcomp;
566 /* Remove the icalcomp from the toplevel */
567 if (obj_data->full_object) {
568 icalcomp = e_cal_component_get_icalcomponent (obj_data->full_object);
569 g_assert (icalcomp != NULL);
571 icalcomponent_remove_component (priv->icalcomp, icalcomp);
573 /* Remove it from our mapping */
574 l = g_list_find (priv->comp, obj_data->full_object);
575 g_assert (l != NULL);
576 priv->comp = g_list_delete_link (priv->comp, l);
579 /* remove the recurrences also */
580 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_recurrence_cb, cbfile);
582 g_hash_table_remove (priv->comp_uid_hash, uid);
587 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
589 scan_vcalendar (ECalBackendFile *cbfile)
591 ECalBackendFilePrivate *priv;
595 g_assert (priv->icalcomp != NULL);
596 g_assert (priv->comp_uid_hash != NULL);
598 for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_ANY_COMPONENT);
599 icalcompiter_deref (&iter) != NULL;
600 icalcompiter_next (&iter)) {
601 icalcomponent *icalcomp;
602 icalcomponent_kind kind;
605 icalcomp = icalcompiter_deref (&iter);
607 kind = icalcomponent_isa (icalcomp);
609 if (!(kind == ICAL_VEVENT_COMPONENT
610 || kind == ICAL_VTODO_COMPONENT
611 || kind == ICAL_VJOURNAL_COMPONENT))
614 comp = e_cal_component_new ();
616 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
619 add_component (cbfile, comp, FALSE);
624 get_uri_string_for_gnome_vfs (ECalBackend *backend)
626 ECalBackendFile *cbfile;
627 ECalBackendFilePrivate *priv;
628 const char *master_uri;
629 char *full_uri, *str_uri;
632 cbfile = E_CAL_BACKEND_FILE (backend);
635 master_uri = e_cal_backend_get_uri (backend);
637 /* FIXME Check the error conditions a little more elegantly here */
638 if (g_strrstr ("tasks.ics", master_uri) || g_strrstr ("calendar.ics", master_uri)) {
639 g_warning (G_STRLOC ": Existing file name %s", master_uri);
644 full_uri = g_strdup_printf ("%s/%s", master_uri, priv->file_name);
645 uri = gnome_vfs_uri_new (full_uri);
651 str_uri = gnome_vfs_uri_to_string (uri,
652 (GNOME_VFS_URI_HIDE_USER_NAME
653 | GNOME_VFS_URI_HIDE_PASSWORD
654 | GNOME_VFS_URI_HIDE_HOST_NAME
655 | GNOME_VFS_URI_HIDE_HOST_PORT
656 | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD));
657 gnome_vfs_uri_unref (uri);
659 if (!str_uri || !strlen (str_uri)) {
668 /* Parses an open iCalendar file and loads it into the backend */
669 static ECalBackendSyncStatus
670 open_cal (ECalBackendFile *cbfile, const char *uristr)
672 ECalBackendFilePrivate *priv;
673 icalcomponent *icalcomp;
677 icalcomp = e_cal_util_parse_ics_file (uristr);
679 return GNOME_Evolution_Calendar_OtherError;
681 /* FIXME: should we try to demangle XROOT components and
682 * individual components as well?
685 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
686 icalcomponent_free (icalcomp);
688 return GNOME_Evolution_Calendar_OtherError;
691 priv->icalcomp = icalcomp;
692 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
694 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
695 scan_vcalendar (cbfile);
697 return GNOME_Evolution_Calendar_Success;
702 ECalBackend *backend;
703 GHashTable *old_uid_hash;
704 GHashTable *new_uid_hash;
709 notify_removals_cb (gpointer key, gpointer value, gpointer data)
711 BackendDeltaContext *context = data;
712 const gchar *uid = key;
713 ECalBackendFileObject *old_obj_data = value;
715 if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
716 icalcomponent *old_icomp;
721 /* Object was removed */
723 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
727 old_obj_str = icalcomponent_as_ical_string (old_icomp);
731 comp = e_cal_component_new_from_string (old_obj_str);
732 id = e_cal_component_get_id (comp);
735 e_cal_backend_notify_object_removed (context->backend, id, old_obj_str, NULL);
737 e_cal_component_free_id (id);
738 g_object_unref (comp);
743 notify_adds_modifies_cb (gpointer key, gpointer value, gpointer data)
745 BackendDeltaContext *context = data;
746 const gchar *uid = key;
747 ECalBackendFileObject *new_obj_data = value;
748 ECalBackendFileObject *old_obj_data;
749 icalcomponent *old_icomp, *new_icomp;
750 gchar *old_obj_str, *new_obj_str;
752 old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
755 /* Object was added */
757 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
761 new_obj_str = icalcomponent_as_ical_string (new_icomp);
765 e_cal_backend_notify_object_created (context->backend, new_obj_str);
767 old_icomp = e_cal_component_get_icalcomponent (old_obj_data->full_object);
768 new_icomp = e_cal_component_get_icalcomponent (new_obj_data->full_object);
769 if (!old_icomp || !new_icomp)
772 old_obj_str = icalcomponent_as_ical_string (old_icomp);
773 new_obj_str = icalcomponent_as_ical_string (new_icomp);
774 if (!old_obj_str || !new_obj_str)
777 if (strcmp (old_obj_str, new_obj_str)) {
778 /* Object was modified */
780 e_cal_backend_notify_object_modified (context->backend, old_obj_str, new_obj_str);
786 notify_changes (ECalBackendFile *cbfile, GHashTable *old_uid_hash, GHashTable *new_uid_hash)
788 BackendDeltaContext context;
790 context.backend = E_CAL_BACKEND (cbfile);
791 context.old_uid_hash = old_uid_hash;
792 context.new_uid_hash = new_uid_hash;
794 g_hash_table_foreach (old_uid_hash, (GHFunc) notify_removals_cb, &context);
795 g_hash_table_foreach (new_uid_hash, (GHFunc) notify_adds_modifies_cb, &context);
798 static ECalBackendSyncStatus
799 reload_cal (ECalBackendFile *cbfile, const char *uristr)
801 ECalBackendFilePrivate *priv;
802 icalcomponent *icalcomp, *icalcomp_old;
803 GHashTable *comp_uid_hash_old;
807 icalcomp = e_cal_util_parse_ics_file (uristr);
809 return GNOME_Evolution_Calendar_OtherError;
811 /* FIXME: should we try to demangle XROOT components and
812 * individual components as well?
815 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
816 icalcomponent_free (icalcomp);
818 return GNOME_Evolution_Calendar_OtherError;
821 /* Keep old data for comparison - free later */
823 icalcomp_old = priv->icalcomp;
824 priv->icalcomp = NULL;
826 comp_uid_hash_old = priv->comp_uid_hash;
827 priv->comp_uid_hash = NULL;
829 /* Load new calendar */
831 free_calendar_data (cbfile);
833 priv->icalcomp = icalcomp;
835 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
836 scan_vcalendar (cbfile);
838 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
840 /* Compare old and new versions of calendar */
842 notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
846 free_calendar_components (comp_uid_hash_old, icalcomp_old);
847 return GNOME_Evolution_Calendar_Success;
850 static ECalBackendSyncStatus
851 create_cal (ECalBackendFile *cbfile, const char *uristr)
854 ECalBackendFilePrivate *priv;
858 /* Create the directory to contain the file */
859 dirname = g_path_get_dirname (uristr);
860 if (g_mkdir_with_parents (dirname, 0700) != 0) {
862 return GNOME_Evolution_Calendar_NoSuchCal;
867 /* Create the new calendar information */
868 priv->icalcomp = e_cal_util_new_top_level ();
870 /* Create our internal data */
871 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
873 priv->uri = get_uri_string_for_gnome_vfs (E_CAL_BACKEND (cbfile));
877 return GNOME_Evolution_Calendar_Success;
881 get_uri_string (ECalBackend *backend)
883 gchar *str_uri, *full_uri;
885 str_uri = get_uri_string_for_gnome_vfs (backend);
886 full_uri = gnome_vfs_unescape_string (str_uri, "");
892 /* Open handler for the file backend */
893 static ECalBackendSyncStatus
894 e_cal_backend_file_open (ECalBackendSync *backend, EDataCal *cal, gboolean only_if_exists,
895 const char *username, const char *password)
897 ECalBackendFile *cbfile;
898 ECalBackendFilePrivate *priv;
900 ECalBackendSyncStatus status;
902 cbfile = E_CAL_BACKEND_FILE (backend);
904 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
906 /* Claim a succesful open if we are already open */
907 if (priv->uri && priv->comp_uid_hash) {
908 status = GNOME_Evolution_Calendar_Success;
912 str_uri = get_uri_string (E_CAL_BACKEND (backend));
914 status = GNOME_Evolution_Calendar_OtherError;
918 if (g_access (str_uri, R_OK) == 0) {
919 status = open_cal (cbfile, str_uri);
920 if (g_access (str_uri, W_OK) != 0)
921 priv->read_only = TRUE;
924 status = GNOME_Evolution_Calendar_NoSuchCal;
926 status = create_cal (cbfile, str_uri);
929 if (status == GNOME_Evolution_Calendar_Success) {
930 if (priv->default_zone) {
931 icalcomponent *icalcomp = icaltimezone_get_component (priv->default_zone);
933 icalcomponent_add_component (priv->icalcomp, icalcomponent_new_clone (icalcomp));
941 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
945 static ECalBackendSyncStatus
946 e_cal_backend_file_remove (ECalBackendSync *backend, EDataCal *cal)
948 ECalBackendFile *cbfile;
949 ECalBackendFilePrivate *priv;
950 char *str_uri = NULL, *dirname = NULL;
951 char *full_path = NULL;
954 GError *error = NULL;
955 ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;
957 cbfile = E_CAL_BACKEND_FILE (backend);
959 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
961 str_uri = get_uri_string (E_CAL_BACKEND (backend));
963 status = GNOME_Evolution_Calendar_OtherError;
967 if (g_access (str_uri, W_OK) != 0) {
968 status = GNOME_Evolution_Calendar_PermissionDenied;
972 /* remove all files in the directory */
973 dirname = g_path_get_dirname (str_uri);
974 dir = g_dir_open (dirname, 0, &error);
976 status = GNOME_Evolution_Calendar_PermissionDenied;
980 while ((fname = g_dir_read_name (dir))) {
981 full_path = g_build_filename (dirname, fname, NULL);
982 if (g_unlink (full_path) != 0) {
983 status = GNOME_Evolution_Calendar_OtherError;
991 /* remove the directory itself */
992 if (g_rmdir (dirname) != 0) {
993 status = GNOME_Evolution_Calendar_OtherError;
1004 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1008 /* is_loaded handler for the file backend */
1010 e_cal_backend_file_is_loaded (ECalBackend *backend)
1012 ECalBackendFile *cbfile;
1013 ECalBackendFilePrivate *priv;
1015 cbfile = E_CAL_BACKEND_FILE (backend);
1016 priv = cbfile->priv;
1018 return (priv->icalcomp != NULL);
1021 /* is_remote handler for the file backend */
1023 e_cal_backend_file_get_mode (ECalBackend *backend)
1025 ECalBackendFile *cbfile;
1026 ECalBackendFilePrivate *priv;
1028 cbfile = E_CAL_BACKEND_FILE (backend);
1029 priv = cbfile->priv;
1031 return CAL_MODE_LOCAL;
1034 /* Set_mode handler for the file backend */
1036 e_cal_backend_file_set_mode (ECalBackend *backend, CalMode mode)
1038 e_cal_backend_notify_mode (backend,
1039 GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
1040 GNOME_Evolution_Calendar_MODE_LOCAL);
1044 static ECalBackendSyncStatus
1045 e_cal_backend_file_get_default_object (ECalBackendSync *backend, EDataCal *cal, char **object)
1047 ECalComponent *comp;
1049 comp = e_cal_component_new ();
1051 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
1052 case ICAL_VEVENT_COMPONENT:
1053 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
1055 case ICAL_VTODO_COMPONENT:
1056 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
1058 case ICAL_VJOURNAL_COMPONENT:
1059 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
1062 g_object_unref (comp);
1063 return GNOME_Evolution_Calendar_ObjectNotFound;
1066 *object = e_cal_component_get_as_string (comp);
1067 g_object_unref (comp);
1069 return GNOME_Evolution_Calendar_Success;
1073 add_detached_recur_to_vcalendar (gpointer key, gpointer value, gpointer user_data)
1075 ECalComponent *recurrence = value;
1076 icalcomponent *vcalendar = user_data;
1078 icalcomponent_add_component (
1080 icalcomponent_new_clone (e_cal_component_get_icalcomponent (recurrence)));
1083 /* Get_object_component handler for the file backend */
1084 static ECalBackendSyncStatus
1085 e_cal_backend_file_get_object (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *rid, char **object)
1087 ECalBackendFile *cbfile;
1088 ECalBackendFilePrivate *priv;
1089 ECalBackendFileObject *obj_data;
1091 cbfile = E_CAL_BACKEND_FILE (backend);
1092 priv = cbfile->priv;
1094 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
1095 g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1096 g_assert (priv->comp_uid_hash != NULL);
1098 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1100 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1102 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1103 return GNOME_Evolution_Calendar_ObjectNotFound;
1107 ECalComponent *comp;
1109 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1111 *object = e_cal_component_get_as_string (comp);
1113 icalcomponent *icalcomp;
1114 struct icaltimetype itt;
1116 itt = icaltime_from_string (rid);
1117 icalcomp = e_cal_util_construct_instance (
1118 e_cal_component_get_icalcomponent (obj_data->full_object),
1121 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1122 return GNOME_Evolution_Calendar_ObjectNotFound;
1125 *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1127 icalcomponent_free (icalcomp);
1130 if (g_hash_table_size (obj_data->recurrences) > 0) {
1131 icalcomponent *icalcomp;
1133 /* if we have detached recurrences, return a VCALENDAR */
1134 icalcomp = e_cal_util_new_top_level ();
1135 icalcomponent_add_component (
1137 icalcomponent_new_clone (e_cal_component_get_icalcomponent (obj_data->full_object)));
1139 /* add all detached recurrences */
1140 g_hash_table_foreach (obj_data->recurrences, (GHFunc) add_detached_recur_to_vcalendar, icalcomp);
1142 *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1144 icalcomponent_free (icalcomp);
1146 *object = e_cal_component_get_as_string (obj_data->full_object);
1149 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1150 return GNOME_Evolution_Calendar_Success;
1153 /* Get_timezone_object handler for the file backend */
1154 static ECalBackendSyncStatus
1155 e_cal_backend_file_get_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid, char **object)
1157 ECalBackendFile *cbfile;
1158 ECalBackendFilePrivate *priv;
1160 icalcomponent *icalcomp;
1162 cbfile = E_CAL_BACKEND_FILE (backend);
1163 priv = cbfile->priv;
1165 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1166 g_return_val_if_fail (tzid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1168 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1170 if (!strcmp (tzid, "UTC")) {
1171 zone = icaltimezone_get_utc_timezone ();
1173 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1175 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1177 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1178 return GNOME_Evolution_Calendar_ObjectNotFound;
1183 icalcomp = icaltimezone_get_component (zone);
1185 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1186 return GNOME_Evolution_Calendar_InvalidObject;
1189 *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
1191 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1192 return GNOME_Evolution_Calendar_Success;
1195 /* Add_timezone handler for the file backend */
1196 static ECalBackendSyncStatus
1197 e_cal_backend_file_add_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj)
1199 icalcomponent *tz_comp;
1200 ECalBackendFile *cbfile;
1201 ECalBackendFilePrivate *priv;
1203 cbfile = (ECalBackendFile *) backend;
1205 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), GNOME_Evolution_Calendar_OtherError);
1206 g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
1208 priv = cbfile->priv;
1210 tz_comp = icalparser_parse_string (tzobj);
1212 return GNOME_Evolution_Calendar_InvalidObject;
1214 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
1217 zone = icaltimezone_new ();
1218 icaltimezone_set_component (zone, tz_comp);
1220 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1221 if (!icalcomponent_get_timezone (priv->icalcomp,
1222 icaltimezone_get_tzid (zone))) {
1223 icalcomponent_add_component (priv->icalcomp, tz_comp);
1226 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1228 icaltimezone_free (zone, 1);
1231 return GNOME_Evolution_Calendar_Success;
1234 static ECalBackendSyncStatus
1235 e_cal_backend_file_set_default_zone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj)
1237 icalcomponent *tz_comp;
1238 ECalBackendFile *cbfile;
1239 ECalBackendFilePrivate *priv;
1242 cbfile = (ECalBackendFile *) backend;
1244 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), GNOME_Evolution_Calendar_OtherError);
1245 g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
1247 priv = cbfile->priv;
1249 tz_comp = icalparser_parse_string (tzobj);
1251 return GNOME_Evolution_Calendar_InvalidObject;
1253 zone = icaltimezone_new ();
1254 icaltimezone_set_component (zone, tz_comp);
1256 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1257 if (priv->default_zone != icaltimezone_get_utc_timezone ())
1258 icaltimezone_free (priv->default_zone, 1);
1260 /* Set the default timezone to it. */
1261 priv->default_zone = zone;
1262 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1264 return GNOME_Evolution_Calendar_Success;
1269 gboolean search_needed;
1271 ECalBackendSExp *obj_sexp;
1272 ECalBackend *backend;
1273 icaltimezone *default_zone;
1277 match_recurrence_sexp (gpointer key, gpointer value, gpointer data)
1279 ECalComponent *comp = value;
1280 MatchObjectData *match_data = data;
1282 if ((!match_data->search_needed) ||
1283 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
1284 match_data->obj_list = g_list_append (match_data->obj_list,
1285 e_cal_component_get_as_string (comp));
1290 match_object_sexp (gpointer key, gpointer value, gpointer data)
1292 ECalBackendFileObject *obj_data = value;
1293 MatchObjectData *match_data = data;
1295 if (obj_data->full_object) {
1296 if ((!match_data->search_needed) ||
1297 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, obj_data->full_object, match_data->backend))) {
1298 match_data->obj_list = g_list_append (match_data->obj_list,
1299 e_cal_component_get_as_string (obj_data->full_object));
1303 /* match also recurrences */
1304 g_hash_table_foreach (obj_data->recurrences,
1305 (GHFunc) match_recurrence_sexp,
1309 /* Get_objects_in_range handler for the file backend */
1310 static ECalBackendSyncStatus
1311 e_cal_backend_file_get_object_list (ECalBackendSync *backend, EDataCal *cal, const char *sexp, GList **objects)
1313 ECalBackendFile *cbfile;
1314 ECalBackendFilePrivate *priv;
1315 MatchObjectData match_data;
1317 cbfile = E_CAL_BACKEND_FILE (backend);
1318 priv = cbfile->priv;
1320 d(g_message (G_STRLOC ": Getting object list (%s)", sexp));
1322 match_data.search_needed = TRUE;
1323 match_data.query = sexp;
1324 match_data.obj_list = NULL;
1325 match_data.backend = E_CAL_BACKEND (backend);
1326 match_data.default_zone = priv->default_zone;
1328 if (!strcmp (sexp, "#t"))
1329 match_data.search_needed = FALSE;
1331 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1332 if (!match_data.obj_sexp)
1333 return GNOME_Evolution_Calendar_InvalidQuery;
1335 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1336 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1337 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1339 *objects = match_data.obj_list;
1341 g_object_unref (match_data.obj_sexp);
1343 return GNOME_Evolution_Calendar_Success;
1346 /* Gets the list of attachments */
1347 static ECalBackendSyncStatus
1348 e_cal_backend_file_get_attachment_list (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *rid, GSList **list)
1351 /* TODO implement the function */
1352 return GNOME_Evolution_Calendar_Success;
1355 /* get_query handler for the file backend */
1357 e_cal_backend_file_start_query (ECalBackend *backend, EDataCalView *query)
1359 ECalBackendFile *cbfile;
1360 ECalBackendFilePrivate *priv;
1361 MatchObjectData match_data;
1363 cbfile = E_CAL_BACKEND_FILE (backend);
1364 priv = cbfile->priv;
1366 d(g_message (G_STRLOC ": Starting query (%s)", e_data_cal_view_get_text (query)));
1368 /* try to match all currently existing objects */
1369 match_data.search_needed = TRUE;
1370 match_data.query = e_data_cal_view_get_text (query);
1371 match_data.obj_list = NULL;
1372 match_data.backend = backend;
1373 match_data.default_zone = priv->default_zone;
1375 if (!strcmp (match_data.query, "#t"))
1376 match_data.search_needed = FALSE;
1378 match_data.obj_sexp = e_data_cal_view_get_object_sexp (query);
1379 if (!match_data.obj_sexp) {
1380 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_InvalidQuery);
1384 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1385 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1386 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1388 /* notify listeners of all objects */
1389 if (match_data.obj_list) {
1390 e_data_cal_view_notify_objects_added (query, (const GList *) match_data.obj_list);
1393 g_list_foreach (match_data.obj_list, (GFunc) g_free, NULL);
1394 g_list_free (match_data.obj_list);
1396 g_object_unref (match_data.obj_sexp);
1398 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
1402 free_busy_instance (ECalComponent *comp,
1403 time_t instance_start,
1404 time_t instance_end,
1407 icalcomponent *vfb = data;
1409 icalparameter *param;
1410 struct icalperiodtype ipt;
1411 icaltimezone *utc_zone;
1413 utc_zone = icaltimezone_get_utc_timezone ();
1415 ipt.start = icaltime_from_timet_with_zone (instance_start, FALSE, utc_zone);
1416 ipt.end = icaltime_from_timet_with_zone (instance_end, FALSE, utc_zone);
1417 ipt.duration = icaldurationtype_null_duration ();
1419 /* add busy information to the vfb component */
1420 prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
1421 icalproperty_set_freebusy (prop, ipt);
1423 param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
1424 icalproperty_add_parameter (prop, param);
1426 icalcomponent_add_property (vfb, prop);
1431 static icalcomponent *
1432 create_user_free_busy (ECalBackendFile *cbfile, const char *address, const char *cn,
1433 time_t start, time_t end)
1435 ECalBackendFilePrivate *priv;
1438 icaltimezone *utc_zone;
1439 ECalBackendSExp *obj_sexp;
1440 char *query, *iso_start, *iso_end;
1442 priv = cbfile->priv;
1444 /* create the (unique) VFREEBUSY object that we'll return */
1445 vfb = icalcomponent_new_vfreebusy ();
1446 if (address != NULL) {
1448 icalparameter *param;
1450 prop = icalproperty_new_organizer (address);
1451 if (prop != NULL && cn != NULL) {
1452 param = icalparameter_new_cn (cn);
1453 icalproperty_add_parameter (prop, param);
1456 icalcomponent_add_property (vfb, prop);
1458 utc_zone = icaltimezone_get_utc_timezone ();
1459 icalcomponent_set_dtstart (vfb, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
1460 icalcomponent_set_dtend (vfb, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
1462 /* add all objects in the given interval */
1463 iso_start = isodate_from_time_t (start);
1464 iso_end = isodate_from_time_t (end);
1465 query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") (make-time \"%s\")",
1466 iso_start, iso_end);
1467 obj_sexp = e_cal_backend_sexp_new (query);
1475 for (l = priv->comp; l; l = l->next) {
1476 ECalComponent *comp = l->data;
1477 icalcomponent *icalcomp, *vcalendar_comp;
1480 icalcomp = e_cal_component_get_icalcomponent (comp);
1484 /* If the event is TRANSPARENT, skip it. */
1485 prop = icalcomponent_get_first_property (icalcomp,
1486 ICAL_TRANSP_PROPERTY);
1488 icalproperty_transp transp_val = icalproperty_get_transp (prop);
1489 if (transp_val == ICAL_TRANSP_TRANSPARENT ||
1490 transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
1494 if (!e_cal_backend_sexp_match_comp (obj_sexp, l->data, E_CAL_BACKEND (cbfile)))
1497 vcalendar_comp = icalcomponent_get_parent (icalcomp);
1498 e_cal_recur_generate_instances (comp, start, end,
1503 priv->default_zone);
1505 g_object_unref (obj_sexp);
1510 /* Get_free_busy handler for the file backend */
1511 static ECalBackendSyncStatus
1512 e_cal_backend_file_get_free_busy (ECalBackendSync *backend, EDataCal *cal, GList *users,
1513 time_t start, time_t end, GList **freebusy)
1515 ECalBackendFile *cbfile;
1516 ECalBackendFilePrivate *priv;
1517 gchar *address, *name;
1522 cbfile = E_CAL_BACKEND_FILE (backend);
1523 priv = cbfile->priv;
1525 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1526 g_return_val_if_fail (start != -1 && end != -1, GNOME_Evolution_Calendar_InvalidRange);
1527 g_return_val_if_fail (start <= end, GNOME_Evolution_Calendar_InvalidRange);
1529 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1533 if (users == NULL) {
1534 if (e_cal_backend_mail_account_get_default (&address, &name)) {
1535 vfb = create_user_free_busy (cbfile, address, name, start, end);
1536 calobj = icalcomponent_as_ical_string (vfb);
1537 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1538 icalcomponent_free (vfb);
1543 for (l = users; l != NULL; l = l->next ) {
1545 if (e_cal_backend_mail_account_is_valid (address, &name)) {
1546 vfb = create_user_free_busy (cbfile, address, name, start, end);
1547 calobj = icalcomponent_as_ical_string (vfb);
1548 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1549 icalcomponent_free (vfb);
1555 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1557 return GNOME_Evolution_Calendar_Success;
1562 ECalBackendFile *backend;
1563 icalcomponent_kind kind;
1566 } ECalBackendFileComputeChangesData;
1569 e_cal_backend_file_compute_changes_foreach_key (const char *key, gpointer value, gpointer data)
1571 ECalBackendFileComputeChangesData *be_data = data;
1573 if (!lookup_component (be_data->backend, key)) {
1574 ECalComponent *comp;
1576 comp = e_cal_component_new ();
1577 if (be_data->kind == ICAL_VTODO_COMPONENT)
1578 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
1580 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
1582 e_cal_component_set_uid (comp, key);
1583 be_data->deletes = g_list_prepend (be_data->deletes, e_cal_component_get_as_string (comp));
1585 g_object_unref (comp);
1591 static ECalBackendSyncStatus
1592 e_cal_backend_file_compute_changes (ECalBackendFile *cbfile, const char *change_id,
1593 GList **adds, GList **modifies, GList **deletes)
1595 ECalBackendFilePrivate *priv;
1598 ECalBackendFileComputeChangesData be_data;
1600 gchar *unescaped_uri;
1602 priv = cbfile->priv;
1605 /* FIXME Will this always work? */
1606 unescaped_uri = gnome_vfs_unescape_string (priv->uri, "");
1607 filename = g_strdup_printf ("%s-%s.db", unescaped_uri, change_id);
1608 g_free (unescaped_uri);
1609 if (!(ehash = e_xmlhash_new (filename))) {
1611 return GNOME_Evolution_Calendar_OtherError;
1616 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1618 /* Calculate adds and modifies */
1619 for (i = priv->comp; i != NULL; i = i->next) {
1623 e_cal_component_get_uid (i->data, &uid);
1624 calobj = e_cal_component_get_as_string (i->data);
1626 g_assert (calobj != NULL);
1628 /* check what type of change has occurred, if any */
1629 switch (e_xmlhash_compare (ehash, uid, calobj)) {
1630 case E_XMLHASH_STATUS_SAME:
1632 case E_XMLHASH_STATUS_NOT_FOUND:
1633 *adds = g_list_prepend (*adds, g_strdup (calobj));
1634 e_xmlhash_add (ehash, uid, calobj);
1636 case E_XMLHASH_STATUS_DIFFERENT:
1637 *modifies = g_list_prepend (*modifies, g_strdup (calobj));
1638 e_xmlhash_add (ehash, uid, calobj);
1645 /* Calculate deletions */
1646 be_data.backend = cbfile;
1647 be_data.kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbfile));
1648 be_data.deletes = NULL;
1649 be_data.ehash = ehash;
1651 e_xmlhash_foreach_key_remove (ehash, (EXmlHashRemoveFunc)e_cal_backend_file_compute_changes_foreach_key, &be_data);
1653 *deletes = be_data.deletes;
1655 e_xmlhash_write (ehash);
1656 e_xmlhash_destroy (ehash);
1658 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1659 return GNOME_Evolution_Calendar_Success;
1662 /* Get_changes handler for the file backend */
1663 static ECalBackendSyncStatus
1664 e_cal_backend_file_get_changes (ECalBackendSync *backend, EDataCal *cal, const char *change_id,
1665 GList **adds, GList **modifies, GList **deletes)
1667 ECalBackendFile *cbfile;
1668 ECalBackendFilePrivate *priv;
1670 cbfile = E_CAL_BACKEND_FILE (backend);
1671 priv = cbfile->priv;
1673 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1674 g_return_val_if_fail (change_id != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1676 return e_cal_backend_file_compute_changes (cbfile, change_id, adds, modifies, deletes);
1679 /* Discard_alarm handler for the file backend */
1680 static ECalBackendSyncStatus
1681 e_cal_backend_file_discard_alarm (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *auid)
1683 /* we just do nothing with the alarm */
1684 return GNOME_Evolution_Calendar_Success;
1687 static icaltimezone *
1688 e_cal_backend_file_internal_get_default_timezone (ECalBackend *backend)
1690 ECalBackendFile *cbfile;
1691 ECalBackendFilePrivate *priv;
1693 cbfile = E_CAL_BACKEND_FILE (backend);
1694 priv = cbfile->priv;
1696 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1698 return priv->default_zone;
1701 static icaltimezone *
1702 e_cal_backend_file_internal_get_timezone (ECalBackend *backend, const char *tzid)
1704 ECalBackendFile *cbfile;
1705 ECalBackendFilePrivate *priv;
1708 cbfile = E_CAL_BACKEND_FILE (backend);
1709 priv = cbfile->priv;
1711 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1713 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1715 if (!strcmp (tzid, "UTC"))
1716 zone = icaltimezone_get_utc_timezone ();
1718 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1720 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1723 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1728 sanitize_component (ECalBackendFile *cbfile, ECalComponent *comp)
1730 ECalComponentDateTime dt;
1731 icaltimezone *zone, *default_zone;
1733 /* Check dtstart, dtend and due's timezone, and convert it to local
1734 * default timezone if the timezone is not in our builtin timezone
1736 e_cal_component_get_dtstart (comp, &dt);
1737 if (dt.value && dt.tzid) {
1738 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1740 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1741 g_free ((char *)dt.tzid);
1742 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1743 e_cal_component_set_dtstart (comp, &dt);
1746 e_cal_component_free_datetime (&dt);
1748 e_cal_component_get_dtend (comp, &dt);
1749 if (dt.value && dt.tzid) {
1750 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1752 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1753 g_free ((char *)dt.tzid);
1754 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1755 e_cal_component_set_dtend (comp, &dt);
1758 e_cal_component_free_datetime (&dt);
1760 e_cal_component_get_due (comp, &dt);
1761 if (dt.value && dt.tzid) {
1762 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *)cbfile, dt.tzid);
1764 default_zone = e_cal_backend_file_internal_get_default_timezone ((ECalBackend *)cbfile);
1765 g_free ((char *)dt.tzid);
1766 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
1767 e_cal_component_set_due (comp, &dt);
1770 e_cal_component_free_datetime (&dt);
1771 e_cal_component_abort_sequence (comp);
1775 static ECalBackendSyncStatus
1776 e_cal_backend_file_create_object (ECalBackendSync *backend, EDataCal *cal, char **calobj, char **uid)
1778 ECalBackendFile *cbfile;
1779 ECalBackendFilePrivate *priv;
1780 icalcomponent *icalcomp;
1781 ECalComponent *comp;
1782 const char *comp_uid;
1783 struct icaltimetype current;
1785 cbfile = E_CAL_BACKEND_FILE (backend);
1786 priv = cbfile->priv;
1788 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1789 g_return_val_if_fail (*calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1791 /* Parse the icalendar text */
1792 icalcomp = icalparser_parse_string (*calobj);
1794 return GNOME_Evolution_Calendar_InvalidObject;
1796 /* Check kind with the parent */
1797 if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
1798 icalcomponent_free (icalcomp);
1799 return GNOME_Evolution_Calendar_InvalidObject;
1802 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1805 comp_uid = icalcomponent_get_uid (icalcomp);
1809 new_uid = e_cal_component_gen_uid ();
1811 icalcomponent_free (icalcomp);
1812 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1813 return GNOME_Evolution_Calendar_InvalidObject;
1816 icalcomponent_set_uid (icalcomp, new_uid);
1817 comp_uid = icalcomponent_get_uid (icalcomp);
1822 /* check the object is not in our cache */
1823 if (lookup_component (cbfile, comp_uid)) {
1824 icalcomponent_free (icalcomp);
1825 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1826 return GNOME_Evolution_Calendar_ObjectIdAlreadyExists;
1829 /* Create the cal component */
1830 comp = e_cal_component_new ();
1831 e_cal_component_set_icalcomponent (comp, icalcomp);
1833 /* Set the created and last modified times on the component */
1834 current = icaltime_from_timet (time (NULL), 0);
1835 e_cal_component_set_created (comp, ¤t);
1836 e_cal_component_set_last_modified (comp, ¤t);
1838 /* sanitize the component*/
1839 sanitize_component (cbfile, comp);
1841 /* Add the object */
1842 add_component (cbfile, comp, TRUE);
1847 /* Return the UID and the modified component */
1849 *uid = g_strdup (comp_uid);
1850 *calobj = e_cal_component_get_as_string (comp);
1852 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1853 return GNOME_Evolution_Calendar_Success;
1857 ECalBackendFile *cbfile;
1858 ECalBackendFileObject *obj_data;
1861 } RemoveRecurrenceData;
1864 remove_object_instance_cb (gpointer key, gpointer value, gpointer user_data)
1866 time_t fromtt, instancett;
1867 ECalComponent *instance = value;
1868 RemoveRecurrenceData *rrdata = user_data;
1870 fromtt = icaltime_as_timet (icaltime_from_string (rrdata->rid));
1871 instancett = icaltime_as_timet (get_rid_icaltime (instance));
1873 if (fromtt > 0 && instancett > 0) {
1874 if ((rrdata->mod == CALOBJ_MOD_THISANDPRIOR && instancett <= fromtt) ||
1875 (rrdata->mod == CALOBJ_MOD_THISANDFUTURE && instancett >= fromtt)) {
1876 /* remove the component from our data */
1877 icalcomponent_remove_component (rrdata->cbfile->priv->icalcomp,
1878 e_cal_component_get_icalcomponent (instance));
1879 rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
1881 rrdata->obj_data->recurrences_list = g_list_remove (rrdata->obj_data->recurrences_list, instance);
1890 static ECalBackendSyncStatus
1891 e_cal_backend_file_modify_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj,
1892 CalObjModType mod, char **old_object, char **new_object)
1894 RemoveRecurrenceData rrdata;
1895 ECalBackendFile *cbfile;
1896 ECalBackendFilePrivate *priv;
1897 icalcomponent *icalcomp;
1898 const char *comp_uid, *rid = NULL;
1900 ECalComponent *comp, *recurrence;
1901 ECalBackendFileObject *obj_data;
1902 struct icaltimetype current;
1904 cbfile = E_CAL_BACKEND_FILE (backend);
1905 priv = cbfile->priv;
1907 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1908 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1910 /* Parse the icalendar text */
1911 icalcomp = icalparser_parse_string ((char *) calobj);
1913 return GNOME_Evolution_Calendar_InvalidObject;
1915 /* Check kind with the parent */
1916 if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
1917 icalcomponent_free (icalcomp);
1918 return GNOME_Evolution_Calendar_InvalidObject;
1921 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1924 comp_uid = icalcomponent_get_uid (icalcomp);
1926 /* Get the object from our cache */
1927 if (!(obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid))) {
1928 icalcomponent_free (icalcomp);
1929 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1930 return GNOME_Evolution_Calendar_ObjectNotFound;
1933 /* Create the cal component */
1934 comp = e_cal_component_new ();
1935 e_cal_component_set_icalcomponent (comp, icalcomp);
1937 /* Set the last modified time on the component */
1938 current = icaltime_from_timet (time (NULL), 0);
1939 e_cal_component_set_last_modified (comp, ¤t);
1941 /* sanitize the component*/
1942 sanitize_component (cbfile, comp);
1943 rid = e_cal_component_get_recurid_as_string (comp);
1945 /* handle mod_type */
1947 case CALOBJ_MOD_THIS :
1948 if (!rid || !*rid) {
1950 *old_object = e_cal_component_get_as_string (obj_data->full_object);
1952 /* replace only the full object */
1953 icalcomponent_remove_component (priv->icalcomp,
1954 e_cal_component_get_icalcomponent (obj_data->full_object));
1955 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
1957 /* add the new object */
1958 g_object_unref (obj_data->full_object);
1959 obj_data->full_object = comp;
1961 icalcomponent_add_component (priv->icalcomp,
1962 e_cal_component_get_icalcomponent (obj_data->full_object));
1963 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
1967 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1968 return GNOME_Evolution_Calendar_Success;
1971 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (void **)&real_rid, (void **)&recurrence)) {
1973 *old_object = e_cal_component_get_as_string (recurrence);
1975 /* remove the component from our data */
1976 icalcomponent_remove_component (priv->icalcomp,
1977 e_cal_component_get_icalcomponent (recurrence));
1978 priv->comp = g_list_remove (priv->comp, recurrence);
1979 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
1980 g_hash_table_remove (obj_data->recurrences, rid);
1983 /* add the detached instance */
1984 g_hash_table_insert (obj_data->recurrences,
1987 icalcomponent_add_component (priv->icalcomp,
1988 e_cal_component_get_icalcomponent (comp));
1989 priv->comp = g_list_append (priv->comp, comp);
1990 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
1992 case CALOBJ_MOD_THISANDPRIOR :
1993 case CALOBJ_MOD_THISANDFUTURE :
1994 rid = e_cal_component_get_recurid_as_string (comp);
1995 if (!rid || !*rid) {
1997 *old_object = e_cal_component_get_as_string (obj_data->full_object);
1999 remove_component (cbfile, comp_uid, obj_data);
2001 /* Add the new object */
2002 add_component (cbfile, comp, TRUE);
2006 /* remove the component from our data, temporarily */
2007 icalcomponent_remove_component (priv->icalcomp,
2008 e_cal_component_get_icalcomponent (obj_data->full_object));
2009 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2011 /* now deal with the detached recurrence */
2012 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
2013 (void **)&real_rid, (void **)&recurrence)) {
2015 *old_object = e_cal_component_get_as_string (recurrence);
2017 /* remove the component from our data */
2018 icalcomponent_remove_component (priv->icalcomp,
2019 e_cal_component_get_icalcomponent (recurrence));
2020 priv->comp = g_list_remove (priv->comp, recurrence);
2021 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2022 g_hash_table_remove (obj_data->recurrences, rid);
2025 *old_object = e_cal_component_get_as_string (obj_data->full_object);
2028 rrdata.cbfile = cbfile;
2029 rrdata.obj_data = obj_data;
2032 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2034 /* add the modified object to the beginning of the list,
2035 so that it's always before any detached instance we
2037 icalcomponent_add_component (priv->icalcomp,
2038 e_cal_component_get_icalcomponent (obj_data->full_object));
2039 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2041 /* add the new detached recurrence */
2042 g_hash_table_insert (obj_data->recurrences,
2043 g_strdup (e_cal_component_get_recurid_as_string (comp)),
2045 icalcomponent_add_component (priv->icalcomp,
2046 e_cal_component_get_icalcomponent (comp));
2047 priv->comp = g_list_append (priv->comp, comp);
2048 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
2050 case CALOBJ_MOD_ALL :
2051 /* in this case, we blow away all recurrences, and start over
2052 with a clean component */
2054 if (e_cal_util_component_has_recurrences (icalcomp) && rid && *rid) {
2055 icaltimetype start, recur = icaltime_from_string (rid);
2057 start = icalcomponent_get_dtstart (icalcomp);
2059 /* This means its a instance generated from master object. So replace
2060 the dates stored dates from the master object */
2062 recur.zone = start.zone;
2064 if (icaltime_compare_date_only (start, recur) == 0) {
2065 ECalComponentDateTime m_sdate, m_endate;
2067 e_cal_component_get_dtstart (obj_data->full_object, &m_sdate);
2068 e_cal_component_get_dtend (obj_data->full_object, &m_endate);
2070 if (icaltime_compare (start, recur) != 0) {
2071 icaltimetype end = icalcomponent_get_dtend (icalcomp);
2073 m_sdate.value->hour = start.hour;
2074 m_sdate.value->minute = start.minute;
2075 m_sdate.value->second =
2077 m_endate.value->hour = end.hour;
2078 m_endate.value->minute = end.minute;
2079 m_endate.value->second = end.second;
2082 e_cal_component_set_dtstart (comp, &m_sdate);
2083 e_cal_component_set_dtend (comp, &m_endate);
2084 e_cal_component_set_recurid (comp, NULL);
2085 e_cal_component_commit_sequence (comp);
2087 e_cal_component_set_recurid (comp, NULL);
2088 *new_object = e_cal_component_get_as_string (comp);
2091 /* Remove the old version */
2093 *old_object = e_cal_component_get_as_string (obj_data->full_object);
2095 remove_component (cbfile, comp_uid, obj_data);
2097 /* Add the new object */
2098 add_component (cbfile, comp, TRUE);
2104 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2105 return GNOME_Evolution_Calendar_Success;
2109 remove_instance (ECalBackendFile *cbfile, ECalBackendFileObject *obj_data, const char *rid)
2112 ECalComponent *comp;
2117 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (void **)&hash_rid, (void **)&comp)) {
2118 /* remove the component from our data */
2119 icalcomponent_remove_component (cbfile->priv->icalcomp,
2120 e_cal_component_get_icalcomponent (comp));
2121 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
2122 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp);
2123 g_hash_table_remove (obj_data->recurrences, rid);
2126 /* remove the component from our data, temporarily */
2127 icalcomponent_remove_component (cbfile->priv->icalcomp,
2128 e_cal_component_get_icalcomponent (obj_data->full_object));
2129 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
2131 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
2132 icaltime_from_string (rid), CALOBJ_MOD_THIS);
2134 /* add the modified object to the beginning of the list,
2135 so that it's always before any detached instance we
2137 icalcomponent_add_component (cbfile->priv->icalcomp,
2138 e_cal_component_get_icalcomponent (obj_data->full_object));
2139 cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
2143 get_object_string_from_fileobject (ECalBackendFileObject *obj_data, const char *rid)
2145 ECalComponent *comp = obj_data->full_object;
2149 return e_cal_component_get_as_string (comp);
2151 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (void **)&real_rid, (void **)&comp))
2152 return e_cal_component_get_as_string (comp);
2154 /* FIXME remove this once we delete an instance from master object through
2155 modify request by setting exception */
2156 return e_cal_component_get_as_string (comp);
2163 /* Remove_object handler for the file backend */
2164 static ECalBackendSyncStatus
2165 e_cal_backend_file_remove_object (ECalBackendSync *backend, EDataCal *cal,
2166 const char *uid, const char *rid,
2167 CalObjModType mod, char **old_object,
2170 ECalBackendFile *cbfile;
2171 ECalBackendFilePrivate *priv;
2172 ECalBackendFileObject *obj_data;
2173 ECalComponent *comp;
2174 RemoveRecurrenceData rrdata;
2175 const char *recur_id = NULL;
2177 cbfile = E_CAL_BACKEND_FILE (backend);
2178 priv = cbfile->priv;
2180 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
2181 g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
2183 *old_object = *object = NULL;
2185 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2187 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2189 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2190 return GNOME_Evolution_Calendar_ObjectNotFound;
2196 comp = obj_data->full_object;
2199 case CALOBJ_MOD_ALL :
2200 *old_object = get_object_string_from_fileobject (obj_data, recur_id);
2201 remove_component (cbfile, uid, obj_data);
2205 case CALOBJ_MOD_THIS :
2207 *old_object = get_object_string_from_fileobject (obj_data, recur_id);
2208 remove_component (cbfile, uid, obj_data);
2211 *old_object = get_object_string_from_fileobject (obj_data, recur_id);
2213 remove_instance (cbfile, obj_data, recur_id);
2215 *object = e_cal_component_get_as_string (comp);
2218 case CALOBJ_MOD_THISANDPRIOR :
2219 case CALOBJ_MOD_THISANDFUTURE :
2220 if (!recur_id || !*recur_id) {
2221 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2222 return GNOME_Evolution_Calendar_ObjectNotFound;
2225 *old_object = e_cal_component_get_as_string (comp);
2227 /* remove the component from our data, temporarily */
2228 icalcomponent_remove_component (priv->icalcomp,
2229 e_cal_component_get_icalcomponent (comp));
2230 priv->comp = g_list_remove (priv->comp, comp);
2232 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (comp),
2233 icaltime_from_string (recur_id), mod);
2235 /* now remove all detached instances */
2236 rrdata.cbfile = cbfile;
2237 rrdata.obj_data = obj_data;
2238 rrdata.rid = recur_id;
2240 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2242 /* add the modified object to the beginning of the list,
2243 so that it's always before any detached instance we
2245 priv->comp = g_list_prepend (priv->comp, comp);
2247 *object = e_cal_component_get_as_string (obj_data->full_object);
2253 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2254 return GNOME_Evolution_Calendar_Success;
2258 cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp)
2260 ECalBackendFileObject *obj_data;
2261 ECalBackendFilePrivate *priv;
2263 ECalComponent *comp;
2265 priv = cbfile->priv;
2267 /* Find the old version of the component. */
2268 obj_data = g_hash_table_lookup (priv->comp_uid_hash, icalcomponent_get_uid (icalcomp));
2273 comp = e_cal_component_new ();
2274 if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
2275 g_object_unref (comp);
2279 rid = e_cal_component_get_recurid_as_string (comp);
2281 remove_instance (cbfile, obj_data, rid);
2283 remove_component (cbfile, icalcomponent_get_uid (icalcomp), obj_data);
2292 } ECalBackendFileTzidData;
2295 check_tzids (icalparameter *param, void *data)
2297 ECalBackendFileTzidData *tzdata = data;
2300 tzid = icalparameter_get_tzid (param);
2301 if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
2302 tzdata->found = FALSE;
2306 /* This function is largely duplicated in
2307 * ../groupwise/e-cal-backend-groupwise.c
2310 fetch_attachments (ECalBackendSync *backend, ECalComponent *comp)
2312 GSList *attach_list = NULL, *new_attach_list = NULL;
2315 char *dest_url, *dest_file;
2319 e_cal_component_get_attachment_list (comp, &attach_list);
2320 e_cal_component_get_uid (comp, &uid);
2321 /*FIXME get the uri rather than computing the path */
2322 attach_store = g_build_filename (g_get_home_dir (),
2323 ".evolution/calendar/local/system", NULL);
2325 for (l = attach_list; l ; l = l->next) {
2326 char *sfname = (char *)l->data;
2327 char *filename, *new_filename;
2328 GMappedFile *mapped_file;
2329 GError *error = NULL;
2331 mapped_file = g_mapped_file_new (sfname, FALSE, &error);
2333 g_message ("DEBUG: could not map %s: %s\n",
2334 sfname, error->message);
2335 g_error_free (error);
2338 filename = g_path_get_basename (sfname);
2339 new_filename = g_strconcat (uid, "-", filename, NULL);
2341 dest_file = g_build_filename (attach_store, new_filename, NULL);
2342 g_free (new_filename);
2343 fd = g_open (dest_file, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0600);
2345 /* TODO handle error conditions */
2346 g_message ("DEBUG: could not open %s for writing\n",
2348 } else if (write (fd, g_mapped_file_get_contents (mapped_file),
2349 g_mapped_file_get_length (mapped_file)) == -1) {
2350 /* TODO handle error condition */
2351 g_message ("DEBUG: attachment write failed.\n");
2354 g_mapped_file_free (mapped_file);
2357 dest_url = g_filename_to_uri (dest_file, NULL, NULL);
2359 new_attach_list = g_slist_append (new_attach_list, dest_url);
2361 g_free (attach_store);
2362 e_cal_component_set_attachment_list (comp, new_attach_list);
2365 /* Update_objects handler for the file backend. */
2366 static ECalBackendSyncStatus
2367 e_cal_backend_file_receive_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj)
2369 ECalBackendFile *cbfile;
2370 ECalBackendFilePrivate *priv;
2371 icalcomponent *toplevel_comp, *icalcomp = NULL;
2372 icalcomponent_kind kind;
2373 icalproperty_method toplevel_method, method;
2374 icalcomponent *subcomp;
2375 GList *comps, *del_comps, *l;
2376 ECalComponent *comp;
2377 struct icaltimetype current;
2378 ECalBackendFileTzidData tzdata;
2379 ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;
2381 cbfile = E_CAL_BACKEND_FILE (backend);
2382 priv = cbfile->priv;
2384 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
2385 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_InvalidObject);
2387 /* Pull the component from the string and ensure that it is sane */
2388 toplevel_comp = icalparser_parse_string ((char *) calobj);
2390 return GNOME_Evolution_Calendar_InvalidObject;
2392 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2394 kind = icalcomponent_isa (toplevel_comp);
2395 if (kind != ICAL_VCALENDAR_COMPONENT) {
2396 /* If its not a VCALENDAR, make it one to simplify below */
2397 icalcomp = toplevel_comp;
2398 toplevel_comp = e_cal_util_new_top_level ();
2399 if (icalcomponent_get_method (icalcomp) == ICAL_METHOD_CANCEL)
2400 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_CANCEL);
2402 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_PUBLISH);
2403 icalcomponent_add_component (toplevel_comp, icalcomp);
2405 if (!icalcomponent_get_first_property (toplevel_comp, ICAL_METHOD_PROPERTY))
2406 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_PUBLISH);
2409 toplevel_method = icalcomponent_get_method (toplevel_comp);
2411 /* Build a list of timezones so we can make sure all the objects have valid info */
2412 tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2414 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
2418 zone = icaltimezone_new ();
2419 if (icaltimezone_set_component (zone, subcomp))
2420 g_hash_table_insert (tzdata.zones, g_strdup (icaltimezone_get_tzid (zone)), NULL);
2422 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
2425 /* First we make sure all the components are usuable */
2426 comps = del_comps = NULL;
2427 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
2429 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
2431 icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
2433 if (child_kind != kind) {
2434 /* remove the component from the toplevel VCALENDAR */
2435 if (child_kind != ICAL_VTIMEZONE_COMPONENT)
2436 del_comps = g_list_prepend (del_comps, subcomp);
2438 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
2442 tzdata.found = TRUE;
2443 icalcomponent_foreach_tzid (subcomp, check_tzids, &tzdata);
2445 if (!tzdata.found) {
2446 status = GNOME_Evolution_Calendar_InvalidObject;
2450 if (!icalcomponent_get_uid (subcomp)) {
2451 if (toplevel_method == ICAL_METHOD_PUBLISH) {
2453 char *new_uid = NULL;
2455 new_uid = e_cal_component_gen_uid ();
2456 icalcomponent_set_uid (subcomp, new_uid);
2459 status = GNOME_Evolution_Calendar_InvalidObject;
2465 comps = g_list_prepend (comps, subcomp);
2466 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
2469 /* Now we manipulate the components we care about */
2470 for (l = comps; l; l = l->next) {
2471 const char *uid, *rid;
2472 char *object, *old_object;
2473 ECalBackendFileObject *obj_data;
2477 /* Create the cal component */
2478 comp = e_cal_component_new ();
2479 e_cal_component_set_icalcomponent (comp, subcomp);
2481 /* Set the created and last modified times on the component */
2482 current = icaltime_from_timet (time (NULL), 0);
2483 e_cal_component_set_created (comp, ¤t);
2484 e_cal_component_set_last_modified (comp, ¤t);
2486 e_cal_component_get_uid (comp, &uid);
2487 rid = e_cal_component_get_recurid_as_string (comp);
2489 if (icalcomponent_get_first_property (subcomp, ICAL_METHOD_PROPERTY))
2490 method = icalcomponent_get_method (subcomp);
2492 method = toplevel_method;
2495 case ICAL_METHOD_PUBLISH:
2496 case ICAL_METHOD_REQUEST:
2497 case ICAL_METHOD_REPLY:
2498 /* handle attachments */
2499 if (e_cal_component_has_attachments (comp))
2500 fetch_attachments (backend, comp);
2501 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2503 old_object = e_cal_component_get_as_string (obj_data->full_object);
2505 remove_instance (cbfile, obj_data, rid);
2507 remove_component (cbfile, uid, obj_data);
2508 add_component (cbfile, comp, FALSE);
2510 object = e_cal_component_get_as_string (comp);
2511 e_cal_backend_notify_object_modified (E_CAL_BACKEND (backend), old_object, object);
2513 g_free (old_object);
2515 add_component (cbfile, comp, FALSE);
2517 object = e_cal_component_get_as_string (comp);
2518 e_cal_backend_notify_object_created (E_CAL_BACKEND (backend), object);
2522 case ICAL_METHOD_ADD:
2523 /* FIXME This should be doable once all the recurid stuff is done */
2524 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2527 case ICAL_METHOD_COUNTER:
2528 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2531 case ICAL_METHOD_DECLINECOUNTER:
2532 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2535 case ICAL_METHOD_CANCEL:
2536 if (cancel_received_object (cbfile, subcomp)) {
2537 ECalComponentId *id;
2538 object = (char *) icalcomponent_as_ical_string (subcomp);
2539 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2541 old_object = e_cal_component_get_as_string (obj_data->full_object);
2545 id = e_cal_component_get_id (comp);
2547 e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend), id, old_object, object);
2549 /* remove the component from the toplevel VCALENDAR */
2550 icalcomponent_remove_component (toplevel_comp, subcomp);
2551 icalcomponent_free (subcomp);
2552 e_cal_component_free_id (id);
2554 g_free (old_object);
2558 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2563 g_list_free (comps);
2565 /* Now we remove the components we don't care about */
2566 for (l = del_comps; l; l = l->next) {
2569 icalcomponent_remove_component (toplevel_comp, subcomp);
2570 icalcomponent_free (subcomp);
2573 g_list_free (del_comps);
2575 /* Merge the iCalendar components with our existing VCALENDAR,
2576 resolving any conflicting TZIDs. */
2577 icalcomponent_merge_component (priv->icalcomp, toplevel_comp);
2582 g_hash_table_destroy (tzdata.zones);
2583 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2587 static ECalBackendSyncStatus
2588 e_cal_backend_file_send_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj, GList **users,
2589 char **modified_calobj)
2592 *modified_calobj = g_strdup (calobj);
2594 return GNOME_Evolution_Calendar_Success;
2597 /* Object initialization function for the file backend */
2599 e_cal_backend_file_init (ECalBackendFile *cbfile)
2601 ECalBackendFilePrivate *priv;
2603 priv = g_new0 (ECalBackendFilePrivate, 1);
2604 cbfile->priv = priv;
2607 priv->file_name = g_strdup ("calendar.ics");
2608 priv->read_only = FALSE;
2609 priv->is_dirty = FALSE;
2610 priv->dirty_idle_id = 0;
2611 g_static_rec_mutex_init (&priv->idle_save_rmutex);
2612 priv->icalcomp = NULL;
2613 priv->comp_uid_hash = NULL;
2616 /* The timezone defaults to UTC. */
2617 priv->default_zone = icaltimezone_get_utc_timezone ();
2620 * data access is serialized via idle_save_rmutex, so locking at the
2621 * backend method level is not needed
2623 e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbfile), FALSE);
2626 /* Class initialization function for the file backend */
2628 e_cal_backend_file_class_init (ECalBackendFileClass *class)
2630 GObjectClass *object_class;
2631 ECalBackendClass *backend_class;
2632 ECalBackendSyncClass *sync_class;
2634 object_class = (GObjectClass *) class;
2635 backend_class = (ECalBackendClass *) class;
2636 sync_class = (ECalBackendSyncClass *) class;
2638 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
2640 object_class->dispose = e_cal_backend_file_dispose;
2641 object_class->finalize = e_cal_backend_file_finalize;
2643 sync_class->is_read_only_sync = e_cal_backend_file_is_read_only;
2644 sync_class->get_cal_address_sync = e_cal_backend_file_get_cal_address;
2645 sync_class->get_alarm_email_address_sync = e_cal_backend_file_get_alarm_email_address;
2646 sync_class->get_ldap_attribute_sync = e_cal_backend_file_get_ldap_attribute;
2647 sync_class->get_static_capabilities_sync = e_cal_backend_file_get_static_capabilities;
2648 sync_class->open_sync = e_cal_backend_file_open;
2649 sync_class->remove_sync = e_cal_backend_file_remove;
2650 sync_class->create_object_sync = e_cal_backend_file_create_object;
2651 sync_class->modify_object_sync = e_cal_backend_file_modify_object;
2652 sync_class->remove_object_sync = e_cal_backend_file_remove_object;
2653 sync_class->discard_alarm_sync = e_cal_backend_file_discard_alarm;
2654 sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
2655 sync_class->send_objects_sync = e_cal_backend_file_send_objects;
2656 sync_class->get_default_object_sync = e_cal_backend_file_get_default_object;
2657 sync_class->get_object_sync = e_cal_backend_file_get_object;
2658 sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
2659 sync_class->get_attachment_list_sync = e_cal_backend_file_get_attachment_list;
2660 sync_class->get_timezone_sync = e_cal_backend_file_get_timezone;
2661 sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
2662 sync_class->set_default_zone_sync = e_cal_backend_file_set_default_zone;
2663 sync_class->get_freebusy_sync = e_cal_backend_file_get_free_busy;
2664 sync_class->get_changes_sync = e_cal_backend_file_get_changes;
2666 backend_class->is_loaded = e_cal_backend_file_is_loaded;
2667 backend_class->start_query = e_cal_backend_file_start_query;
2668 backend_class->get_mode = e_cal_backend_file_get_mode;
2669 backend_class->set_mode = e_cal_backend_file_set_mode;
2671 backend_class->internal_get_default_timezone = e_cal_backend_file_internal_get_default_timezone;
2672 backend_class->internal_get_timezone = e_cal_backend_file_internal_get_timezone;
2677 * e_cal_backend_file_get_type:
2680 * Registers the #ECalBackendFile class if necessary, and returns the type ID
2683 * Return value: The type ID of the #ECalBackendFile class.
2686 e_cal_backend_file_get_type (void)
2688 static GType e_cal_backend_file_type = 0;
2690 if (!e_cal_backend_file_type) {
2691 static GTypeInfo info = {
2692 sizeof (ECalBackendFileClass),
2693 (GBaseInitFunc) NULL,
2694 (GBaseFinalizeFunc) NULL,
2695 (GClassInitFunc) e_cal_backend_file_class_init,
2697 sizeof (ECalBackendFile),
2699 (GInstanceInitFunc) e_cal_backend_file_init
2701 e_cal_backend_file_type = g_type_register_static (E_TYPE_CAL_BACKEND_SYNC,
2702 "ECalBackendFile", &info, 0);
2705 return e_cal_backend_file_type;
2709 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile, const char *file_name)
2711 ECalBackendFilePrivate *priv;
2713 g_return_if_fail (cbfile != NULL);
2714 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
2715 g_return_if_fail (file_name != NULL);
2717 priv = cbfile->priv;
2718 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2720 if (priv->file_name)
2721 g_free (priv->file_name);
2723 priv->file_name = g_strdup (file_name);
2725 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2729 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
2731 ECalBackendFilePrivate *priv;
2733 g_return_val_if_fail (cbfile != NULL, NULL);
2734 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
2736 priv = cbfile->priv;
2738 return priv->file_name;
2741 ECalBackendSyncStatus
2742 e_cal_backend_file_reload (ECalBackendFile *cbfile)
2744 ECalBackendFilePrivate *priv;
2746 ECalBackendSyncStatus status;
2748 priv = cbfile->priv;
2749 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2751 str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
2753 status = GNOME_Evolution_Calendar_OtherError;
2757 if (g_access (str_uri, R_OK) == 0) {
2758 status = reload_cal (cbfile, str_uri);
2759 if (g_access (str_uri, W_OK) != 0)
2760 priv->read_only = TRUE;
2762 status = GNOME_Evolution_Calendar_NoSuchCal;
2768 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);