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-util.h>
35 #include "e-cal-backend-file-events.h"
36 #include "e-cal-backend-util.h"
37 #include "e-cal-backend-sexp.h"
41 /* Placeholder for each component and its recurrences */
43 ECalComponent *full_object;
44 GHashTable *recurrences;
45 } ECalBackendFileObject;
47 /* Private part of the ECalBackendFile structure */
48 struct _ECalBackendFilePrivate {
49 /* URI where the calendar data is stored */
52 /* Filename in the dir */
56 /* Toplevel VCALENDAR component */
57 icalcomponent *icalcomp;
59 /* All the objects in the calendar, hashed by UID. The
60 * hash key *is* the uid returned by cal_component_get_uid(); it is not
61 * copied, so don't free it when you remove an object from the hash
62 * table. Each item in the hash table is a ECalBackendFileObject.
64 GHashTable *comp_uid_hash;
68 /* The calendar's default timezone, used for resolving DATE and
69 floating DATE-TIME values. */
70 icaltimezone *default_zone;
72 /* The list of live queries */
78 static void e_cal_backend_file_dispose (GObject *object);
79 static void e_cal_backend_file_finalize (GObject *object);
81 static ECalBackendSyncClass *parent_class;
85 /* g_hash_table_foreach() callback to destroy recurrences in the hash table */
87 free_recurrence (gpointer key, gpointer value, gpointer data)
90 ECalComponent *comp = value;
93 g_object_unref (comp);
96 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
98 free_object (gpointer key, gpointer value, gpointer data)
100 ECalBackendFileObject *obj_data = value;
102 g_object_unref (obj_data->full_object);
103 g_hash_table_foreach (obj_data->recurrences, (GHFunc) free_recurrence, NULL);
104 g_hash_table_destroy (obj_data->recurrences);
107 /* Saves the calendar data */
109 save (ECalBackendFile *cbfile)
111 ECalBackendFilePrivate *priv;
112 GnomeVFSURI *uri, *backup_uri;
113 GnomeVFSHandle *handle = NULL;
114 GnomeVFSResult result = GNOME_VFS_ERROR_BAD_FILE;
115 GnomeVFSFileSize out;
116 gchar *tmp, *backup_uristr;
120 g_assert (priv->uri != NULL);
121 g_assert (priv->icalcomp != NULL);
123 uri = gnome_vfs_uri_new (priv->uri);
125 goto error_malformed_uri;
127 /* save calendar to backup file */
128 tmp = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
130 gnome_vfs_uri_unref (uri);
131 goto error_malformed_uri;
134 backup_uristr = g_strconcat (tmp, "~", NULL);
135 backup_uri = gnome_vfs_uri_new (backup_uristr);
138 g_free (backup_uristr);
141 gnome_vfs_uri_unref (uri);
142 goto error_malformed_uri;
145 result = gnome_vfs_create_uri (&handle, backup_uri,
146 GNOME_VFS_OPEN_WRITE,
148 if (result != GNOME_VFS_OK) {
149 gnome_vfs_uri_unref (uri);
150 gnome_vfs_uri_unref (backup_uri);
154 buf = icalcomponent_as_ical_string (priv->icalcomp);
155 result = gnome_vfs_write (handle, buf, strlen (buf) * sizeof (char), &out);
156 gnome_vfs_close (handle);
157 if (result != GNOME_VFS_OK) {
158 gnome_vfs_uri_unref (uri);
159 gnome_vfs_uri_unref (backup_uri);
163 /* now copy the temporary file to the real file */
164 result = gnome_vfs_move_uri (backup_uri, uri, TRUE);
166 gnome_vfs_uri_unref (uri);
167 gnome_vfs_uri_unref (backup_uri);
168 if (result != GNOME_VFS_OK)
174 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
175 _("Can't save calendar data: Malformed URI."));
179 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), gnome_vfs_result_to_string (result));
183 /* Dispose handler for the file backend */
185 e_cal_backend_file_dispose (GObject *object)
187 ECalBackendFile *cbfile;
188 ECalBackendFilePrivate *priv;
190 cbfile = E_CAL_BACKEND_FILE (object);
193 /* Save if necessary */
195 if (priv->comp_uid_hash) {
196 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) free_object, NULL);
197 g_hash_table_destroy (priv->comp_uid_hash);
198 priv->comp_uid_hash = NULL;
201 g_list_free (priv->comp);
204 if (priv->icalcomp) {
205 icalcomponent_free (priv->icalcomp);
206 priv->icalcomp = NULL;
209 if (G_OBJECT_CLASS (parent_class)->dispose)
210 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
213 /* Finalize handler for the file backend */
215 e_cal_backend_file_finalize (GObject *object)
217 ECalBackendFile *cbfile;
218 ECalBackendFilePrivate *priv;
220 g_return_if_fail (object != NULL);
221 g_return_if_fail (E_IS_CAL_BACKEND_FILE (object));
223 cbfile = E_CAL_BACKEND_FILE (object);
236 if (G_OBJECT_CLASS (parent_class)->finalize)
237 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
242 /* Looks up a component by its UID on the backend's component hash table */
243 static ECalComponent *
244 lookup_component (ECalBackendFile *cbfile, const char *uid)
246 ECalBackendFilePrivate *priv;
247 ECalBackendFileObject *obj_data;
251 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
252 return obj_data ? obj_data->full_object : NULL;
257 /* Calendar backend methods */
259 /* Is_read_only handler for the file backend */
260 static ECalBackendSyncStatus
261 e_cal_backend_file_is_read_only (ECalBackendSync *backend, EDataCal *cal, gboolean *read_only)
263 ECalBackendFile *cbfile = (ECalBackendFile *) backend;
265 *read_only = cbfile->priv->read_only;
267 return GNOME_Evolution_Calendar_Success;
270 /* Get_email_address handler for the file backend */
271 static ECalBackendSyncStatus
272 e_cal_backend_file_get_cal_address (ECalBackendSync *backend, EDataCal *cal, char **address)
274 /* A file backend has no particular email address associated
275 * with it (although that would be a useful feature some day).
279 return GNOME_Evolution_Calendar_Success;
282 static ECalBackendSyncStatus
283 e_cal_backend_file_get_ldap_attribute (ECalBackendSync *backend, EDataCal *cal, char **attribute)
287 return GNOME_Evolution_Calendar_Success;
290 static ECalBackendSyncStatus
291 e_cal_backend_file_get_alarm_email_address (ECalBackendSync *backend, EDataCal *cal, char **address)
293 /* A file backend has no particular email address associated
294 * with it (although that would be a useful feature some day).
298 return GNOME_Evolution_Calendar_Success;
301 static ECalBackendSyncStatus
302 e_cal_backend_file_get_static_capabilities (ECalBackendSync *backend, EDataCal *cal, char **capabilities)
304 *capabilities = CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS;
306 return GNOME_Evolution_Calendar_Success;
309 /* function to resolve timezones */
310 static icaltimezone *
311 resolve_tzid (const char *tzid, gpointer user_data)
313 icalcomponent *vcalendar_comp = user_data;
315 if (!tzid || !tzid[0])
317 else if (!strcmp (tzid, "UTC"))
318 return icaltimezone_get_utc_timezone ();
320 return icalcomponent_get_timezone (vcalendar_comp, tzid);
323 /* Checks if the specified component has a duplicated UID and if so changes it */
325 check_dup_uid (ECalBackendFile *cbfile, ECalComponent *comp)
327 ECalBackendFilePrivate *priv;
328 ECalBackendFileObject *obj_data;
334 e_cal_component_get_uid (comp, &uid);
336 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
338 return; /* Everything is fine */
340 g_message ("check_dup_uid(): Got object with duplicated UID `%s', changing it...", uid);
342 new_uid = e_cal_component_gen_uid ();
343 e_cal_component_set_uid (comp, new_uid);
346 /* FIXME: I think we need to reset the SEQUENCE property and reset the
347 * CREATED/DTSTAMP/LAST-MODIFIED.
354 get_rid_string (ECalComponent *comp)
356 ECalComponentRange range;
357 struct icaltimetype tt;
359 e_cal_component_get_recurid (comp, &range);
360 if (!range.datetime.value)
362 tt = *range.datetime.value;
363 e_cal_component_free_range (&range);
365 return icaltime_is_valid_time (tt) && !icaltime_is_null_time (tt) ?
366 icaltime_as_ical_string (tt) : "0";
369 static struct icaltimetype
370 get_rid_icaltime (ECalComponent *comp)
372 ECalComponentRange range;
373 struct icaltimetype tt;
375 e_cal_component_get_recurid (comp, &range);
376 if (!range.datetime.value)
377 return icaltime_null_time ();
378 tt = *range.datetime.value;
379 e_cal_component_free_range (&range);
384 /* Tries to add an icalcomponent to the file backend. We only store the objects
385 * of the types we support; all others just remain in the toplevel component so
386 * that we don't lose them.
389 add_component (ECalBackendFile *cbfile, ECalComponent *comp, gboolean add_to_toplevel)
391 ECalBackendFilePrivate *priv;
392 ECalBackendFileObject *obj_data;
398 if (e_cal_component_is_instance (comp)) { /* FIXME: more checks needed, to detect detached instances */
401 e_cal_component_get_uid (comp, &uid);
403 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
405 g_warning (G_STRLOC ": Got an instance of a non-existing component");
409 rid = get_rid_string (comp);
410 if (g_hash_table_lookup (obj_data->recurrences, rid)) {
411 g_warning (G_STRLOC ": Tried to adding an already existing recurrence");
415 g_hash_table_insert (obj_data->recurrences, g_strdup (rid), comp);
417 /* Ensure that the UID is unique; some broken implementations spit
418 * components with duplicated UIDs.
420 check_dup_uid (cbfile, comp);
421 e_cal_component_get_uid (comp, &uid);
423 obj_data = g_new0 (ECalBackendFileObject, 1);
424 obj_data->full_object = comp;
425 obj_data->recurrences = g_hash_table_new (g_str_hash, g_str_equal);
427 g_hash_table_insert (priv->comp_uid_hash, (gpointer) uid, obj_data);
430 priv->comp = g_list_prepend (priv->comp, comp);
432 /* Put the object in the toplevel component if required */
434 if (add_to_toplevel) {
435 icalcomponent *icalcomp;
437 icalcomp = e_cal_component_get_icalcomponent (comp);
438 g_assert (icalcomp != NULL);
440 icalcomponent_add_component (priv->icalcomp, icalcomp);
443 /* Update the set of categories */
444 e_cal_component_get_categories_list (comp, &categories);
445 e_cal_backend_ref_categories (E_CAL_BACKEND (cbfile), categories);
446 e_cal_component_free_categories_list (categories);
449 /* g_hash_table_foreach() callback to remove recurrences from the calendar */
451 remove_recurrence_cb (gpointer key, gpointer value, gpointer data)
455 icalcomponent *icalcomp;
456 ECalBackendFilePrivate *priv;
457 ECalComponent *comp = value;
458 ECalBackendFile *cbfile = data;
462 /* remove the recurrence from the top-level calendar */
463 icalcomp = e_cal_component_get_icalcomponent (comp);
464 g_assert (icalcomp != NULL);
466 icalcomponent_remove_component (priv->icalcomp, icalcomp);
468 /* remove it from our mapping */
469 l = g_list_find (priv->comp, comp);
470 priv->comp = g_list_delete_link (priv->comp, l);
472 /* update the set of categories */
473 e_cal_component_get_categories_list (comp, &categories);
474 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
475 e_cal_component_free_categories_list (categories);
478 /* Removes a component from the backend's hash and lists. Does not perform
479 * notification on the clients. Also removes the component from the toplevel
483 remove_component (ECalBackendFile *cbfile, ECalComponent *comp)
485 ECalBackendFilePrivate *priv;
486 icalcomponent *icalcomp;
490 ECalBackendFileObject *obj_data;
494 /* Remove the icalcomp from the toplevel */
496 icalcomp = e_cal_component_get_icalcomponent (comp);
497 g_assert (icalcomp != NULL);
499 icalcomponent_remove_component (priv->icalcomp, icalcomp);
501 /* Remove it from our mapping */
503 e_cal_component_get_uid (comp, &uid);
504 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
508 g_hash_table_remove (priv->comp_uid_hash, uid);
510 l = g_list_find (priv->comp, comp);
511 g_assert (l != NULL);
512 priv->comp = g_list_delete_link (priv->comp, l);
514 /* remove the recurrences also */
515 g_hash_table_foreach (obj_data->recurrences, (GHFunc) remove_recurrence_cb, cbfile);
517 /* Update the set of categories */
518 e_cal_component_get_categories_list (comp, &categories);
519 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
520 e_cal_component_free_categories_list (categories);
522 free_object ((gpointer) uid, (gpointer) obj_data, NULL);
525 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
527 scan_vcalendar (ECalBackendFile *cbfile)
529 ECalBackendFilePrivate *priv;
533 g_assert (priv->icalcomp != NULL);
534 g_assert (priv->comp_uid_hash != NULL);
536 for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_ANY_COMPONENT);
537 icalcompiter_deref (&iter) != NULL;
538 icalcompiter_next (&iter)) {
539 icalcomponent *icalcomp;
540 icalcomponent_kind kind;
543 icalcomp = icalcompiter_deref (&iter);
545 kind = icalcomponent_isa (icalcomp);
547 if (!(kind == ICAL_VEVENT_COMPONENT
548 || kind == ICAL_VTODO_COMPONENT
549 || kind == ICAL_VJOURNAL_COMPONENT))
552 comp = e_cal_component_new ();
554 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
557 add_component (cbfile, comp, FALSE);
561 /* Parses an open iCalendar file and loads it into the backend */
562 static ECalBackendSyncStatus
563 open_cal (ECalBackendFile *cbfile, const char *uristr)
565 ECalBackendFilePrivate *priv;
566 icalcomponent *icalcomp;
570 icalcomp = e_cal_util_parse_ics_file (uristr);
572 return GNOME_Evolution_Calendar_OtherError;
574 /* FIXME: should we try to demangle XROOT components and
575 * individual components as well?
578 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
579 icalcomponent_free (icalcomp);
581 return GNOME_Evolution_Calendar_OtherError;
584 priv->icalcomp = icalcomp;
586 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
587 scan_vcalendar (cbfile);
589 priv->uri = g_strdup (uristr);
591 return GNOME_Evolution_Calendar_Success;
594 static ECalBackendSyncStatus
595 create_cal (ECalBackendFile *cbfile, const char *uristr)
597 ECalBackendFilePrivate *priv;
601 /* Create the new calendar information */
602 priv->icalcomp = e_cal_util_new_top_level ();
604 /* Create our internal data */
605 priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
607 priv->uri = g_strdup (uristr);
611 return GNOME_Evolution_Calendar_Success;
615 get_uri_string (ECalBackend *backend)
617 ECalBackendFile *cbfile;
618 ECalBackendFilePrivate *priv;
619 const char *master_uri;
620 char *full_uri, *str_uri;
623 cbfile = E_CAL_BACKEND_FILE (backend);
626 master_uri = e_cal_backend_get_uri (backend);
627 g_message (G_STRLOC ": Trying to open %s", master_uri);
629 /* FIXME Check the error conditions a little more elegantly here */
630 if (g_strrstr ("tasks.ics", master_uri) || g_strrstr ("calendar.ics", master_uri)) {
631 g_warning (G_STRLOC ": Existing file name %s", master_uri);
636 full_uri = g_strdup_printf ("%s%s%s", master_uri, G_DIR_SEPARATOR_S, priv->file_name);
637 uri = gnome_vfs_uri_new (full_uri);
643 str_uri = gnome_vfs_uri_to_string (uri,
644 (GNOME_VFS_URI_HIDE_USER_NAME
645 | GNOME_VFS_URI_HIDE_PASSWORD
646 | GNOME_VFS_URI_HIDE_HOST_NAME
647 | GNOME_VFS_URI_HIDE_HOST_PORT
648 | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD));
649 gnome_vfs_uri_unref (uri);
651 if (!str_uri || !strlen (str_uri)) {
660 /* Open handler for the file backend */
661 static ECalBackendSyncStatus
662 e_cal_backend_file_open (ECalBackendSync *backend, EDataCal *cal, gboolean only_if_exists)
664 ECalBackendFile *cbfile;
665 ECalBackendFilePrivate *priv;
667 ECalBackendSyncStatus status;
669 cbfile = E_CAL_BACKEND_FILE (backend);
672 /* Claim a succesful open if we are already open */
673 if (priv->uri && priv->comp_uid_hash)
674 return GNOME_Evolution_Calendar_Success;
676 str_uri = get_uri_string (E_CAL_BACKEND (backend));
678 return GNOME_Evolution_Calendar_OtherError;
680 if (access (str_uri, R_OK) == 0) {
681 status = open_cal (cbfile, str_uri);
682 if (access (str_uri, W_OK) != 0)
683 priv->read_only = TRUE;
686 status = GNOME_Evolution_Calendar_NoSuchCal;
688 status = create_cal (cbfile, str_uri);
696 static ECalBackendSyncStatus
697 e_cal_backend_file_remove (ECalBackendSync *backend, EDataCal *cal)
699 ECalBackendFile *cbfile;
700 ECalBackendFilePrivate *priv;
703 cbfile = E_CAL_BACKEND_FILE (backend);
706 str_uri = get_uri_string (E_CAL_BACKEND (backend));
708 return GNOME_Evolution_Calendar_OtherError;
710 if (access (str_uri, W_OK) != 0) {
713 return GNOME_Evolution_Calendar_PermissionDenied;
716 /* FIXME Remove backup file and whole directory too? */
717 if (unlink (str_uri) != 0) {
720 return GNOME_Evolution_Calendar_OtherError;
725 return GNOME_Evolution_Calendar_Success;
728 /* is_loaded handler for the file backend */
730 e_cal_backend_file_is_loaded (ECalBackend *backend)
732 ECalBackendFile *cbfile;
733 ECalBackendFilePrivate *priv;
735 cbfile = E_CAL_BACKEND_FILE (backend);
738 return (priv->icalcomp != NULL);
741 /* is_remote handler for the file backend */
743 e_cal_backend_file_get_mode (ECalBackend *backend)
745 ECalBackendFile *cbfile;
746 ECalBackendFilePrivate *priv;
748 cbfile = E_CAL_BACKEND_FILE (backend);
751 return CAL_MODE_LOCAL;
754 /* Set_mode handler for the file backend */
756 e_cal_backend_file_set_mode (ECalBackend *backend, CalMode mode)
758 e_cal_backend_notify_mode (backend,
759 GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
760 GNOME_Evolution_Calendar_MODE_LOCAL);
764 static ECalBackendSyncStatus
765 e_cal_backend_file_get_default_object (ECalBackendSync *backend, EDataCal *cal, char **object)
769 comp = e_cal_component_new ();
771 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
772 case ICAL_VEVENT_COMPONENT:
773 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
775 case ICAL_VTODO_COMPONENT:
776 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
778 case ICAL_VJOURNAL_COMPONENT:
779 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
782 g_object_unref (comp);
783 return GNOME_Evolution_Calendar_ObjectNotFound;
786 *object = e_cal_component_get_as_string (comp);
787 g_object_unref (comp);
789 return GNOME_Evolution_Calendar_Success;
792 /* Get_object_component handler for the file backend */
793 static ECalBackendSyncStatus
794 e_cal_backend_file_get_object (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *rid, char **object)
796 ECalBackendFile *cbfile;
797 ECalBackendFilePrivate *priv;
798 ECalBackendFileObject *obj_data;
799 ECalComponent *comp = NULL;
800 gboolean free_comp = FALSE;
802 cbfile = E_CAL_BACKEND_FILE (backend);
805 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
806 g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
807 g_assert (priv->comp_uid_hash != NULL);
809 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
811 return GNOME_Evolution_Calendar_ObjectNotFound;
814 comp = g_hash_table_lookup (obj_data->recurrences, rid);
816 icalcomponent *icalcomp;
817 struct icaltimetype itt;
819 itt = icaltime_from_string (rid);
820 icalcomp = e_cal_util_construct_instance (
821 e_cal_component_get_icalcomponent (obj_data->full_object),
824 return GNOME_Evolution_Calendar_ObjectNotFound;
826 comp = e_cal_component_new ();
828 e_cal_component_set_icalcomponent (comp, icalcomp);
831 comp = obj_data->full_object;
834 return GNOME_Evolution_Calendar_ObjectNotFound;
836 *object = e_cal_component_get_as_string (comp);
839 g_object_unref (comp);
841 return GNOME_Evolution_Calendar_Success;
844 /* Get_timezone_object handler for the file backend */
845 static ECalBackendSyncStatus
846 e_cal_backend_file_get_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid, char **object)
848 ECalBackendFile *cbfile;
849 ECalBackendFilePrivate *priv;
851 icalcomponent *icalcomp;
853 cbfile = E_CAL_BACKEND_FILE (backend);
856 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
857 g_return_val_if_fail (tzid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
859 if (!strcmp (tzid, "UTC")) {
860 zone = icaltimezone_get_utc_timezone ();
862 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
864 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
866 return GNOME_Evolution_Calendar_ObjectNotFound;
870 icalcomp = icaltimezone_get_component (zone);
872 return GNOME_Evolution_Calendar_InvalidObject;
874 *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
876 return GNOME_Evolution_Calendar_Success;
879 /* Add_timezone handler for the file backend */
880 static ECalBackendSyncStatus
881 e_cal_backend_file_add_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzobj)
883 icalcomponent *tz_comp;
884 ECalBackendFile *cbfile;
885 ECalBackendFilePrivate *priv;
887 cbfile = (ECalBackendFile *) backend;
889 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), GNOME_Evolution_Calendar_OtherError);
890 g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
894 tz_comp = icalparser_parse_string (tzobj);
896 return GNOME_Evolution_Calendar_InvalidObject;
898 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
901 zone = icaltimezone_new ();
902 icaltimezone_set_component (zone, tz_comp);
903 if (!icalcomponent_get_timezone (priv->icalcomp,
904 icaltimezone_get_tzid (zone))) {
905 icalcomponent_add_component (priv->icalcomp, tz_comp);
909 icaltimezone_free (zone, 1);
912 return GNOME_Evolution_Calendar_Success;
916 static ECalBackendSyncStatus
917 e_cal_backend_file_set_default_timezone (ECalBackendSync *backend, EDataCal *cal, const char *tzid)
919 ECalBackendFile *cbfile;
920 ECalBackendFilePrivate *priv;
923 cbfile = E_CAL_BACKEND_FILE (backend);
926 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
928 /* Look up the VTIMEZONE in our icalcomponent. */
929 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
931 return GNOME_Evolution_Calendar_ObjectNotFound;
933 /* Set the default timezone to it. */
934 priv->default_zone = zone;
936 return GNOME_Evolution_Calendar_Success;
941 gboolean search_needed;
943 ECalBackendSExp *obj_sexp;
944 ECalBackend *backend;
945 icaltimezone *default_zone;
949 match_recurrence_sexp (gpointer key, gpointer value, gpointer data)
951 ECalComponent *comp = value;
952 MatchObjectData *match_data = data;
954 if ((!match_data->search_needed) ||
955 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
956 match_data->obj_list = g_list_append (match_data->obj_list,
957 e_cal_component_get_as_string (comp));
962 match_object_sexp (gpointer key, gpointer value, gpointer data)
964 ECalBackendFileObject *obj_data = value;
965 MatchObjectData *match_data = data;
967 if ((!match_data->search_needed) ||
968 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, obj_data->full_object, match_data->backend))) {
969 match_data->obj_list = g_list_append (match_data->obj_list,
970 e_cal_component_get_as_string (obj_data->full_object));
972 /* match also recurrences */
973 g_hash_table_foreach (obj_data->recurrences,
974 (GHFunc) match_recurrence_sexp,
979 /* Get_objects_in_range handler for the file backend */
980 static ECalBackendSyncStatus
981 e_cal_backend_file_get_object_list (ECalBackendSync *backend, EDataCal *cal, const char *sexp, GList **objects)
983 ECalBackendFile *cbfile;
984 ECalBackendFilePrivate *priv;
985 MatchObjectData match_data;
987 cbfile = E_CAL_BACKEND_FILE (backend);
990 g_message (G_STRLOC ": Getting object list (%s)", sexp);
992 match_data.search_needed = TRUE;
993 match_data.query = sexp;
994 match_data.obj_list = NULL;
995 match_data.backend = E_CAL_BACKEND (backend);
996 match_data.default_zone = priv->default_zone;
998 if (!strcmp (sexp, "#t"))
999 match_data.search_needed = FALSE;
1001 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1002 if (!match_data.obj_sexp)
1003 return GNOME_Evolution_Calendar_InvalidQuery;
1005 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1007 *objects = match_data.obj_list;
1009 return GNOME_Evolution_Calendar_Success;
1012 /* get_query handler for the file backend */
1014 e_cal_backend_file_start_query (ECalBackend *backend, EDataCalView *query)
1016 ECalBackendFile *cbfile;
1017 ECalBackendFilePrivate *priv;
1018 MatchObjectData match_data;
1020 cbfile = E_CAL_BACKEND_FILE (backend);
1021 priv = cbfile->priv;
1023 g_message (G_STRLOC ": Starting query (%s)", e_data_cal_view_get_text (query));
1025 /* try to match all currently existing objects */
1026 match_data.search_needed = TRUE;
1027 match_data.query = e_data_cal_view_get_text (query);
1028 match_data.obj_list = NULL;
1029 match_data.backend = backend;
1030 match_data.default_zone = priv->default_zone;
1032 if (!strcmp (match_data.query, "#t"))
1033 match_data.search_needed = FALSE;
1035 match_data.obj_sexp = e_data_cal_view_get_object_sexp (query);
1036 if (!match_data.obj_sexp) {
1037 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_InvalidQuery);
1041 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp, &match_data);
1043 /* notify listeners of all objects */
1044 if (match_data.obj_list) {
1045 e_data_cal_view_notify_objects_added (query, (const GList *) match_data.obj_list);
1048 g_list_foreach (match_data.obj_list, (GFunc) g_free, NULL);
1049 g_list_free (match_data.obj_list);
1052 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
1056 free_busy_instance (ECalComponent *comp,
1057 time_t instance_start,
1058 time_t instance_end,
1061 icalcomponent *vfb = data;
1063 icalparameter *param;
1064 struct icalperiodtype ipt;
1065 icaltimezone *utc_zone;
1067 utc_zone = icaltimezone_get_utc_timezone ();
1069 ipt.start = icaltime_from_timet_with_zone (instance_start, FALSE, utc_zone);
1070 ipt.end = icaltime_from_timet_with_zone (instance_end, FALSE, utc_zone);
1071 ipt.duration = icaldurationtype_null_duration ();
1073 /* add busy information to the vfb component */
1074 prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
1075 icalproperty_set_freebusy (prop, ipt);
1077 param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
1078 icalproperty_add_parameter (prop, param);
1080 icalcomponent_add_property (vfb, prop);
1085 static icalcomponent *
1086 create_user_free_busy (ECalBackendFile *cbfile, const char *address, const char *cn,
1087 time_t start, time_t end)
1089 ECalBackendFilePrivate *priv;
1092 icaltimezone *utc_zone;
1093 ECalBackendSExp *obj_sexp;
1096 priv = cbfile->priv;
1098 /* create the (unique) VFREEBUSY object that we'll return */
1099 vfb = icalcomponent_new_vfreebusy ();
1100 if (address != NULL) {
1102 icalparameter *param;
1104 prop = icalproperty_new_organizer (address);
1105 if (prop != NULL && cn != NULL) {
1106 param = icalparameter_new_cn (cn);
1107 icalproperty_add_parameter (prop, param);
1110 icalcomponent_add_property (vfb, prop);
1112 utc_zone = icaltimezone_get_utc_timezone ();
1113 icalcomponent_set_dtstart (vfb, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
1114 icalcomponent_set_dtend (vfb, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
1116 /* add all objects in the given interval */
1117 query = g_strdup_printf ("occur-in-time-range? %lu %lu", start, end);
1118 obj_sexp = e_cal_backend_sexp_new (query);
1124 for (l = priv->comp; l; l = l->next) {
1125 ECalComponent *comp = l->data;
1126 icalcomponent *icalcomp, *vcalendar_comp;
1129 icalcomp = e_cal_component_get_icalcomponent (comp);
1133 /* If the event is TRANSPARENT, skip it. */
1134 prop = icalcomponent_get_first_property (icalcomp,
1135 ICAL_TRANSP_PROPERTY);
1137 icalproperty_transp transp_val = icalproperty_get_transp (prop);
1138 if (transp_val == ICAL_TRANSP_TRANSPARENT ||
1139 transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
1143 if (!e_cal_backend_sexp_match_comp (obj_sexp, l->data, E_CAL_BACKEND (cbfile)))
1146 vcalendar_comp = icalcomponent_get_parent (icalcomp);
1147 e_cal_recur_generate_instances (comp, start, end,
1152 priv->default_zone);
1158 /* Get_free_busy handler for the file backend */
1159 static ECalBackendSyncStatus
1160 e_cal_backend_file_get_free_busy (ECalBackendSync *backend, EDataCal *cal, GList *users,
1161 time_t start, time_t end, GList **freebusy)
1163 ECalBackendFile *cbfile;
1164 ECalBackendFilePrivate *priv;
1165 gchar *address, *name;
1170 cbfile = E_CAL_BACKEND_FILE (backend);
1171 priv = cbfile->priv;
1173 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1174 g_return_val_if_fail (start != -1 && end != -1, GNOME_Evolution_Calendar_InvalidRange);
1175 g_return_val_if_fail (start <= end, GNOME_Evolution_Calendar_InvalidRange);
1179 if (users == NULL) {
1180 if (e_cal_backend_mail_account_get_default (&address, &name)) {
1181 vfb = create_user_free_busy (cbfile, address, name, start, end);
1182 calobj = icalcomponent_as_ical_string (vfb);
1183 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1184 icalcomponent_free (vfb);
1189 for (l = users; l != NULL; l = l->next ) {
1191 if (e_cal_backend_mail_account_is_valid (address, &name)) {
1192 vfb = create_user_free_busy (cbfile, address, name, start, end);
1193 calobj = icalcomponent_as_ical_string (vfb);
1194 *freebusy = g_list_append (*freebusy, g_strdup (calobj));
1195 icalcomponent_free (vfb);
1201 return GNOME_Evolution_Calendar_Success;
1206 ECalBackendFile *backend;
1207 icalcomponent_kind kind;
1210 } ECalBackendFileComputeChangesData;
1213 e_cal_backend_file_compute_changes_foreach_key (const char *key, gpointer data)
1215 ECalBackendFileComputeChangesData *be_data = data;
1217 if (!lookup_component (be_data->backend, key)) {
1218 ECalComponent *comp;
1220 comp = e_cal_component_new ();
1221 if (be_data->kind == ICAL_VTODO_COMPONENT)
1222 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
1224 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
1226 e_cal_component_set_uid (comp, key);
1227 be_data->deletes = g_list_prepend (be_data->deletes, e_cal_component_get_as_string (comp));
1229 e_xmlhash_remove (be_data->ehash, key);
1233 static ECalBackendSyncStatus
1234 e_cal_backend_file_compute_changes (ECalBackendFile *cbfile, const char *change_id,
1235 GList **adds, GList **modifies, GList **deletes)
1237 ECalBackendFilePrivate *priv;
1240 ECalBackendFileComputeChangesData be_data;
1243 priv = cbfile->priv;
1245 /* FIXME Will this always work? */
1246 filename = g_strdup_printf ("%s/%s.db", priv->uri, change_id);
1247 ehash = e_xmlhash_new (filename);
1250 /* Calculate adds and modifies */
1251 for (i = priv->comp; i != NULL; i = i->next) {
1255 e_cal_component_get_uid (i->data, &uid);
1256 calobj = e_cal_component_get_as_string (i->data);
1258 g_assert (calobj != NULL);
1260 /* check what type of change has occurred, if any */
1261 switch (e_xmlhash_compare (ehash, uid, calobj)) {
1262 case E_XMLHASH_STATUS_SAME:
1264 case E_XMLHASH_STATUS_NOT_FOUND:
1265 *adds = g_list_prepend (*adds, g_strdup (calobj));
1266 e_xmlhash_add (ehash, uid, calobj);
1268 case E_XMLHASH_STATUS_DIFFERENT:
1269 *modifies = g_list_prepend (*modifies, g_strdup (calobj));
1270 e_xmlhash_add (ehash, uid, calobj);
1277 /* Calculate deletions */
1278 be_data.backend = cbfile;
1279 be_data.kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbfile));
1280 be_data.deletes = NULL;
1281 be_data.ehash = ehash;
1282 e_xmlhash_foreach_key (ehash, (EXmlHashFunc)e_cal_backend_file_compute_changes_foreach_key, &be_data);
1284 *deletes = be_data.deletes;
1286 e_xmlhash_write (ehash);
1287 e_xmlhash_destroy (ehash);
1289 return GNOME_Evolution_Calendar_Success;
1292 /* Get_changes handler for the file backend */
1293 static ECalBackendSyncStatus
1294 e_cal_backend_file_get_changes (ECalBackendSync *backend, EDataCal *cal, const char *change_id,
1295 GList **adds, GList **modifies, GList **deletes)
1297 ECalBackendFile *cbfile;
1298 ECalBackendFilePrivate *priv;
1300 cbfile = E_CAL_BACKEND_FILE (backend);
1301 priv = cbfile->priv;
1303 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1304 g_return_val_if_fail (change_id != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1306 return e_cal_backend_file_compute_changes (cbfile, change_id, adds, modifies, deletes);
1309 /* Discard_alarm handler for the file backend */
1310 static ECalBackendSyncStatus
1311 e_cal_backend_file_discard_alarm (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *auid)
1313 /* we just do nothing with the alarm */
1314 return GNOME_Evolution_Calendar_Success;
1317 static ECalBackendSyncStatus
1318 e_cal_backend_file_create_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj, char **uid)
1320 ECalBackendFile *cbfile;
1321 ECalBackendFilePrivate *priv;
1322 icalcomponent *icalcomp;
1323 icalcomponent_kind kind;
1324 ECalComponent *comp;
1325 const char *comp_uid;
1326 struct icaltimetype current;
1328 cbfile = E_CAL_BACKEND_FILE (backend);
1329 priv = cbfile->priv;
1331 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1332 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1334 icalcomp = icalparser_parse_string ((char *) calobj);
1336 return GNOME_Evolution_Calendar_InvalidObject;
1338 /* FIXME Check kind with the parent */
1339 kind = icalcomponent_isa (icalcomp);
1340 if (kind != ICAL_VEVENT_COMPONENT && kind != ICAL_VTODO_COMPONENT) {
1341 icalcomponent_free (icalcomp);
1342 return GNOME_Evolution_Calendar_InvalidObject;
1346 comp_uid = icalcomponent_get_uid (icalcomp);
1348 /* check the object is not in our cache */
1349 if (lookup_component (cbfile, comp_uid)) {
1350 icalcomponent_free (icalcomp);
1351 return GNOME_Evolution_Calendar_CardIdAlreadyExists;
1354 /* Create the cal component */
1355 comp = e_cal_component_new ();
1356 e_cal_component_set_icalcomponent (comp, icalcomp);
1358 /* Set the created and last modified times on the component */
1359 current = icaltime_from_timet (time (NULL), 0);
1360 e_cal_component_set_created (comp, ¤t);
1361 e_cal_component_set_last_modified (comp, ¤t);
1363 /* Add the object */
1364 add_component (cbfile, comp, TRUE);
1369 /* Return the UID */
1371 *uid = g_strdup (comp_uid);
1373 return GNOME_Evolution_Calendar_Success;
1376 static ECalBackendSyncStatus
1377 e_cal_backend_file_modify_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj,
1378 CalObjModType mod, char **old_object)
1380 ECalBackendFile *cbfile;
1381 ECalBackendFilePrivate *priv;
1382 icalcomponent *icalcomp;
1383 icalcomponent_kind kind;
1384 const char *comp_uid, *rid;
1386 ECalComponent *comp, *recurrence;
1387 ECalBackendFileObject *obj_data;
1388 struct icaltimetype current;
1390 cbfile = E_CAL_BACKEND_FILE (backend);
1391 priv = cbfile->priv;
1393 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1394 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1396 icalcomp = icalparser_parse_string ((char *) calobj);
1398 return GNOME_Evolution_Calendar_InvalidObject;
1400 /* check kind with the parent */
1401 kind = icalcomponent_isa (icalcomp);
1402 if (kind != ICAL_VEVENT_COMPONENT && kind != ICAL_VTODO_COMPONENT) {
1403 icalcomponent_free (icalcomp);
1404 return GNOME_Evolution_Calendar_InvalidObject;
1408 comp_uid = icalcomponent_get_uid (icalcomp);
1410 /* Get the object from our cache */
1411 if (!(obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid))) {
1412 icalcomponent_free (icalcomp);
1413 return GNOME_Evolution_Calendar_ObjectNotFound;
1416 /* Create the cal component */
1417 comp = e_cal_component_new ();
1418 e_cal_component_set_icalcomponent (comp, icalcomp);
1420 /* Set the last modified time on the component */
1421 current = icaltime_from_timet (time (NULL), 0);
1422 e_cal_component_set_last_modified (comp, ¤t);
1424 /* handle mod_type */
1426 case CALOBJ_MOD_THIS :
1427 rid = get_rid_string (comp);
1428 if (!rid || !*rid) {
1429 g_object_unref (comp);
1430 return GNOME_Evolution_Calendar_ObjectNotFound;
1433 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
1434 &real_rid, &recurrence)) {
1435 /* remove the component from our data */
1436 icalcomponent_remove_component (priv->icalcomp,
1437 e_cal_component_get_icalcomponent (recurrence));
1438 priv->comp = g_list_remove (priv->comp, recurrence);
1439 g_hash_table_remove (obj_data->recurrences, rid);
1443 g_object_unref (recurrence);
1447 old = e_cal_component_get_as_string (obj_data->full_object);
1449 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
1450 get_rid_icaltime (comp),
1453 new = e_cal_component_get_as_string (obj_data->full_object);
1455 e_cal_backend_notify_object_modified (E_CAL_BACKEND (backend), old, new);
1461 /* add the detached instance */
1462 g_hash_table_insert (obj_data->recurrences, g_strdup (get_rid_string (comp)), comp);
1464 case CALOBJ_MOD_THISANDPRIOR :
1466 case CALOBJ_MOD_THISANDFUTURE :
1468 case CALOBJ_MOD_ALL :
1469 /* in this case, we blow away all recurrences, and start over
1470 with a clean component */
1471 /* Remove the old version */
1472 remove_component (cbfile, obj_data->full_object);
1474 /* Add the new object */
1475 add_component (cbfile, comp, TRUE);
1482 *old_object = e_cal_component_get_as_string (comp);
1484 return GNOME_Evolution_Calendar_Success;
1488 remove_instance (ECalBackendFile *cbfile, ECalBackendFileObject *obj_data, const char *rid)
1491 ECalComponent *comp;
1497 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, &hash_rid, &comp)) {
1498 /* remove the component from our data */
1499 icalcomponent_remove_component (cbfile->priv->icalcomp,
1500 e_cal_component_get_icalcomponent (comp));
1501 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
1502 g_hash_table_remove (obj_data->recurrences, rid);
1504 /* update the set of categories */
1505 e_cal_component_get_categories_list (comp, &categories);
1506 e_cal_backend_unref_categories (E_CAL_BACKEND (cbfile), categories);
1507 e_cal_component_free_categories_list (categories);
1511 g_object_unref (comp);
1516 /* remove the component from our data, temporarily */
1517 icalcomponent_remove_component (cbfile->priv->icalcomp,
1518 e_cal_component_get_icalcomponent (obj_data->full_object));
1519 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
1521 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
1522 icaltime_from_string (rid), CALOBJ_MOD_THIS);
1524 /* add the modified object to the beginning of the list,
1525 so that it's always before any detached instance we
1527 cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
1531 ECalBackendFile *cbfile;
1532 ECalBackendFileObject *obj_data;
1535 } RemoveRecurrenceData;
1538 remove_object_instance_cb (gpointer key, gpointer value, gpointer user_data)
1540 time_t fromtt, instancett;
1543 ECalComponent *instance = value;
1544 RemoveRecurrenceData *rrdata = user_data;
1546 fromtt = icaltime_as_timet (icaltime_from_string (rrdata->rid));
1547 instancett = icaltime_as_timet (get_rid_icaltime (instance));
1549 if (fromtt > 0 && instancett > 0) {
1550 if ((rrdata->mod == CALOBJ_MOD_THISANDPRIOR && instancett <= fromtt) ||
1551 (rrdata->mod == CALOBJ_MOD_THISANDFUTURE && instancett >= fromtt)) {
1552 /* remove the component from our data */
1553 icalcomponent_remove_component (rrdata->cbfile->priv->icalcomp,
1554 e_cal_component_get_icalcomponent (instance));
1555 rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
1557 /* update the set of categories */
1558 e_cal_component_get_categories_list (instance, &categories);
1559 e_cal_backend_unref_categories (E_CAL_BACKEND (rrdata->cbfile), categories);
1560 e_cal_component_free_categories_list (categories);
1564 g_object_unref (instance);
1573 /* Remove_object handler for the file backend */
1574 static ECalBackendSyncStatus
1575 e_cal_backend_file_remove_object (ECalBackendSync *backend, EDataCal *cal,
1576 const char *uid, const char *rid,
1577 CalObjModType mod, char **object)
1579 ECalBackendFile *cbfile;
1580 ECalBackendFilePrivate *priv;
1581 ECalBackendFileObject *obj_data;
1582 ECalComponent *comp;
1584 RemoveRecurrenceData rrdata;
1586 cbfile = E_CAL_BACKEND_FILE (backend);
1587 priv = cbfile->priv;
1589 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_NoSuchCal);
1590 g_return_val_if_fail (uid != NULL, GNOME_Evolution_Calendar_ObjectNotFound);
1592 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1594 return GNOME_Evolution_Calendar_ObjectNotFound;
1596 comp = obj_data->full_object;
1599 case CALOBJ_MOD_ALL :
1600 *object = e_cal_component_get_as_string (comp);
1601 remove_component (cbfile, comp);
1603 case CALOBJ_MOD_THIS :
1605 return GNOME_Evolution_Calendar_ObjectNotFound;
1607 remove_instance (cbfile, obj_data, rid);
1609 case CALOBJ_MOD_THISANDPRIOR :
1610 case CALOBJ_MOD_THISANDFUTURE :
1612 return GNOME_Evolution_Calendar_ObjectNotFound;
1614 /* remove the component from our data, temporarily */
1615 icalcomponent_remove_component (priv->icalcomp,
1616 e_cal_component_get_icalcomponent (comp));
1617 priv->comp = g_list_remove (priv->comp, comp);
1619 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (comp),
1620 icaltime_from_string (rid), mod);
1622 /* now remove all detached instances */
1623 rrdata.cbfile = cbfile;
1624 rrdata.obj_data = obj_data;
1627 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
1629 /* add the modified object to the beginning of the list,
1630 so that it's always before any detached instance we
1632 priv->comp = g_list_prepend (priv->comp, comp);
1638 return GNOME_Evolution_Calendar_Success;
1642 cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp)
1644 ECalComponent *old_comp;
1646 /* Find the old version of the component. */
1647 old_comp = lookup_component (cbfile, icalcomponent_get_uid (icalcomp));
1652 remove_component (cbfile, old_comp);
1661 } ECalBackendFileTzidData;
1664 check_tzids (icalparameter *param, void *data)
1666 ECalBackendFileTzidData *tzdata = data;
1669 tzid = icalparameter_get_tzid (param);
1670 if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
1671 tzdata->found = FALSE;
1674 /* Update_objects handler for the file backend. */
1675 static ECalBackendSyncStatus
1676 e_cal_backend_file_receive_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj)
1678 ECalBackendFile *cbfile;
1679 ECalBackendFilePrivate *priv;
1680 icalcomponent *toplevel_comp, *icalcomp = NULL;
1681 icalcomponent_kind kind;
1682 icalproperty_method method;
1683 icalcomponent *subcomp;
1685 ECalBackendFileTzidData tzdata;
1686 ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;
1688 cbfile = E_CAL_BACKEND_FILE (backend);
1689 priv = cbfile->priv;
1691 g_return_val_if_fail (priv->icalcomp != NULL, GNOME_Evolution_Calendar_InvalidObject);
1692 g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_InvalidObject);
1694 /* Pull the component from the string and ensure that it is sane */
1695 toplevel_comp = icalparser_parse_string ((char *) calobj);
1697 return GNOME_Evolution_Calendar_InvalidObject;
1699 kind = icalcomponent_isa (toplevel_comp);
1700 if (kind != ICAL_VCALENDAR_COMPONENT) {
1701 /* If its not a VCALENDAR, make it one to simplify below */
1702 icalcomp = toplevel_comp;
1703 toplevel_comp = e_cal_util_new_top_level ();
1704 icalcomponent_add_component (toplevel_comp, icalcomp);
1707 method = icalcomponent_get_method (toplevel_comp);
1709 /* Build a list of timezones so we can make sure all the objects have valid info */
1710 tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1712 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
1716 zone = icaltimezone_new ();
1717 if (icaltimezone_set_component (zone, subcomp))
1718 g_hash_table_insert (tzdata.zones, g_strdup (icaltimezone_get_tzid (zone)), NULL);
1720 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
1723 /* First we make sure all the components are usuable */
1725 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
1727 /* We ignore anything except VEVENT, VTODO and VJOURNAL
1729 icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
1731 switch (child_kind) {
1732 case ICAL_VEVENT_COMPONENT:
1733 case ICAL_VTODO_COMPONENT:
1734 case ICAL_VJOURNAL_COMPONENT:
1735 tzdata.found = TRUE;
1736 icalcomponent_foreach_tzid (subcomp, check_tzids, &tzdata);
1738 if (!tzdata.found) {
1739 status = GNOME_Evolution_Calendar_InvalidObject;
1743 if (!icalcomponent_get_uid (subcomp)) {
1744 status = GNOME_Evolution_Calendar_InvalidObject;
1748 comps = g_list_prepend (comps, subcomp);
1755 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
1758 /* Now we manipulate the components we care about */
1759 for (l = comps; l; l = l->next) {
1763 case ICAL_METHOD_PUBLISH:
1764 case ICAL_METHOD_REQUEST:
1765 /* FIXME Need to see the new create/modify stuff before we set this up */
1767 case ICAL_METHOD_REPLY:
1768 /* FIXME Update the status of the user, if we are the organizer */
1770 case ICAL_METHOD_ADD:
1771 /* FIXME This should be doable once all the recurid stuff is done */
1773 case ICAL_METHOD_COUNTER:
1774 status = GNOME_Evolution_Calendar_UnsupportedMethod;
1777 case ICAL_METHOD_DECLINECOUNTER:
1778 status = GNOME_Evolution_Calendar_UnsupportedMethod;
1781 case ICAL_METHOD_CANCEL:
1782 /* FIXME Do we need to remove the subcomp so it isn't merged? */
1783 if (cancel_received_object (cbfile, subcomp)) {
1784 const char *calobj = icalcomponent_as_ical_string (subcomp);
1785 e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend), icalcomponent_get_uid (subcomp), calobj);
1789 status = GNOME_Evolution_Calendar_UnsupportedMethod;
1793 g_list_free (comps);
1795 /* Merge the iCalendar components with our existing VCALENDAR,
1796 resolving any conflicting TZIDs. */
1797 icalcomponent_merge_component (priv->icalcomp, toplevel_comp);
1802 g_hash_table_destroy (tzdata.zones);
1807 static ECalBackendSyncStatus
1808 e_cal_backend_file_send_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj)
1810 /* FIXME Put in a util routine to send stuff via email */
1812 return GNOME_Evolution_Calendar_Success;
1815 static icaltimezone *
1816 e_cal_backend_file_internal_get_default_timezone (ECalBackend *backend)
1818 ECalBackendFile *cbfile;
1819 ECalBackendFilePrivate *priv;
1821 cbfile = E_CAL_BACKEND_FILE (backend);
1822 priv = cbfile->priv;
1824 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1826 return priv->default_zone;
1829 static icaltimezone *
1830 e_cal_backend_file_internal_get_timezone (ECalBackend *backend, const char *tzid)
1832 ECalBackendFile *cbfile;
1833 ECalBackendFilePrivate *priv;
1836 cbfile = E_CAL_BACKEND_FILE (backend);
1837 priv = cbfile->priv;
1839 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
1841 if (!strcmp (tzid, "UTC"))
1842 zone = icaltimezone_get_utc_timezone ();
1844 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
1846 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
1852 /* Object initialization function for the file backend */
1854 e_cal_backend_file_init (ECalBackendFile *cbfile, ECalBackendFileClass *class)
1856 ECalBackendFilePrivate *priv;
1858 priv = g_new0 (ECalBackendFilePrivate, 1);
1859 cbfile->priv = priv;
1862 priv->file_name = g_strdup ("calendar.ics");
1863 priv->read_only = FALSE;
1864 priv->icalcomp = NULL;
1865 priv->comp_uid_hash = NULL;
1868 /* The timezone defaults to UTC. */
1869 priv->default_zone = icaltimezone_get_utc_timezone ();
1872 /* Class initialization function for the file backend */
1874 e_cal_backend_file_class_init (ECalBackendFileClass *class)
1876 GObjectClass *object_class;
1877 ECalBackendClass *backend_class;
1878 ECalBackendSyncClass *sync_class;
1880 object_class = (GObjectClass *) class;
1881 backend_class = (ECalBackendClass *) class;
1882 sync_class = (ECalBackendSyncClass *) class;
1884 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
1886 object_class->dispose = e_cal_backend_file_dispose;
1887 object_class->finalize = e_cal_backend_file_finalize;
1889 sync_class->is_read_only_sync = e_cal_backend_file_is_read_only;
1890 sync_class->get_cal_address_sync = e_cal_backend_file_get_cal_address;
1891 sync_class->get_alarm_email_address_sync = e_cal_backend_file_get_alarm_email_address;
1892 sync_class->get_ldap_attribute_sync = e_cal_backend_file_get_ldap_attribute;
1893 sync_class->get_static_capabilities_sync = e_cal_backend_file_get_static_capabilities;
1894 sync_class->open_sync = e_cal_backend_file_open;
1895 sync_class->remove_sync = e_cal_backend_file_remove;
1896 sync_class->create_object_sync = e_cal_backend_file_create_object;
1897 sync_class->modify_object_sync = e_cal_backend_file_modify_object;
1898 sync_class->remove_object_sync = e_cal_backend_file_remove_object;
1899 sync_class->discard_alarm_sync = e_cal_backend_file_discard_alarm;
1900 sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
1901 sync_class->send_objects_sync = e_cal_backend_file_send_objects;
1902 sync_class->get_default_object_sync = e_cal_backend_file_get_default_object;
1903 sync_class->get_object_sync = e_cal_backend_file_get_object;
1904 sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
1905 sync_class->get_timezone_sync = e_cal_backend_file_get_timezone;
1906 sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
1907 sync_class->set_default_timezone_sync = e_cal_backend_file_set_default_timezone;
1908 sync_class->get_freebusy_sync = e_cal_backend_file_get_free_busy;
1909 sync_class->get_changes_sync = e_cal_backend_file_get_changes;
1911 backend_class->is_loaded = e_cal_backend_file_is_loaded;
1912 backend_class->start_query = e_cal_backend_file_start_query;
1913 backend_class->get_mode = e_cal_backend_file_get_mode;
1914 backend_class->set_mode = e_cal_backend_file_set_mode;
1916 backend_class->internal_get_default_timezone = e_cal_backend_file_internal_get_default_timezone;
1917 backend_class->internal_get_timezone = e_cal_backend_file_internal_get_timezone;
1922 * e_cal_backend_file_get_type:
1925 * Registers the #ECalBackendFile class if necessary, and returns the type ID
1928 * Return value: The type ID of the #ECalBackendFile class.
1931 e_cal_backend_file_get_type (void)
1933 static GType e_cal_backend_file_type = 0;
1935 if (!e_cal_backend_file_type) {
1936 static GTypeInfo info = {
1937 sizeof (ECalBackendFileClass),
1938 (GBaseInitFunc) NULL,
1939 (GBaseFinalizeFunc) NULL,
1940 (GClassInitFunc) e_cal_backend_file_class_init,
1942 sizeof (ECalBackendFile),
1944 (GInstanceInitFunc) e_cal_backend_file_init
1946 e_cal_backend_file_type = g_type_register_static (E_TYPE_CAL_BACKEND_SYNC,
1947 "ECalBackendFile", &info, 0);
1950 return e_cal_backend_file_type;
1954 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile, const char *file_name)
1956 ECalBackendFilePrivate *priv;
1958 g_return_if_fail (cbfile != NULL);
1959 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
1960 g_return_if_fail (file_name != NULL);
1962 priv = cbfile->priv;
1964 if (priv->file_name)
1965 g_free (priv->file_name);
1967 priv->file_name = g_strdup (file_name);
1971 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
1973 ECalBackendFilePrivate *priv;
1975 g_return_val_if_fail (cbfile != NULL, NULL);
1976 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
1978 priv = cbfile->priv;
1980 return priv->file_name;