1 /* Evolution calendar - iCalendar file backend
3 * Copyright (C) 1993 Free Software Foundation, Inc.
4 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6 * Authors: Federico Mena-Quintero <federico@ximian.com>
7 * Rodrigo Moya <rodrigo@ximian.com>
8 * Jan Brittenson <bson@gnu.ai.mit.edu>
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of version 2 of the GNU Lesser General Public
12 * License as published by the Free Software Foundation.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 #include <sys/types.h>
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
36 #include "libedataserver/e-data-server-util.h"
37 #include "libedataserver/e-xml-hash-utils.h"
38 #include "libedataserver/e-debug-log.h"
39 #include <libecal/e-cal-recur.h>
40 #include <libecal/e-cal-time-util.h>
41 #include <libecal/e-cal-util.h>
42 #include <libecal/e-cal-check-timezones.h>
43 #include <libedata-cal/e-cal-backend-util.h>
44 #include <libedata-cal/e-cal-backend-sexp.h>
45 #include <libedata-cal/e-cal-backend-intervaltree.h>
46 #include "e-cal-backend-file-events.h"
52 #define E_CAL_BACKEND_FILE_GET_PRIVATE(obj) \
53 (G_TYPE_INSTANCE_GET_PRIVATE \
54 ((obj), E_TYPE_CAL_BACKEND_FILE, ECalBackendFilePrivate))
56 #define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
57 #define EDC_ERROR_NO_URI() e_data_cal_create_error (OtherError, "Cannot get URI")
59 #define ECAL_REVISION_X_PROP "X-EVOLUTION-DATA-REVISION"
61 G_DEFINE_TYPE (ECalBackendFile, e_cal_backend_file, E_TYPE_CAL_BACKEND_SYNC)
63 /* Placeholder for each component and its recurrences */
65 ECalComponent *full_object;
66 GHashTable *recurrences;
67 GList *recurrences_list;
68 } ECalBackendFileObject;
70 /* Private part of the ECalBackendFile structure */
71 struct _ECalBackendFilePrivate {
72 /* path where the calendar data is stored */
75 /* Filename in the dir */
81 /* locked in high-level functions to ensure data is consistent
82 * in idle and CORBA thread(s?); because high-level functions
83 * may call other high-level functions the mutex must allow
86 GStaticRecMutex idle_save_rmutex;
88 /* Toplevel VCALENDAR component */
89 icalcomponent *icalcomp;
91 /* All the objects in the calendar, hashed by UID. The
92 * hash key *is* the uid returned by cal_component_get_uid(); it is not
93 * copied, so don't free it when you remove an object from the hash
94 * table. Each item in the hash table is a ECalBackendFileObject.
96 GHashTable *comp_uid_hash;
98 EIntervalTree *interval_tree;
102 /* a custom filename opened */
105 /* guards refresh members */
106 GMutex *refresh_lock;
107 /* set to TRUE to indicate thread should stop */
108 gboolean refresh_thread_stop;
109 /* condition for refreshing, not NULL when thread exists */
111 /* cond to know the refresh thread gone */
112 GCond *refresh_gone_cond;
113 /* increased when backend saves the file */
116 /* Monitor for a refresh type "1" */
117 GFileMonitor *refresh_monitor;
119 /* timeour id for refresh type "2" */
120 guint refresh_timeout_id;
122 /* Just an incremental number to ensure uniqueness across revisions */
123 guint revision_counter;
130 static void e_cal_backend_file_dispose (GObject *object);
131 static void e_cal_backend_file_finalize (GObject *object);
133 static void free_refresh_data (ECalBackendFile *cbfile);
135 static icaltimezone *
136 e_cal_backend_file_internal_get_timezone (ECalBackend *backend, const gchar *tzid);
138 static void bump_revision (ECalBackendFile *cbfile);
140 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
142 free_object_data (gpointer data)
144 ECalBackendFileObject *obj_data = data;
146 if (obj_data->full_object)
147 g_object_unref (obj_data->full_object);
148 g_hash_table_destroy (obj_data->recurrences);
149 g_list_free (obj_data->recurrences_list);
154 /* Saves the calendar data */
156 save_file_when_idle (gpointer user_data)
158 ECalBackendFilePrivate *priv;
160 GFile *file, *backup_file;
161 GFileOutputStream *stream;
163 gchar *tmp, *backup_uristr;
165 ECalBackendFile *cbfile = user_data;
168 g_assert (priv->path != NULL);
169 g_assert (priv->icalcomp != NULL);
171 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
172 if (!priv->is_dirty || priv->read_only) {
173 priv->dirty_idle_id = 0;
174 priv->is_dirty = FALSE;
175 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
179 file = g_file_new_for_path (priv->path);
181 goto error_malformed_uri;
183 /* save calendar to backup file */
184 tmp = g_file_get_uri (file);
186 g_object_unref (file);
187 goto error_malformed_uri;
190 backup_uristr = g_strconcat (tmp, "~", NULL);
191 backup_file = g_file_new_for_uri (backup_uristr);
194 g_free (backup_uristr);
197 g_object_unref (file);
198 goto error_malformed_uri;
201 priv->refresh_skip++;
202 stream = g_file_replace (backup_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &e);
205 g_object_unref (stream);
207 g_object_unref (file);
208 g_object_unref (backup_file);
209 priv->refresh_skip--;
213 buf = icalcomponent_as_ical_string_r (priv->icalcomp);
214 succeeded = g_output_stream_write_all (G_OUTPUT_STREAM (stream), buf, strlen (buf) * sizeof (gchar), NULL, NULL, &e);
217 if (!succeeded || e) {
218 g_object_unref (stream);
219 g_object_unref (file);
220 g_object_unref (backup_file);
224 succeeded = g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &e);
225 g_object_unref (stream);
227 if (!succeeded || e) {
228 g_object_unref (file);
229 g_object_unref (backup_file);
233 /* now copy the temporary file to the real file */
234 g_file_move (backup_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &e);
236 g_object_unref (file);
237 g_object_unref (backup_file);
241 priv->is_dirty = FALSE;
242 priv->dirty_idle_id = 0;
244 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
249 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
250 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
251 _("Cannot save calendar data: Malformed URI."));
255 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
258 gchar *msg = g_strdup_printf ("%s: %s", _("Cannot save calendar data"), e->message);
260 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), msg);
264 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), _("Cannot save calendar data"));
270 save (ECalBackendFile *cbfile,
271 gboolean do_bump_revision)
273 ECalBackendFilePrivate *priv;
275 if (do_bump_revision)
276 bump_revision (cbfile);
280 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
281 priv->is_dirty = TRUE;
283 if (!priv->dirty_idle_id)
284 priv->dirty_idle_id = g_idle_add ((GSourceFunc) save_file_when_idle, cbfile);
286 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
290 free_calendar_components (GHashTable *comp_uid_hash,
291 icalcomponent *top_icomp)
294 g_hash_table_destroy (comp_uid_hash);
297 icalcomponent_free (top_icomp);
301 free_calendar_data (ECalBackendFile *cbfile)
303 ECalBackendFilePrivate *priv;
307 e_intervaltree_destroy (priv->interval_tree);
308 priv->interval_tree = NULL;
310 free_calendar_components (priv->comp_uid_hash, priv->icalcomp);
311 priv->comp_uid_hash = NULL;
312 priv->icalcomp = NULL;
314 g_list_free (priv->comp);
318 /* Dispose handler for the file backend */
320 e_cal_backend_file_dispose (GObject *object)
322 ECalBackendFile *cbfile;
323 ECalBackendFilePrivate *priv;
326 cbfile = E_CAL_BACKEND_FILE (object);
329 /* Save if necessary */
331 save_file_when_idle (cbfile);
333 free_calendar_data (cbfile);
335 source = e_backend_get_source (E_BACKEND (cbfile));
337 g_signal_handlers_disconnect_matched (source, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, cbfile);
339 /* Chain up to parent's dispose() method. */
340 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->dispose (object);
343 /* Finalize handler for the file backend */
345 e_cal_backend_file_finalize (GObject *object)
347 ECalBackendFilePrivate *priv;
349 priv = E_CAL_BACKEND_FILE_GET_PRIVATE (object);
353 if (priv->dirty_idle_id)
354 g_source_remove (priv->dirty_idle_id);
356 free_refresh_data (E_CAL_BACKEND_FILE (object));
358 if (priv->refresh_lock)
359 g_mutex_free (priv->refresh_lock);
361 g_static_rec_mutex_free (&priv->idle_save_rmutex);
364 g_free (priv->custom_file);
365 g_free (priv->file_name);
367 /* Chain up to parent's finalize() method. */
368 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->finalize (object);
373 /* Looks up an component by its UID on the backend's component hash table
374 * and returns TRUE if any event (regardless whether it is the master or a child)
375 * with that UID exists */
377 uid_in_use (ECalBackendFile *cbfile,
380 ECalBackendFilePrivate *priv;
381 ECalBackendFileObject *obj_data;
385 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
386 return obj_data != NULL;
391 static icalproperty *
392 get_revision_property (ECalBackendFile *cbfile)
394 ECalBackendFilePrivate *priv;
398 prop = icalcomponent_get_first_property (priv->icalcomp, ICAL_X_PROPERTY);
400 while (prop != NULL) {
401 const gchar *name = icalproperty_get_x_name (prop);
403 if (name && strcmp (name, ECAL_REVISION_X_PROP) == 0)
406 prop = icalcomponent_get_next_property (priv->icalcomp, ICAL_X_PROPERTY);
413 make_revision_string (ECalBackendFile *cbfile)
419 g_get_current_time (&timeval);
421 datestr = g_time_val_to_iso8601 (&timeval);
422 revision = g_strdup_printf ("%s(%d)", datestr, cbfile->priv->revision_counter++);
428 static icalproperty *
429 ensure_revision (ECalBackendFile *cbfile)
433 prop = get_revision_property (cbfile);
436 gchar *revision = make_revision_string (cbfile);
438 prop = icalproperty_new (ICAL_X_PROPERTY);
440 icalproperty_set_x_name (prop, ECAL_REVISION_X_PROP);
441 icalproperty_set_x (prop, revision);
443 icalcomponent_add_property (cbfile->priv->icalcomp, prop);
452 bump_revision (ECalBackendFile *cbfile)
454 /* Update the revision string */
455 icalproperty *prop = ensure_revision (cbfile);
456 gchar *revision = make_revision_string (cbfile);
458 icalproperty_set_x (prop, revision);
460 e_cal_backend_notify_property_changed (E_CAL_BACKEND (cbfile),
461 CAL_BACKEND_PROPERTY_REVISION,
467 /* Calendar backend methods */
469 /* Get_email_address handler for the file backend */
471 e_cal_backend_file_get_backend_property (ECalBackendSync *backend,
473 GCancellable *cancellable,
474 const gchar *prop_name,
478 gboolean processed = TRUE;
480 g_return_val_if_fail (prop_name != NULL, FALSE);
481 g_return_val_if_fail (prop_value != NULL, FALSE);
483 if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
484 *prop_value = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS ","
485 CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
486 CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED ","
487 CAL_STATIC_CAPABILITY_REMOVE_ONLY_THIS ","
488 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
489 CAL_STATIC_CAPABILITY_BULK_ADDS ","
490 CAL_STATIC_CAPABILITY_BULK_MODIFIES ","
491 CAL_STATIC_CAPABILITY_BULK_REMOVES);
492 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
493 g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
494 /* A file backend has no particular email address associated
495 * with it (although that would be a useful feature some day).
498 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
501 comp = e_cal_component_new ();
503 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
504 case ICAL_VEVENT_COMPONENT:
505 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
507 case ICAL_VTODO_COMPONENT:
508 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
510 case ICAL_VJOURNAL_COMPONENT:
511 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
514 g_object_unref (comp);
515 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
519 *prop_value = e_cal_component_get_as_string (comp);
520 g_object_unref (comp);
521 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_REVISION)) {
523 const gchar *revision;
525 prop = ensure_revision (E_CAL_BACKEND_FILE (backend));
526 revision = icalproperty_get_x (prop);
528 *prop_value = g_strdup (revision);
536 /* function to resolve timezones */
537 static icaltimezone *
538 resolve_tzid (const gchar *tzid,
541 icalcomponent *vcalendar_comp = user_data;
544 if (!tzid || !tzid[0])
546 else if (!strcmp (tzid, "UTC"))
547 return icaltimezone_get_utc_timezone ();
549 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
552 zone = icalcomponent_get_timezone (vcalendar_comp, tzid);
557 /* Checks if the specified component has a duplicated UID and if so changes it.
558 * UIDs may be shared between components if there is at most one component
559 * without RECURRENCE-ID (master) and all others have different RECURRENCE-ID
563 check_dup_uid (ECalBackendFile *cbfile,
566 ECalBackendFilePrivate *priv;
567 ECalBackendFileObject *obj_data;
568 const gchar *uid = NULL;
569 gchar *new_uid = NULL;
574 e_cal_component_get_uid (comp, &uid);
577 g_warning ("Checking for duplicate uid, the component does not have a valid UID skipping it\n");
581 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
583 return; /* Everything is fine */
585 rid = e_cal_component_get_recurid_as_string (comp);
587 /* new component has rid, must not be the same as in other detached recurrence */
588 if (!g_hash_table_lookup (obj_data->recurrences, rid))
591 /* new component has no rid, must not clash with existing master */
592 if (!obj_data->full_object)
596 d(g_message (G_STRLOC ": Got object with duplicated UID `%s' and rid `%s', changing it...",
600 new_uid = e_cal_component_gen_uid ();
601 e_cal_component_set_uid (comp, new_uid);
603 /* FIXME: I think we need to reset the SEQUENCE property and reset the
604 * CREATED/DTSTAMP/LAST-MODIFIED.
607 save (cbfile, FALSE);
614 static struct icaltimetype
615 get_rid_icaltime (ECalComponent *comp)
617 ECalComponentRange range;
618 struct icaltimetype tt;
620 e_cal_component_get_recurid (comp, &range);
621 if (!range.datetime.value)
622 return icaltime_null_time ();
623 tt = *range.datetime.value;
624 e_cal_component_free_range (&range);
629 /* Adds component to the interval tree
632 add_component_to_intervaltree (ECalBackendFile *cbfile,
635 time_t time_start = -1, time_end = -1;
636 ECalBackendFilePrivate *priv;
638 g_return_val_if_fail (cbfile != NULL, FALSE);
639 g_return_val_if_fail (comp != NULL, FALSE);
643 e_cal_util_get_component_occur_times (comp, &time_start, &time_end,
644 resolve_tzid, priv->icalcomp, icaltimezone_get_utc_timezone (),
645 e_cal_backend_get_kind (E_CAL_BACKEND (cbfile)));
647 if (time_end != -1 && time_start > time_end)
648 g_print ("Bogus component %s\n", e_cal_component_get_as_string (comp));
650 e_intervaltree_insert (priv->interval_tree, time_start, time_end, comp);
656 remove_component_from_intervaltree (ECalBackendFile *cbfile,
659 const gchar *uid = NULL;
662 ECalBackendFilePrivate *priv;
664 g_return_val_if_fail (cbfile != NULL, FALSE);
665 g_return_val_if_fail (comp != NULL, FALSE);
669 rid = e_cal_component_get_recurid_as_string (comp);
670 e_cal_component_get_uid (comp, &uid);
671 res = e_intervaltree_remove (priv->interval_tree, uid, rid);
677 /* Tries to add an icalcomponent to the file backend. We only store the objects
678 * of the types we support; all others just remain in the toplevel component so
679 * that we don't lose them.
681 * The caller is responsible for ensuring that the component has a UID and that
682 * the UID is not in use already.
685 add_component (ECalBackendFile *cbfile,
687 gboolean add_to_toplevel)
689 ECalBackendFilePrivate *priv;
690 ECalBackendFileObject *obj_data;
691 const gchar *uid = NULL;
695 e_cal_component_get_uid (comp, &uid);
698 g_warning ("The component does not have a valid UID skipping it\n");
702 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
703 if (e_cal_component_is_instance (comp)) {
706 rid = e_cal_component_get_recurid_as_string (comp);
708 if (g_hash_table_lookup (obj_data->recurrences, rid)) {
709 g_warning (G_STRLOC ": Tried to add an already existing recurrence");
714 obj_data = g_new0 (ECalBackendFileObject, 1);
715 obj_data->full_object = NULL;
716 obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
717 g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
720 add_component_to_intervaltree (cbfile, comp);
721 g_hash_table_insert (obj_data->recurrences, rid, comp);
722 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
725 if (obj_data->full_object) {
726 g_warning (G_STRLOC ": Tried to add an already existing object");
730 obj_data->full_object = comp;
732 obj_data = g_new0 (ECalBackendFileObject, 1);
733 obj_data->full_object = comp;
734 obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
736 g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
737 add_component_to_intervaltree (cbfile, comp);
741 priv->comp = g_list_prepend (priv->comp, comp);
743 /* Put the object in the toplevel component if required */
745 if (add_to_toplevel) {
746 icalcomponent *icalcomp;
748 icalcomp = e_cal_component_get_icalcomponent (comp);
749 g_assert (icalcomp != NULL);
751 icalcomponent_add_component (priv->icalcomp, icalcomp);
755 /* g_hash_table_foreach_remove() callback to remove recurrences from the calendar */
757 remove_recurrence_cb (gpointer key,
762 icalcomponent *icalcomp;
763 ECalBackendFilePrivate *priv;
764 ECalComponent *comp = value;
765 ECalBackendFile *cbfile = data;
769 /* remove the recurrence from the top-level calendar */
770 icalcomp = e_cal_component_get_icalcomponent (comp);
771 g_assert (icalcomp != NULL);
773 if (!remove_component_from_intervaltree (cbfile, comp)) {
774 g_message (G_STRLOC " Could not remove component from interval tree!");
776 icalcomponent_remove_component (priv->icalcomp, icalcomp);
778 /* remove it from our mapping */
779 l = g_list_find (priv->comp, comp);
780 priv->comp = g_list_delete_link (priv->comp, l);
785 /* Removes a component from the backend's hash and lists. Does not perform
786 * notification on the clients. Also removes the component from the toplevel
790 remove_component (ECalBackendFile *cbfile,
792 ECalBackendFileObject *obj_data)
794 ECalBackendFilePrivate *priv;
795 icalcomponent *icalcomp;
800 /* Remove the icalcomp from the toplevel */
801 if (obj_data->full_object) {
802 icalcomp = e_cal_component_get_icalcomponent (obj_data->full_object);
803 g_assert (icalcomp != NULL);
805 icalcomponent_remove_component (priv->icalcomp, icalcomp);
807 /* Remove it from our mapping */
808 l = g_list_find (priv->comp, obj_data->full_object);
809 g_assert (l != NULL);
810 priv->comp = g_list_delete_link (priv->comp, l);
812 if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) {
813 g_message (G_STRLOC " Could not remove component from interval tree!");
817 /* remove the recurrences also */
818 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_recurrence_cb, cbfile);
820 g_hash_table_remove (priv->comp_uid_hash, uid);
825 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
827 scan_vcalendar (ECalBackendFile *cbfile)
829 ECalBackendFilePrivate *priv;
833 g_assert (priv->icalcomp != NULL);
834 g_assert (priv->comp_uid_hash != NULL);
836 for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_ANY_COMPONENT);
837 icalcompiter_deref (&iter) != NULL;
838 icalcompiter_next (&iter)) {
839 icalcomponent *icalcomp;
840 icalcomponent_kind kind;
843 icalcomp = icalcompiter_deref (&iter);
845 kind = icalcomponent_isa (icalcomp);
847 if (!(kind == ICAL_VEVENT_COMPONENT
848 || kind == ICAL_VTODO_COMPONENT
849 || kind == ICAL_VJOURNAL_COMPONENT))
852 comp = e_cal_component_new ();
854 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
857 check_dup_uid (cbfile, comp);
859 add_component (cbfile, comp, FALSE);
864 uri_to_path (ECalBackend *backend)
866 ECalBackendFile *cbfile;
867 ECalBackendFilePrivate *priv;
869 const gchar *cache_dir;
870 gchar *filename = NULL;
872 cbfile = E_CAL_BACKEND_FILE (backend);
875 cache_dir = e_cal_backend_get_cache_dir (backend);
877 source = e_backend_get_source (E_BACKEND (backend));
878 if (source && e_source_get_property (source, "custom-file")) {
879 const gchar *property;
881 /* custom-uri is with a filename already */
882 property = e_source_get_property (source, "custom-file");
883 filename = g_strdup (property);
886 if (filename == NULL)
887 filename = g_build_filename (cache_dir, priv->file_name, NULL);
889 if (filename != NULL && *filename == '\0') {
898 refresh_thread_func (gpointer data)
900 ECalBackendFile *cbfile = data;
901 ECalBackendFilePrivate *priv;
904 guint64 last_modified, modified;
906 g_return_val_if_fail (cbfile != NULL, NULL);
907 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
910 g_return_val_if_fail (priv->custom_file != NULL, NULL);
912 file = g_file_new_for_path (priv->custom_file);
913 info = g_file_query_info (
914 file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
915 G_FILE_QUERY_INFO_NONE, NULL, NULL);
916 g_return_val_if_fail (info != NULL, NULL);
918 last_modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
919 g_object_unref (info);
921 g_mutex_lock (priv->refresh_lock);
922 while (!priv->refresh_thread_stop) {
923 g_cond_wait (priv->refresh_cond, priv->refresh_lock);
925 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
927 if (priv->refresh_skip > 0) {
928 priv->refresh_skip--;
929 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
933 if (priv->is_dirty) {
934 /* save before reload, if dirty */
935 if (priv->dirty_idle_id) {
936 g_source_remove (priv->dirty_idle_id);
937 priv->dirty_idle_id = 0;
939 save_file_when_idle (cbfile);
940 priv->refresh_skip = 0;
943 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
945 info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL);
949 modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
950 g_object_unref (info);
952 if (modified != last_modified) {
953 last_modified = modified;
954 e_cal_backend_file_reload (cbfile, NULL);
958 g_object_unref (file);
959 g_cond_signal (priv->refresh_gone_cond);
960 g_mutex_unlock (priv->refresh_lock);
966 check_refresh_calendar_timeout (ECalBackendFilePrivate *priv)
968 g_return_val_if_fail (priv != NULL, FALSE);
970 /* called in the main thread */
971 if (priv->refresh_cond)
972 g_cond_signal (priv->refresh_cond);
974 /* call it next time again */
979 custom_file_changed (GFileMonitor *monitor,
982 GFileMonitorEvent event_type,
983 ECalBackendFilePrivate *priv)
985 if (priv->refresh_cond)
986 g_cond_signal (priv->refresh_cond);
990 prepare_refresh_data (ECalBackendFile *cbfile)
992 ECalBackendFilePrivate *priv;
996 g_return_if_fail (cbfile != NULL);
1000 g_mutex_lock (priv->refresh_lock);
1002 priv->refresh_thread_stop = FALSE;
1003 priv->refresh_skip = 0;
1005 source = e_backend_get_source (E_BACKEND (cbfile));
1006 value = e_source_get_property (source, "refresh-type");
1007 if (e_source_get_property (source, "custom-file") && value && *value && !value[1]) {
1009 GError *error = NULL;
1012 case '1': /* on file change */
1013 file = g_file_new_for_path (priv->custom_file);
1014 priv->refresh_monitor = g_file_monitor_file (file, G_FILE_MONITOR_WATCH_MOUNTS, NULL, &error);
1016 g_object_unref (file);
1017 if (priv->refresh_monitor)
1018 g_signal_connect (G_OBJECT (priv->refresh_monitor), "changed", G_CALLBACK (custom_file_changed), priv);
1020 case '2': /* on refresh timeout */
1021 value = e_source_get_property (source, "refresh");
1022 if (value && atoi (value) > 0) {
1023 priv->refresh_timeout_id = g_timeout_add_seconds (60 * atoi (value), (GSourceFunc) check_refresh_calendar_timeout, priv);
1031 if (priv->refresh_monitor || priv->refresh_timeout_id) {
1032 priv->refresh_cond = g_cond_new ();
1033 priv->refresh_gone_cond = g_cond_new ();
1035 g_thread_create (refresh_thread_func, cbfile, FALSE, NULL);
1038 g_mutex_unlock (priv->refresh_lock);
1042 free_refresh_data (ECalBackendFile *cbfile)
1044 ECalBackendFilePrivate *priv;
1046 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
1048 priv = cbfile->priv;
1050 g_mutex_lock (priv->refresh_lock);
1052 if (priv->refresh_monitor)
1053 g_object_unref (priv->refresh_monitor);
1054 priv->refresh_monitor = NULL;
1056 if (priv->refresh_timeout_id)
1057 g_source_remove (priv->refresh_timeout_id);
1058 priv->refresh_timeout_id = 0;
1060 if (priv->refresh_cond) {
1061 priv->refresh_thread_stop = TRUE;
1062 g_cond_signal (priv->refresh_cond);
1063 g_cond_wait (priv->refresh_gone_cond, priv->refresh_lock);
1065 g_cond_free (priv->refresh_cond);
1066 priv->refresh_cond = NULL;
1067 g_cond_free (priv->refresh_gone_cond);
1068 priv->refresh_gone_cond = NULL;
1071 priv->refresh_skip = 0;
1073 g_mutex_unlock (priv->refresh_lock);
1076 /* Parses an open iCalendar file and loads it into the backend */
1078 open_cal (ECalBackendFile *cbfile,
1079 const gchar *uristr,
1082 ECalBackendFilePrivate *priv;
1083 icalcomponent *icalcomp;
1085 priv = cbfile->priv;
1087 free_refresh_data (cbfile);
1089 icalcomp = e_cal_util_parse_ics_file (uristr);
1091 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, "Cannot parse ISC file '%s'", uristr));
1095 /* FIXME: should we try to demangle XROOT components and
1096 * individual components as well?
1099 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
1100 icalcomponent_free (icalcomp);
1102 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, "File '%s' is not v VCALENDAR component", uristr));
1106 priv->icalcomp = icalcomp;
1107 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1108 g_free (priv->custom_file);
1109 priv->custom_file = g_strdup (uristr);
1111 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1112 priv->interval_tree = e_intervaltree_new ();
1113 scan_vcalendar (cbfile);
1115 prepare_refresh_data (cbfile);
1120 ECalBackend *backend;
1121 GHashTable *old_uid_hash;
1122 GHashTable *new_uid_hash;
1124 BackendDeltaContext;
1127 notify_removals_cb (gpointer key,
1131 BackendDeltaContext *context = data;
1132 const gchar *uid = key;
1133 ECalBackendFileObject *old_obj_data = value;
1135 if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
1136 ECalComponentId *id;
1138 /* Object was removed */
1140 if (!old_obj_data->full_object)
1143 id = e_cal_component_get_id (old_obj_data->full_object);
1145 e_cal_backend_notify_component_removed (context->backend, id, old_obj_data->full_object, NULL);
1147 e_cal_component_free_id (id);
1152 notify_adds_modifies_cb (gpointer key,
1156 BackendDeltaContext *context = data;
1157 const gchar *uid = key;
1158 ECalBackendFileObject *new_obj_data = value;
1159 ECalBackendFileObject *old_obj_data;
1161 old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
1163 if (!old_obj_data) {
1164 /* Object was added */
1165 if (!new_obj_data->full_object)
1168 e_cal_backend_notify_component_created (context->backend, new_obj_data->full_object);
1170 gchar *old_obj_str, *new_obj_str;
1172 if (!old_obj_data->full_object || !new_obj_data->full_object)
1175 /* There should be better ways to compare an icalcomponent
1176 * than serializing and comparing the strings...
1178 old_obj_str = e_cal_component_get_as_string (old_obj_data->full_object);
1179 new_obj_str = e_cal_component_get_as_string (new_obj_data->full_object);
1180 if (old_obj_str && new_obj_str && strcmp (old_obj_str, new_obj_str) != 0) {
1181 /* Object was modified */
1182 e_cal_backend_notify_component_modified (context->backend, old_obj_data->full_object, new_obj_data->full_object);
1185 g_free (old_obj_str);
1186 g_free (new_obj_str);
1191 notify_changes (ECalBackendFile *cbfile,
1192 GHashTable *old_uid_hash,
1193 GHashTable *new_uid_hash)
1195 BackendDeltaContext context;
1197 context.backend = E_CAL_BACKEND (cbfile);
1198 context.old_uid_hash = old_uid_hash;
1199 context.new_uid_hash = new_uid_hash;
1201 g_hash_table_foreach (old_uid_hash, (GHFunc) notify_removals_cb, &context);
1202 g_hash_table_foreach (new_uid_hash, (GHFunc) notify_adds_modifies_cb, &context);
1206 reload_cal (ECalBackendFile *cbfile,
1207 const gchar *uristr,
1210 ECalBackendFilePrivate *priv;
1211 icalcomponent *icalcomp, *icalcomp_old;
1212 GHashTable *comp_uid_hash_old;
1214 priv = cbfile->priv;
1216 icalcomp = e_cal_util_parse_ics_file (uristr);
1218 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, "Cannot parse ISC file '%s'", uristr));
1222 /* FIXME: should we try to demangle XROOT components and
1223 * individual components as well?
1226 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
1227 icalcomponent_free (icalcomp);
1229 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, "File '%s' is not v VCALENDAR component", uristr));
1233 /* Keep old data for comparison - free later */
1235 icalcomp_old = priv->icalcomp;
1236 priv->icalcomp = NULL;
1238 comp_uid_hash_old = priv->comp_uid_hash;
1239 priv->comp_uid_hash = NULL;
1241 /* Load new calendar */
1243 free_calendar_data (cbfile);
1245 priv->icalcomp = icalcomp;
1247 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1248 priv->interval_tree = e_intervaltree_new ();
1249 scan_vcalendar (cbfile);
1251 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1253 /* Compare old and new versions of calendar */
1255 notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
1259 free_calendar_components (comp_uid_hash_old, icalcomp_old);
1263 create_cal (ECalBackendFile *cbfile,
1264 const gchar *uristr,
1268 ECalBackendFilePrivate *priv;
1270 free_refresh_data (cbfile);
1272 priv = cbfile->priv;
1274 /* Create the directory to contain the file */
1275 dirname = g_path_get_dirname (uristr);
1276 if (g_mkdir_with_parents (dirname, 0700) != 0) {
1278 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1284 /* Create the new calendar information */
1285 priv->icalcomp = e_cal_util_new_top_level ();
1287 /* Create our internal data */
1288 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1289 priv->interval_tree = e_intervaltree_new ();
1291 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1293 save (cbfile, TRUE);
1295 g_free (priv->custom_file);
1296 priv->custom_file = g_strdup (uristr);
1297 prepare_refresh_data (cbfile);
1301 get_uri_string (ECalBackend *backend)
1303 gchar *str_uri, *full_uri;
1305 str_uri = uri_to_path (backend);
1306 full_uri = g_uri_unescape_string (str_uri, "");
1313 source_changed_cb (ESource *source,
1314 ECalBackend *backend)
1318 g_return_if_fail (source != NULL);
1319 g_return_if_fail (backend != NULL);
1320 g_return_if_fail (E_IS_CAL_BACKEND (backend));
1322 value = e_source_get_property (source, "custom-file");
1323 if (value && *value) {
1324 ECalBackendFile *cbfile;
1325 gboolean forced_readonly;
1327 cbfile = E_CAL_BACKEND_FILE (backend);
1328 g_return_if_fail (cbfile != NULL);
1330 value = e_source_get_property (source, "custom-file-readonly");
1331 forced_readonly = value && g_str_equal (value, "1");
1333 if ((forced_readonly != FALSE) != (cbfile->priv->read_only != FALSE)) {
1334 cbfile->priv->read_only = forced_readonly;
1335 if (!forced_readonly) {
1336 gchar *str_uri = get_uri_string (backend);
1338 g_return_if_fail (str_uri != NULL);
1340 cbfile->priv->read_only = g_access (str_uri, W_OK) != 0;
1345 e_cal_backend_notify_readonly (backend, cbfile->priv->read_only);
1350 /* Open handler for the file backend */
1352 e_cal_backend_file_open (ECalBackendSync *backend,
1354 GCancellable *cancellable,
1355 gboolean only_if_exists,
1358 ECalBackendFile *cbfile;
1359 ECalBackendFilePrivate *priv;
1363 cbfile = E_CAL_BACKEND_FILE (backend);
1364 priv = cbfile->priv;
1365 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1367 /* Claim a succesful open if we are already open */
1368 if (priv->path && priv->comp_uid_hash) {
1373 str_uri = get_uri_string (E_CAL_BACKEND (backend));
1375 err = EDC_ERROR_NO_URI ();
1379 priv->read_only = FALSE;
1380 if (g_access (str_uri, R_OK) == 0) {
1381 open_cal (cbfile, str_uri, &err);
1382 if (g_access (str_uri, W_OK) != 0)
1383 priv->read_only = TRUE;
1386 err = EDC_ERROR (NoSuchCal);
1388 create_cal (cbfile, str_uri, &err);
1392 if (!priv->read_only) {
1395 source = e_backend_get_source (E_BACKEND (backend));
1399 G_CALLBACK (source_changed_cb), backend);
1401 if (e_source_get_property (source, "custom-file-readonly") && g_str_equal (e_source_get_property (source, "custom-file-readonly"), "1"))
1402 priv->read_only = TRUE;
1409 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1410 e_cal_backend_notify_readonly (E_CAL_BACKEND (backend), priv->read_only);
1411 e_cal_backend_notify_online (E_CAL_BACKEND (backend), TRUE);
1414 g_propagate_error (perror, g_error_copy (err));
1416 e_cal_backend_notify_opened (E_CAL_BACKEND (backend), err);
1420 e_cal_backend_file_remove (ECalBackendSync *backend,
1422 GCancellable *cancellable,
1425 ECalBackendFile *cbfile;
1426 ECalBackendFilePrivate *priv;
1428 gchar *str_uri = NULL, *dirname = NULL;
1429 gchar *full_path = NULL;
1432 GError *local_error = NULL;
1435 cbfile = E_CAL_BACKEND_FILE (backend);
1436 priv = cbfile->priv;
1437 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1439 str_uri = get_uri_string (E_CAL_BACKEND (backend));
1441 err = EDC_ERROR_NO_URI ();
1445 source = e_backend_get_source (E_BACKEND (backend));
1446 if (!source || e_source_get_property (source, "custom-file")) {
1447 /* skip file and directory removal for custom calendars */
1451 if (g_access (str_uri, W_OK) != 0) {
1452 err = EDC_ERROR (PermissionDenied);
1456 /* remove all files in the directory */
1457 dirname = g_path_get_dirname (str_uri);
1458 dir = g_dir_open (dirname, 0, &local_error);
1460 err = e_data_cal_create_error (
1461 PermissionDenied, local_error->message);
1465 while ((fname = g_dir_read_name (dir))) {
1466 full_path = g_build_filename (dirname, fname, NULL);
1467 if (g_unlink (full_path) != 0) {
1468 err = EDC_ERROR (OtherError);
1481 /* remove the directory itself */
1482 if (g_rmdir (dirname) != 0) {
1483 err = EDC_ERROR (OtherError);
1492 /* lie here a bit, but otherwise the calendar will not be removed, even it should */
1494 g_print (G_STRLOC ": %s on dir '%s' from uri '%s'\n", err->message, dirname, str_uri);
1502 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1505 g_error_free (local_error);
1509 add_detached_recur_to_vcalendar (gpointer key,
1513 ECalComponent *recurrence = value;
1514 icalcomponent *vcalendar = user_data;
1516 icalcomponent_add_component (
1518 icalcomponent_new_clone (e_cal_component_get_icalcomponent (recurrence)));
1521 /* Get_object_component handler for the file backend */
1523 e_cal_backend_file_get_object (ECalBackendSync *backend,
1525 GCancellable *cancellable,
1531 ECalBackendFile *cbfile;
1532 ECalBackendFilePrivate *priv;
1533 ECalBackendFileObject *obj_data;
1535 cbfile = E_CAL_BACKEND_FILE (backend);
1536 priv = cbfile->priv;
1538 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, InvalidObject);
1539 e_return_data_cal_error_if_fail (uid != NULL, ObjectNotFound);
1540 g_assert (priv->comp_uid_hash != NULL);
1542 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1544 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1546 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1547 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1552 ECalComponent *comp;
1554 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1556 *object = e_cal_component_get_as_string (comp);
1558 icalcomponent *icalcomp;
1559 struct icaltimetype itt;
1561 if (!obj_data->full_object) {
1562 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1563 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1567 itt = icaltime_from_string (rid);
1568 icalcomp = e_cal_util_construct_instance (
1569 e_cal_component_get_icalcomponent (obj_data->full_object),
1572 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1573 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1577 *object = icalcomponent_as_ical_string_r (icalcomp);
1579 icalcomponent_free (icalcomp);
1582 if (g_hash_table_size (obj_data->recurrences) > 0) {
1583 icalcomponent *icalcomp;
1585 /* if we have detached recurrences, return a VCALENDAR */
1586 icalcomp = e_cal_util_new_top_level ();
1588 /* detached recurrences don't have full_object */
1589 if (obj_data->full_object)
1590 icalcomponent_add_component (
1592 icalcomponent_new_clone (e_cal_component_get_icalcomponent (obj_data->full_object)));
1594 /* add all detached recurrences */
1595 g_hash_table_foreach (obj_data->recurrences, (GHFunc) add_detached_recur_to_vcalendar, icalcomp);
1597 *object = icalcomponent_as_ical_string_r (icalcomp);
1599 icalcomponent_free (icalcomp);
1600 } else if (obj_data->full_object)
1601 *object = e_cal_component_get_as_string (obj_data->full_object);
1604 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1607 /* Add_timezone handler for the file backend */
1609 e_cal_backend_file_add_timezone (ECalBackendSync *backend,
1611 GCancellable *cancellable,
1615 icalcomponent *tz_comp;
1616 ECalBackendFile *cbfile;
1617 ECalBackendFilePrivate *priv;
1619 cbfile = (ECalBackendFile *) backend;
1621 e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), InvalidArg);
1622 e_return_data_cal_error_if_fail (tzobj != NULL, InvalidArg);
1624 priv = cbfile->priv;
1626 tz_comp = icalparser_parse_string (tzobj);
1628 g_propagate_error (error, EDC_ERROR (InvalidObject));
1632 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
1635 zone = icaltimezone_new ();
1636 icaltimezone_set_component (zone, tz_comp);
1638 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1639 if (!icalcomponent_get_timezone (priv->icalcomp,
1640 icaltimezone_get_tzid (zone))) {
1641 icalcomponent_add_component (priv->icalcomp, tz_comp);
1643 save (cbfile, TRUE);
1645 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1647 icaltimezone_free (zone, 1);
1653 gboolean search_needed;
1655 ECalBackendSExp *obj_sexp;
1656 ECalBackend *backend;
1662 match_object_sexp_to_component (gpointer value,
1665 ECalComponent * comp = value;
1666 MatchObjectData *match_data = data;
1669 e_cal_component_get_uid (comp, &uid);
1671 g_return_if_fail (comp != NULL);
1673 g_return_if_fail (match_data->backend != NULL);
1675 if ((!match_data->search_needed) ||
1676 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
1677 if (match_data->as_string)
1678 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (comp));
1680 match_data->comps_list = g_slist_prepend (match_data->comps_list, comp);
1685 match_recurrence_sexp (gpointer key,
1689 ECalComponent *comp = value;
1690 MatchObjectData *match_data = data;
1692 if ((!match_data->search_needed) ||
1693 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
1694 if (match_data->as_string)
1695 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (comp));
1697 match_data->comps_list = g_slist_prepend (match_data->comps_list, comp);
1702 match_object_sexp (gpointer key,
1706 ECalBackendFileObject *obj_data = value;
1707 MatchObjectData *match_data = data;
1709 if (obj_data->full_object) {
1710 if ((!match_data->search_needed) ||
1711 (e_cal_backend_sexp_match_comp (match_data->obj_sexp,
1712 obj_data->full_object,
1713 match_data->backend))) {
1714 if (match_data->as_string)
1715 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (obj_data->full_object));
1717 match_data->comps_list = g_slist_prepend (match_data->comps_list, obj_data->full_object);
1721 /* match also recurrences */
1722 g_hash_table_foreach (obj_data->recurrences,
1723 (GHFunc) match_recurrence_sexp,
1727 /* Get_objects_in_range handler for the file backend */
1729 e_cal_backend_file_get_object_list (ECalBackendSync *backend,
1731 GCancellable *cancellable,
1736 ECalBackendFile *cbfile;
1737 ECalBackendFilePrivate *priv;
1738 MatchObjectData match_data = { 0, };
1739 time_t occur_start = -1, occur_end = -1;
1740 gboolean prunning_by_time;
1741 GList * objs_occuring_in_tw;
1742 cbfile = E_CAL_BACKEND_FILE (backend);
1743 priv = cbfile->priv;
1745 d(g_message (G_STRLOC ": Getting object list (%s)", sexp));
1747 match_data.search_needed = TRUE;
1748 match_data.query = sexp;
1749 match_data.comps_list = NULL;
1750 match_data.as_string = TRUE;
1751 match_data.backend = E_CAL_BACKEND (backend);
1753 if (!strcmp (sexp, "#t"))
1754 match_data.search_needed = FALSE;
1756 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1757 if (!match_data.obj_sexp) {
1758 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
1762 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1764 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (match_data.obj_sexp,
1768 objs_occuring_in_tw = NULL;
1770 if (!prunning_by_time) {
1771 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
1774 objs_occuring_in_tw = e_intervaltree_search (priv->interval_tree,
1775 occur_start, occur_end);
1777 g_list_foreach (objs_occuring_in_tw, (GFunc) match_object_sexp_to_component,
1781 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1783 *objects = g_slist_reverse (match_data.comps_list);
1785 if (objs_occuring_in_tw) {
1786 g_list_foreach (objs_occuring_in_tw, (GFunc) g_object_unref, NULL);
1787 g_list_free (objs_occuring_in_tw);
1790 g_object_unref (match_data.obj_sexp);
1794 add_attach_uris (GSList **attachment_uris,
1795 icalcomponent *icalcomp)
1799 g_return_if_fail (attachment_uris != NULL);
1800 g_return_if_fail (icalcomp != NULL);
1802 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
1804 prop = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
1805 icalattach *attach = icalproperty_get_attach (prop);
1807 if (attach && icalattach_get_is_url (attach)) {
1810 url = icalattach_get_url (attach);
1815 buf_size = strlen (url);
1816 buf = g_malloc0 (buf_size + 1);
1818 icalvalue_decode_ical_string (url, buf, buf_size);
1820 *attachment_uris = g_slist_prepend (*attachment_uris, g_strdup (buf));
1829 add_detached_recur_attach_uris (gpointer key,
1833 ECalComponent *recurrence = value;
1834 GSList **attachment_uris = user_data;
1836 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (recurrence));
1839 /* Gets the list of attachments */
1841 e_cal_backend_file_get_attachment_uris (ECalBackendSync *backend,
1843 GCancellable *cancellable,
1846 GSList **attachment_uris,
1849 ECalBackendFile *cbfile;
1850 ECalBackendFilePrivate *priv;
1851 ECalBackendFileObject *obj_data;
1853 cbfile = E_CAL_BACKEND_FILE (backend);
1854 priv = cbfile->priv;
1856 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, InvalidObject);
1857 e_return_data_cal_error_if_fail (uid != NULL, ObjectNotFound);
1858 e_return_data_cal_error_if_fail (attachment_uris != NULL, InvalidArg);
1859 g_assert (priv->comp_uid_hash != NULL);
1861 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1863 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1865 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1866 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1871 ECalComponent *comp;
1873 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1875 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (comp));
1877 icalcomponent *icalcomp;
1878 struct icaltimetype itt;
1880 if (!obj_data->full_object) {
1881 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1882 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1886 itt = icaltime_from_string (rid);
1887 icalcomp = e_cal_util_construct_instance (
1888 e_cal_component_get_icalcomponent (obj_data->full_object),
1891 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1892 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1896 add_attach_uris (attachment_uris, icalcomp);
1898 icalcomponent_free (icalcomp);
1901 if (g_hash_table_size (obj_data->recurrences) > 0) {
1902 /* detached recurrences don't have full_object */
1903 if (obj_data->full_object)
1904 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (obj_data->full_object));
1906 /* add all detached recurrences */
1907 g_hash_table_foreach (obj_data->recurrences, add_detached_recur_attach_uris, attachment_uris);
1908 } else if (obj_data->full_object)
1909 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (obj_data->full_object));
1912 *attachment_uris = g_slist_reverse (*attachment_uris);
1914 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1917 /* get_query handler for the file backend */
1919 e_cal_backend_file_start_view (ECalBackend *backend,
1920 EDataCalView *query)
1922 ECalBackendFile *cbfile;
1923 ECalBackendFilePrivate *priv;
1924 MatchObjectData match_data = { 0, };
1925 time_t occur_start = -1, occur_end = -1;
1926 gboolean prunning_by_time;
1927 GList * objs_occuring_in_tw;
1928 cbfile = E_CAL_BACKEND_FILE (backend);
1929 priv = cbfile->priv;
1931 d(g_message (G_STRLOC ": Starting query (%s)", e_data_cal_view_get_text (query)));
1933 /* try to match all currently existing objects */
1934 match_data.search_needed = TRUE;
1935 match_data.query = e_data_cal_view_get_text (query);
1936 match_data.comps_list = NULL;
1937 match_data.as_string = FALSE;
1938 match_data.backend = backend;
1939 match_data.obj_sexp = e_data_cal_view_get_object_sexp (query);
1940 match_data.view = query;
1942 if (!strcmp (match_data.query, "#t"))
1943 match_data.search_needed = FALSE;
1945 if (!match_data.obj_sexp) {
1946 GError *error = EDC_ERROR (InvalidQuery);
1947 e_data_cal_view_notify_complete (query, error);
1948 g_error_free (error);
1951 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (match_data.obj_sexp,
1955 objs_occuring_in_tw = NULL;
1957 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
1959 if (!prunning_by_time) {
1961 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
1964 e_debug_log(FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES, "---;%p;QUERY-ITEMS;%s;%s;%d", query,
1965 e_data_cal_view_get_text (query), G_OBJECT_TYPE_NAME (backend),
1966 g_hash_table_size (priv->comp_uid_hash));
1968 /* matches objects in new "interval tree" way */
1969 /* events occuring in time window */
1970 objs_occuring_in_tw = e_intervaltree_search (priv->interval_tree, occur_start, occur_end);
1972 g_list_foreach (objs_occuring_in_tw, (GFunc) match_object_sexp_to_component,
1975 e_debug_log(FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES, "---;%p;QUERY-ITEMS;%s;%s;%d", query,
1976 e_data_cal_view_get_text (query), G_OBJECT_TYPE_NAME (backend),
1977 g_list_length (objs_occuring_in_tw));
1980 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
1982 /* notify listeners of all objects */
1983 if (match_data.comps_list) {
1984 match_data.comps_list = g_slist_reverse (match_data.comps_list);
1986 e_data_cal_view_notify_components_added (query, match_data.comps_list);
1989 g_slist_free (match_data.comps_list);
1992 if (objs_occuring_in_tw) {
1993 g_list_foreach (objs_occuring_in_tw, (GFunc) g_object_unref, NULL);
1994 g_list_free (objs_occuring_in_tw);
1997 e_data_cal_view_notify_complete (query, NULL /* Success */);
2001 free_busy_instance (ECalComponent *comp,
2002 time_t instance_start,
2003 time_t instance_end,
2006 icalcomponent *vfb = data;
2008 icalparameter *param;
2009 struct icalperiodtype ipt;
2010 icaltimezone *utc_zone;
2012 utc_zone = icaltimezone_get_utc_timezone ();
2014 ipt.start = icaltime_from_timet_with_zone (instance_start, FALSE, utc_zone);
2015 ipt.end = icaltime_from_timet_with_zone (instance_end, FALSE, utc_zone);
2016 ipt.duration = icaldurationtype_null_duration ();
2018 /* add busy information to the vfb component */
2019 prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
2020 icalproperty_set_freebusy (prop, ipt);
2022 param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
2023 icalproperty_add_parameter (prop, param);
2025 icalcomponent_add_property (vfb, prop);
2030 static icalcomponent *
2031 create_user_free_busy (ECalBackendFile *cbfile,
2032 const gchar *address,
2037 ECalBackendFilePrivate *priv;
2040 icaltimezone *utc_zone;
2041 ECalBackendSExp *obj_sexp;
2042 gchar *query, *iso_start, *iso_end;
2044 priv = cbfile->priv;
2046 /* create the (unique) VFREEBUSY object that we'll return */
2047 vfb = icalcomponent_new_vfreebusy ();
2048 if (address != NULL) {
2050 icalparameter *param;
2052 prop = icalproperty_new_organizer (address);
2053 if (prop != NULL && cn != NULL) {
2054 param = icalparameter_new_cn (cn);
2055 icalproperty_add_parameter (prop, param);
2058 icalcomponent_add_property (vfb, prop);
2060 utc_zone = icaltimezone_get_utc_timezone ();
2061 icalcomponent_set_dtstart (vfb, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
2062 icalcomponent_set_dtend (vfb, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
2064 /* add all objects in the given interval */
2065 iso_start = isodate_from_time_t (start);
2066 iso_end = isodate_from_time_t (end);
2067 query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") (make-time \"%s\")",
2068 iso_start, iso_end);
2069 obj_sexp = e_cal_backend_sexp_new (query);
2077 for (l = priv->comp; l; l = l->next) {
2078 ECalComponent *comp = l->data;
2079 icalcomponent *icalcomp, *vcalendar_comp;
2082 icalcomp = e_cal_component_get_icalcomponent (comp);
2086 /* If the event is TRANSPARENT, skip it. */
2087 prop = icalcomponent_get_first_property (icalcomp,
2088 ICAL_TRANSP_PROPERTY);
2090 icalproperty_transp transp_val = icalproperty_get_transp (prop);
2091 if (transp_val == ICAL_TRANSP_TRANSPARENT ||
2092 transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
2096 if (!e_cal_backend_sexp_match_comp (obj_sexp, l->data, E_CAL_BACKEND (cbfile)))
2099 vcalendar_comp = icalcomponent_get_parent (icalcomp);
2100 e_cal_recur_generate_instances (comp, start, end,
2105 icaltimezone_get_utc_timezone ());
2107 g_object_unref (obj_sexp);
2112 /* Get_free_busy handler for the file backend */
2114 e_cal_backend_file_get_free_busy (ECalBackendSync *backend,
2116 GCancellable *cancellable,
2117 const GSList *users,
2123 ECalBackendFile *cbfile;
2124 ECalBackendFilePrivate *priv;
2125 gchar *address, *name;
2130 cbfile = E_CAL_BACKEND_FILE (backend);
2131 priv = cbfile->priv;
2133 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
2134 e_return_data_cal_error_if_fail (start != -1 && end != -1, InvalidRange);
2135 e_return_data_cal_error_if_fail (start <= end, InvalidRange);
2137 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2141 if (users == NULL) {
2142 if (e_cal_backend_mail_account_get_default (&address, &name)) {
2143 vfb = create_user_free_busy (cbfile, address, name, start, end);
2144 calobj = icalcomponent_as_ical_string_r (vfb);
2145 *freebusy = g_slist_append (*freebusy, calobj);
2146 icalcomponent_free (vfb);
2151 for (l = users; l != NULL; l = l->next ) {
2153 if (e_cal_backend_mail_account_is_valid (address, &name)) {
2154 vfb = create_user_free_busy (cbfile, address, name, start, end);
2155 calobj = icalcomponent_as_ical_string_r (vfb);
2156 *freebusy = g_slist_append (*freebusy, calobj);
2157 icalcomponent_free (vfb);
2163 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2166 static icaltimezone *
2167 e_cal_backend_file_internal_get_timezone (ECalBackend *backend,
2170 ECalBackendFile *cbfile;
2171 ECalBackendFilePrivate *priv;
2174 cbfile = E_CAL_BACKEND_FILE (backend);
2175 priv = cbfile->priv;
2177 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
2179 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2181 if (!strcmp (tzid, "UTC"))
2182 zone = icaltimezone_get_utc_timezone ();
2184 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
2186 if (!zone && E_CAL_BACKEND_CLASS (e_cal_backend_file_parent_class)->internal_get_timezone)
2187 zone = E_CAL_BACKEND_CLASS (e_cal_backend_file_parent_class)->internal_get_timezone (backend, tzid);
2190 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2195 sanitize_component (ECalBackendFile *cbfile,
2196 ECalComponent *comp)
2198 ECalComponentDateTime dt;
2201 /* Check dtstart, dtend and due's timezone, and convert it to local
2202 * default timezone if the timezone is not in our builtin timezone
2204 e_cal_component_get_dtstart (comp, &dt);
2205 if (dt.value && dt.tzid) {
2206 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *) cbfile, dt.tzid);
2208 g_free ((gchar *) dt.tzid);
2209 dt.tzid = g_strdup ("UTC");
2210 e_cal_component_set_dtstart (comp, &dt);
2213 e_cal_component_free_datetime (&dt);
2215 e_cal_component_get_dtend (comp, &dt);
2216 if (dt.value && dt.tzid) {
2217 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *) cbfile, dt.tzid);
2219 g_free ((gchar *) dt.tzid);
2220 dt.tzid = g_strdup ("UTC");
2221 e_cal_component_set_dtend (comp, &dt);
2224 e_cal_component_free_datetime (&dt);
2226 e_cal_component_get_due (comp, &dt);
2227 if (dt.value && dt.tzid) {
2228 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *) cbfile, dt.tzid);
2230 g_free ((gchar *) dt.tzid);
2231 dt.tzid = g_strdup ("UTC");
2232 e_cal_component_set_due (comp, &dt);
2235 e_cal_component_free_datetime (&dt);
2236 e_cal_component_abort_sequence (comp);
2241 e_cal_backend_file_create_objects (ECalBackendSync *backend,
2243 GCancellable *cancellable,
2244 const GSList *in_calobjs,
2246 GSList **new_components,
2249 ECalBackendFile *cbfile;
2250 ECalBackendFilePrivate *priv;
2251 GSList *icalcomps = NULL;
2254 cbfile = E_CAL_BACKEND_FILE (backend);
2255 priv = cbfile->priv;
2257 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
2258 e_return_data_cal_error_if_fail (in_calobjs != NULL, ObjectNotFound);
2259 e_return_data_cal_error_if_fail (new_components != NULL, ObjectNotFound);
2264 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2266 /* First step, parse input strings and do uid verification: may fail */
2267 for (l = in_calobjs; l; l = l->next) {
2268 icalcomponent *icalcomp;
2269 const gchar *comp_uid;
2271 /* Parse the icalendar text */
2272 icalcomp = icalparser_parse_string ((gchar *) l->data);
2274 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2275 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2276 g_propagate_error (error, EDC_ERROR (InvalidObject));
2280 /* Append icalcomponent to icalcomps */
2281 icalcomps = g_slist_prepend (icalcomps, icalcomp);
2283 /* Check kind with the parent */
2284 if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2285 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2286 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2287 g_propagate_error (error, EDC_ERROR (InvalidObject));
2292 comp_uid = icalcomponent_get_uid (icalcomp);
2296 new_uid = e_cal_component_gen_uid ();
2298 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2299 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2300 g_propagate_error (error, EDC_ERROR (InvalidObject));
2304 icalcomponent_set_uid (icalcomp, new_uid);
2305 comp_uid = icalcomponent_get_uid (icalcomp);
2310 /* check that the object is not in our cache */
2311 if (uid_in_use (cbfile, comp_uid)) {
2312 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2313 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2314 g_propagate_error (error, EDC_ERROR (ObjectIdAlreadyExists));
2319 icalcomps = g_slist_reverse (icalcomps);
2321 /* Second step, add the objects */
2322 for (l = icalcomps; l; l = l->next) {
2323 ECalComponent *comp;
2324 struct icaltimetype current;
2325 icalcomponent *icalcomp = l->data;
2327 /* Create the cal component */
2328 comp = e_cal_component_new ();
2329 e_cal_component_set_icalcomponent (comp, icalcomp);
2331 /* Set the created and last modified times on the component */
2332 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2333 e_cal_component_set_created (comp, ¤t);
2334 e_cal_component_set_last_modified (comp, ¤t);
2336 /* sanitize the component*/
2337 sanitize_component (cbfile, comp);
2339 /* Add the object */
2340 add_component (cbfile, comp, TRUE);
2342 /* Keep the UID and the modified component to return them later */
2344 *uids = g_slist_prepend (*uids, g_strdup (icalcomponent_get_uid (icalcomp)));
2346 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
2349 g_slist_free (icalcomps);
2352 save (cbfile, TRUE);
2354 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2357 *uids = g_slist_reverse (*uids);
2359 *new_components = g_slist_reverse (*new_components);
2363 ECalBackendFile *cbfile;
2364 ECalBackendFileObject *obj_data;
2367 } RemoveRecurrenceData;
2370 remove_object_instance_cb (gpointer key,
2374 time_t fromtt, instancett;
2375 ECalComponent *instance = value;
2376 RemoveRecurrenceData *rrdata = user_data;
2378 fromtt = icaltime_as_timet (icaltime_from_string (rrdata->rid));
2379 instancett = icaltime_as_timet (get_rid_icaltime (instance));
2381 if (fromtt > 0 && instancett > 0) {
2382 if ((rrdata->mod == CALOBJ_MOD_THISANDPRIOR && instancett <= fromtt) ||
2383 (rrdata->mod == CALOBJ_MOD_THISANDFUTURE && instancett >= fromtt)) {
2384 /* remove the component from our data */
2385 icalcomponent_remove_component (rrdata->cbfile->priv->icalcomp,
2386 e_cal_component_get_icalcomponent (instance));
2387 rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
2389 rrdata->obj_data->recurrences_list = g_list_remove (rrdata->obj_data->recurrences_list, instance);
2399 e_cal_backend_file_modify_objects (ECalBackendSync *backend,
2401 GCancellable *cancellable,
2402 const GSList *calobjs,
2404 GSList **old_components,
2405 GSList **new_components,
2408 ECalBackendFile *cbfile;
2409 ECalBackendFilePrivate *priv;
2410 GSList *icalcomps = NULL;
2413 cbfile = E_CAL_BACKEND_FILE (backend);
2414 priv = cbfile->priv;
2416 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
2417 e_return_data_cal_error_if_fail (calobjs != NULL, ObjectNotFound);
2419 case CALOBJ_MOD_THIS:
2420 case CALOBJ_MOD_THISANDPRIOR:
2421 case CALOBJ_MOD_THISANDFUTURE:
2422 case CALOBJ_MOD_ALL:
2425 g_propagate_error (error, EDC_ERROR (NotSupported));
2430 *old_components = NULL;
2432 *new_components = NULL;
2434 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2436 /* First step, parse input strings and do uid verification: may fail */
2437 for (l = calobjs; l; l = l->next) {
2438 const gchar *comp_uid;
2439 icalcomponent *icalcomp;
2441 /* Parse the icalendar text */
2442 icalcomp = icalparser_parse_string (l->data);
2444 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2445 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2446 g_propagate_error (error, EDC_ERROR (InvalidObject));
2450 icalcomps = g_slist_prepend (icalcomps, icalcomp);
2452 /* Check kind with the parent */
2453 if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2454 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2455 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2456 g_propagate_error (error, EDC_ERROR (InvalidObject));
2461 comp_uid = icalcomponent_get_uid (icalcomp);
2463 /* Get the object from our cache */
2464 if (!g_hash_table_lookup (priv->comp_uid_hash, comp_uid)) {
2465 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2466 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2467 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2472 icalcomps = g_slist_reverse (icalcomps);
2474 /* Second step, update the objects */
2475 for (l = icalcomps; l; l = l->next) {
2476 struct icaltimetype current;
2477 RemoveRecurrenceData rrdata;
2478 GList *detached = NULL;
2481 const gchar *comp_uid;
2482 icalcomponent * icalcomp = l->data;
2483 ECalComponent *comp, *recurrence;
2484 ECalBackendFileObject *obj_data;
2486 comp_uid = icalcomponent_get_uid (icalcomp);
2487 obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid);
2489 /* Create the cal component */
2490 comp = e_cal_component_new ();
2491 e_cal_component_set_icalcomponent (comp, icalcomp);
2493 /* Set the last modified time on the component */
2494 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2495 e_cal_component_set_last_modified (comp, ¤t);
2497 /* sanitize the component*/
2498 sanitize_component (cbfile, comp);
2499 rid = e_cal_component_get_recurid_as_string (comp);
2501 /* handle mod_type */
2503 case CALOBJ_MOD_THIS :
2504 if (!rid || !*rid) {
2506 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2508 /* replace only the full object */
2509 if (obj_data->full_object) {
2510 icalcomponent_remove_component (priv->icalcomp,
2511 e_cal_component_get_icalcomponent (obj_data->full_object));
2512 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2514 g_object_unref (obj_data->full_object);
2517 /* add the new object */
2518 obj_data->full_object = comp;
2520 icalcomponent_add_component (priv->icalcomp,
2521 e_cal_component_get_icalcomponent (obj_data->full_object));
2522 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2526 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (gpointer *) &real_rid, (gpointer *) &recurrence)) {
2527 if (*old_components)
2528 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (recurrence));
2530 /* remove the component from our data */
2531 icalcomponent_remove_component (priv->icalcomp,
2532 e_cal_component_get_icalcomponent (recurrence));
2533 priv->comp = g_list_remove (priv->comp, recurrence);
2534 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2535 g_hash_table_remove (obj_data->recurrences, rid);
2538 *old_components = g_slist_prepend (*old_components, NULL);
2541 /* add the detached instance */
2542 g_hash_table_insert (obj_data->recurrences,
2545 icalcomponent_add_component (priv->icalcomp,
2546 e_cal_component_get_icalcomponent (comp));
2547 priv->comp = g_list_append (priv->comp, comp);
2548 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
2550 case CALOBJ_MOD_THISANDPRIOR :
2551 case CALOBJ_MOD_THISANDFUTURE :
2552 if (!rid || !*rid) {
2554 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2556 remove_component (cbfile, comp_uid, obj_data);
2558 /* Add the new object */
2559 add_component (cbfile, comp, TRUE);
2563 /* remove the component from our data, temporarily */
2564 if (obj_data->full_object) {
2565 icalcomponent_remove_component (priv->icalcomp,
2566 e_cal_component_get_icalcomponent (obj_data->full_object));
2567 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2570 /* now deal with the detached recurrence */
2571 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
2572 (gpointer *) &real_rid, (gpointer *) &recurrence)) {
2574 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (recurrence));
2576 /* remove the component from our data */
2577 icalcomponent_remove_component (priv->icalcomp,
2578 e_cal_component_get_icalcomponent (recurrence));
2579 priv->comp = g_list_remove (priv->comp, recurrence);
2580 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2581 g_hash_table_remove (obj_data->recurrences, rid);
2583 if (*old_components)
2584 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2587 rrdata.cbfile = cbfile;
2588 rrdata.obj_data = obj_data;
2591 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2593 /* add the modified object to the beginning of the list,
2594 * so that it's always before any detached instance we
2596 if (obj_data->full_object) {
2597 icalcomponent_add_component (priv->icalcomp,
2598 e_cal_component_get_icalcomponent (obj_data->full_object));
2599 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2602 /* add the new detached recurrence */
2603 g_hash_table_insert (obj_data->recurrences,
2606 icalcomponent_add_component (priv->icalcomp,
2607 e_cal_component_get_icalcomponent (comp));
2608 priv->comp = g_list_append (priv->comp, comp);
2609 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
2611 case CALOBJ_MOD_ALL :
2612 /* Remove the old version */
2614 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2616 if (obj_data->recurrences_list) {
2617 /* has detached components, preserve them */
2620 for (ll = obj_data->recurrences_list; ll; ll = ll->next) {
2621 detached = g_list_prepend (detached, g_object_ref (ll->data));
2625 remove_component (cbfile, comp_uid, obj_data);
2627 /* Add the new object */
2628 add_component (cbfile, comp, TRUE);
2631 /* it had some detached components, place them back */
2632 comp_uid = icalcomponent_get_uid (e_cal_component_get_icalcomponent (comp));
2634 if ((obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid)) != NULL) {
2637 for (ll = detached; ll; ll = ll->next) {
2638 ECalComponent *c = ll->data;
2640 g_hash_table_insert (obj_data->recurrences, e_cal_component_get_recurid_as_string (c), c);
2641 icalcomponent_add_component (priv->icalcomp, e_cal_component_get_icalcomponent (c));
2642 priv->comp = g_list_append (priv->comp, c);
2643 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, c);
2647 g_list_free (detached);
2650 case CALOBJ_MOD_ONLY_THIS:
2651 /* not reached, keep compiler happy */
2657 if (new_components) {
2658 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
2662 g_slist_free (icalcomps);
2664 /* All the components were updated, now we save the file */
2665 save (cbfile, TRUE);
2667 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2670 *old_components = g_slist_reverse (*old_components);
2673 *new_components = g_slist_reverse (*new_components);
2677 * Remove one and only one instance. The object may be empty
2678 * afterwards, in which case it will be removed completely.
2680 * @mod CALOBJ_MOD_THIS or CAL_OBJ_MOD_ONLY_THIS: the later only removes
2681 * the instance, the former also adds an EXDATE if rid is set
2682 * TODO: CAL_OBJ_MOD_ONLY_THIS
2683 * @uid pointer to UID which must remain valid even if the object gets
2685 * @rid NULL, "", or non-empty string when manipulating a specific recurrence;
2686 * also must remain valid
2687 * @error may be NULL if caller is not interested in errors
2688 * @return modified object or NULL if it got removed
2690 static ECalBackendFileObject *
2691 remove_instance (ECalBackendFile *cbfile,
2692 ECalBackendFileObject *obj_data,
2696 ECalComponent **old_comp,
2697 ECalComponent **new_comp,
2701 ECalComponent *comp;
2702 struct icaltimetype current;
2704 /* only check for non-NULL below, empty string is detected here */
2709 /* remove recurrence */
2710 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
2711 (gpointer *) &hash_rid, (gpointer *) &comp)) {
2712 /* Removing without parent or not modifying parent?
2713 * Report removal to caller. */
2715 (!obj_data->full_object || mod == CALOBJ_MOD_ONLY_THIS)) {
2716 *old_comp = e_cal_component_clone (comp);
2719 /* Reporting parent modification to caller?
2720 * Report directly instead of going via caller. */
2721 if (obj_data->full_object &&
2722 mod != CALOBJ_MOD_ONLY_THIS) {
2723 /* old object string not provided,
2724 * instead rely on the view detecting
2725 * whether it contains the id */
2727 id.uid = (gchar *) uid;
2728 id.rid = (gchar *) rid;
2729 e_cal_backend_notify_component_removed (E_CAL_BACKEND (cbfile), &id, NULL, NULL);
2732 /* remove the component from our data */
2733 icalcomponent_remove_component (cbfile->priv->icalcomp,
2734 e_cal_component_get_icalcomponent (comp));
2735 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
2736 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp);
2737 g_hash_table_remove (obj_data->recurrences, rid);
2738 } else if (mod == CALOBJ_MOD_ONLY_THIS) {
2740 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2743 /* not an error, only add EXDATE */
2745 /* component empty? */
2746 if (!obj_data->full_object) {
2747 if (!obj_data->recurrences_list) {
2748 /* empty now, remove it */
2749 remove_component (cbfile, uid, obj_data);
2756 /* avoid modifying parent? */
2757 if (mod == CALOBJ_MOD_ONLY_THIS)
2760 /* remove the main component from our data before modifying it */
2761 icalcomponent_remove_component (cbfile->priv->icalcomp,
2762 e_cal_component_get_icalcomponent (obj_data->full_object));
2763 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
2765 /* add EXDATE or EXRULE to parent, report as update */
2767 *old_comp = e_cal_component_clone (obj_data->full_object);
2770 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object),
2771 icaltime_from_string (rid), CALOBJ_MOD_THIS);
2773 /* Since we are only removing one instance of recurrence
2774 * event, update the last modified time on the component */
2775 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2776 e_cal_component_set_last_modified (obj_data->full_object, ¤t);
2780 *new_comp = e_cal_component_clone (obj_data->full_object);
2783 /* add the modified object to the beginning of the list,
2784 * so that it's always before any detached instance we
2786 icalcomponent_add_component (cbfile->priv->icalcomp,
2787 e_cal_component_get_icalcomponent (obj_data->full_object));
2788 cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
2790 if (!obj_data->full_object) {
2791 /* Nothing to do, parent doesn't exist. Tell
2792 * caller about this? Not an error with
2793 * CALOBJ_MOD_THIS. */
2794 if (mod == CALOBJ_MOD_ONLY_THIS && error)
2795 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2799 /* remove the main component from our data before deleting it */
2800 if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) {
2801 /* return without changing anything */
2802 g_message (G_STRLOC " Could not remove component from interval tree!");
2805 icalcomponent_remove_component (cbfile->priv->icalcomp,
2806 e_cal_component_get_icalcomponent (obj_data->full_object));
2807 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
2809 /* remove parent, report as removal */
2811 *old_comp = g_object_ref (obj_data->full_object);
2813 g_object_unref (obj_data->full_object);
2814 obj_data->full_object = NULL;
2816 /* component may be empty now, check that */
2817 if (!obj_data->recurrences_list) {
2818 remove_component (cbfile, uid, obj_data);
2823 /* component still exists in a modified form */
2827 static ECalComponent *
2828 clone_ecalcomp_from_fileobject (ECalBackendFileObject *obj_data,
2831 ECalComponent *comp = obj_data->full_object;
2838 if (!g_hash_table_lookup_extended (obj_data->recurrences, rid,
2839 (gpointer *) &real_rid, (gpointer *) &comp)) {
2840 /* FIXME remove this once we delete an instance from master object through
2841 * modify request by setting exception */
2842 comp = obj_data->full_object;
2846 return comp ? e_cal_component_clone (comp) : NULL;
2850 notify_comp_removed_cb (gpointer pecalcomp,
2853 ECalComponent *comp = pecalcomp;
2854 ECalBackend *backend = pbackend;
2855 ECalComponentId *id;
2857 g_return_if_fail (comp != NULL);
2858 g_return_if_fail (backend != NULL);
2860 id = e_cal_component_get_id (comp);
2861 g_return_if_fail (id != NULL);
2863 e_cal_backend_notify_component_removed (backend, id, comp, NULL);
2865 e_cal_component_free_id (id);
2868 /* Remove_object handler for the file backend */
2870 e_cal_backend_file_remove_objects (ECalBackendSync *backend,
2872 GCancellable *cancellable,
2875 GSList **old_components,
2876 GSList **new_components,
2879 ECalBackendFile *cbfile;
2880 ECalBackendFilePrivate *priv;
2883 cbfile = E_CAL_BACKEND_FILE (backend);
2884 priv = cbfile->priv;
2886 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
2887 e_return_data_cal_error_if_fail (ids != NULL, ObjectNotFound);
2888 e_return_data_cal_error_if_fail (old_components != NULL, ObjectNotFound);
2889 e_return_data_cal_error_if_fail (new_components != NULL, ObjectNotFound);
2892 case CALOBJ_MOD_THIS:
2893 case CALOBJ_MOD_THISANDPRIOR:
2894 case CALOBJ_MOD_THISANDFUTURE:
2895 case CALOBJ_MOD_ONLY_THIS:
2896 case CALOBJ_MOD_ALL:
2899 g_propagate_error (error, EDC_ERROR (NotSupported));
2903 *old_components = *new_components = NULL;
2905 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
2907 /* First step, validate the input */
2908 for (l = ids; l; l = l->next) {
2909 ECalComponentId *id = l->data;
2910 /* Make the ID contains a uid */
2911 if (!id || !id->uid) {
2912 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2913 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2916 /* Check that it has a recurrence id if mod is CALOBJ_MOD_THISANDPRIOR
2917 or CALOBJ_MOD_THISANDFUTURE */
2918 if ((mod == CALOBJ_MOD_THISANDPRIOR || mod == CALOBJ_MOD_THISANDFUTURE) &&
2919 (!id->rid || !*(id->rid))) {
2920 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2921 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2924 /* Make sure the uid exists in the local hash table */
2925 if (!g_hash_table_lookup (priv->comp_uid_hash, id->uid)) {
2926 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
2927 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2932 /* Second step, remove objects from the calendar */
2933 for (l = ids; l; l = l->next) {
2934 const gchar *recur_id = NULL;
2935 ECalComponent *comp;
2936 RemoveRecurrenceData rrdata;
2937 ECalBackendFileObject *obj_data;
2938 ECalComponentId *id = l->data;
2940 obj_data = g_hash_table_lookup (priv->comp_uid_hash, id->uid);
2942 if (id->rid && *(id->rid))
2946 case CALOBJ_MOD_ALL :
2947 *old_components = g_slist_prepend (*old_components, clone_ecalcomp_from_fileobject (obj_data, recur_id));
2948 *new_components = g_slist_prepend (*new_components, NULL);
2950 if (obj_data->recurrences_list)
2951 g_list_foreach (obj_data->recurrences_list, notify_comp_removed_cb, cbfile);
2952 remove_component (cbfile, id->uid, obj_data);
2954 case CALOBJ_MOD_ONLY_THIS:
2955 case CALOBJ_MOD_THIS: {
2956 ECalComponent *old_component = NULL;
2957 ECalComponent *new_component = NULL;
2959 obj_data = remove_instance (cbfile, obj_data, id->uid, recur_id, mod,
2960 &old_component, &new_component, error);
2962 *old_components = g_slist_prepend (*old_components, old_component);
2963 *new_components = g_slist_prepend (*new_components, new_component);
2966 case CALOBJ_MOD_THISANDPRIOR :
2967 case CALOBJ_MOD_THISANDFUTURE :
2968 comp = obj_data->full_object;
2971 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (comp));
2973 /* remove the component from our data, temporarily */
2974 icalcomponent_remove_component (priv->icalcomp,
2975 e_cal_component_get_icalcomponent (comp));
2976 priv->comp = g_list_remove (priv->comp, comp);
2978 e_cal_util_remove_instances (e_cal_component_get_icalcomponent (comp),
2979 icaltime_from_string (recur_id), mod);
2981 *old_components = g_slist_prepend (*old_components, NULL);
2984 /* now remove all detached instances */
2985 rrdata.cbfile = cbfile;
2986 rrdata.obj_data = obj_data;
2987 rrdata.rid = recur_id;
2989 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2991 /* add the modified object to the beginning of the list,
2992 * so that it's always before any detached instance we
2995 priv->comp = g_list_prepend (priv->comp, comp);
2997 if (obj_data->full_object) {
2998 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (obj_data->full_object));
3000 *new_components = g_slist_prepend (*new_components, NULL);
3006 save (cbfile, TRUE);
3008 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
3010 *old_components = g_slist_reverse (*old_components);
3011 *new_components = g_slist_reverse (*new_components);
3015 cancel_received_object (ECalBackendFile *cbfile,
3016 ECalComponent *comp,
3017 ECalComponent **old_comp,
3018 ECalComponent **new_comp)
3020 ECalBackendFileObject *obj_data;
3021 ECalBackendFilePrivate *priv;
3023 const gchar *uid = NULL;
3025 priv = cbfile->priv;
3030 e_cal_component_get_uid (comp, &uid);
3032 /* Find the old version of the component. */
3033 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
3038 rid = e_cal_component_get_recurid_as_string (comp);
3040 obj_data = remove_instance (cbfile, obj_data, uid, rid, CALOBJ_MOD_THIS,
3041 old_comp, new_comp, NULL);
3042 if (obj_data && obj_data->full_object && !*new_comp) {
3043 *new_comp = e_cal_component_clone (obj_data->full_object);
3046 /* report as removal by keeping *new_component NULL */
3047 if (obj_data->full_object) {
3048 *old_comp = e_cal_component_clone (obj_data->full_object);
3050 remove_component (cbfile, uid, obj_data);
3062 } ECalBackendFileTzidData;
3065 check_tzids (icalparameter *param,
3068 ECalBackendFileTzidData *tzdata = data;
3071 tzid = icalparameter_get_tzid (param);
3072 if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
3073 tzdata->found = FALSE;
3076 /* This function is largely duplicated in
3077 * ../groupwise/e-cal-backend-groupwise.c
3080 fetch_attachments (ECalBackendSync *backend,
3081 ECalComponent *comp)
3083 GSList *attach_list = NULL, *new_attach_list = NULL;
3085 gchar *dest_url, *dest_file;
3089 e_cal_component_get_attachment_list (comp, &attach_list);
3090 e_cal_component_get_uid (comp, &uid);
3092 for (l = attach_list, fileindex = 0; l; l = l->next, fileindex++) {
3093 gchar *sfname = g_filename_from_uri ((const gchar *) l->data, NULL, NULL);
3095 GMappedFile *mapped_file;
3096 GError *error = NULL;
3101 mapped_file = g_mapped_file_new (sfname, FALSE, &error);
3103 g_message ("DEBUG: could not map %s: %s\n",
3104 sfname, error ? error->message : "???");
3105 g_error_free (error);
3109 filename = g_path_get_basename (sfname);
3110 dest_file = e_cal_backend_create_cache_filename (E_CAL_BACKEND (backend), uid, filename, fileindex);
3112 fd = g_open (dest_file, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
3114 /* TODO handle error conditions */
3115 g_message ("DEBUG: could not open %s for writing\n",
3117 } else if (write (fd, g_mapped_file_get_contents (mapped_file),
3118 g_mapped_file_get_length (mapped_file)) == -1) {
3119 /* TODO handle error condition */
3120 g_message ("DEBUG: attachment write failed.\n");
3123 g_mapped_file_unref (mapped_file);
3127 dest_url = g_filename_to_uri (dest_file, NULL, NULL);
3129 new_attach_list = g_slist_append (new_attach_list, dest_url);
3133 e_cal_component_set_attachment_list (comp, new_attach_list);
3136 /* Update_objects handler for the file backend. */
3138 e_cal_backend_file_receive_objects (ECalBackendSync *backend,
3140 GCancellable *cancellable,
3141 const gchar *calobj,
3144 ECalBackendFile *cbfile;
3145 ECalBackendFilePrivate *priv;
3146 icalcomponent *toplevel_comp, *icalcomp = NULL;
3147 icalcomponent_kind kind;
3148 icalproperty_method toplevel_method, method;
3149 icalcomponent *subcomp;
3150 GList *comps, *del_comps, *l;
3151 ECalComponent *comp;
3152 struct icaltimetype current;
3153 ECalBackendFileTzidData tzdata;
3156 cbfile = E_CAL_BACKEND_FILE (backend);
3157 priv = cbfile->priv;
3159 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, InvalidArg);
3160 e_return_data_cal_error_if_fail (calobj != NULL, InvalidObject);
3162 /* Pull the component from the string and ensure that it is sane */
3163 toplevel_comp = icalparser_parse_string ((gchar *) calobj);
3164 if (!toplevel_comp) {
3165 g_propagate_error (error, EDC_ERROR (InvalidObject));
3169 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
3171 kind = icalcomponent_isa (toplevel_comp);
3172 if (kind != ICAL_VCALENDAR_COMPONENT) {
3173 /* If its not a VCALENDAR, make it one to simplify below */
3174 icalcomp = toplevel_comp;
3175 toplevel_comp = e_cal_util_new_top_level ();
3176 if (icalcomponent_get_method (icalcomp) == ICAL_METHOD_CANCEL)
3177 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_CANCEL);
3179 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_PUBLISH);
3180 icalcomponent_add_component (toplevel_comp, icalcomp);
3182 if (!icalcomponent_get_first_property (toplevel_comp, ICAL_METHOD_PROPERTY))
3183 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_PUBLISH);
3186 toplevel_method = icalcomponent_get_method (toplevel_comp);
3188 /* Build a list of timezones so we can make sure all the objects have valid info */
3189 tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
3191 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
3195 zone = icaltimezone_new ();
3196 if (icaltimezone_set_component (zone, subcomp))
3197 g_hash_table_insert (tzdata.zones, g_strdup (icaltimezone_get_tzid (zone)), NULL);
3199 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
3202 /* First we make sure all the components are usuable */
3203 comps = del_comps = NULL;
3204 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
3206 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
3208 icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
3210 if (child_kind != kind) {
3211 /* remove the component from the toplevel VCALENDAR */
3212 if (child_kind != ICAL_VTIMEZONE_COMPONENT)
3213 del_comps = g_list_prepend (del_comps, subcomp);
3215 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
3219 tzdata.found = TRUE;
3220 icalcomponent_foreach_tzid (subcomp, check_tzids, &tzdata);
3222 if (!tzdata.found) {
3223 err = EDC_ERROR (InvalidObject);
3227 if (!icalcomponent_get_uid (subcomp)) {
3228 if (toplevel_method == ICAL_METHOD_PUBLISH) {
3230 gchar *new_uid = NULL;
3232 new_uid = e_cal_component_gen_uid ();
3233 icalcomponent_set_uid (subcomp, new_uid);
3236 err = EDC_ERROR (InvalidObject);
3242 comps = g_list_prepend (comps, subcomp);
3243 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
3246 /* Now we manipulate the components we care about */
3247 for (l = comps; l; l = l->next) {
3248 ECalComponent *old_component = NULL;
3249 ECalComponent *new_component = NULL;
3252 ECalBackendFileObject *obj_data;
3253 gboolean is_declined;
3257 /* Create the cal component */
3258 comp = e_cal_component_new ();
3259 e_cal_component_set_icalcomponent (comp, subcomp);
3261 /* Set the created and last modified times on the component */
3262 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3263 e_cal_component_set_created (comp, ¤t);
3264 e_cal_component_set_last_modified (comp, ¤t);
3266 e_cal_component_get_uid (comp, &uid);
3267 rid = e_cal_component_get_recurid_as_string (comp);
3269 if (icalcomponent_get_first_property (subcomp, ICAL_METHOD_PROPERTY))
3270 method = icalcomponent_get_method (subcomp);
3272 method = toplevel_method;
3275 case ICAL_METHOD_PUBLISH:
3276 case ICAL_METHOD_REQUEST:
3277 case ICAL_METHOD_REPLY:
3278 is_declined = e_cal_backend_user_declined (subcomp);
3280 /* handle attachments */
3281 if (!is_declined && e_cal_component_has_attachments (comp))
3282 fetch_attachments (backend, comp);
3283 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
3287 ECalComponent *ignore_comp = NULL;
3289 remove_instance (cbfile, obj_data, uid, rid, CALOBJ_MOD_THIS,
3290 &old_component, &ignore_comp, NULL);
3293 g_object_unref (ignore_comp);
3295 if (obj_data->full_object) {
3296 old_component = e_cal_component_clone (obj_data->full_object);
3298 remove_component (cbfile, uid, obj_data);
3302 add_component (cbfile, comp, FALSE);
3305 e_cal_backend_notify_component_modified (E_CAL_BACKEND (backend),
3306 old_component, comp);
3308 ECalComponentId *id = e_cal_component_get_id (comp);
3310 e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
3314 e_cal_component_free_id (id);
3318 g_object_unref (old_component);
3320 } else if (!is_declined) {
3321 add_component (cbfile, comp, FALSE);
3323 e_cal_backend_notify_component_created (E_CAL_BACKEND (backend), comp);
3327 case ICAL_METHOD_ADD:
3328 /* FIXME This should be doable once all the recurid stuff is done */
3329 err = EDC_ERROR (UnsupportedMethod);
3333 case ICAL_METHOD_COUNTER:
3334 err = EDC_ERROR (UnsupportedMethod);
3338 case ICAL_METHOD_DECLINECOUNTER:
3339 err = EDC_ERROR (UnsupportedMethod);
3343 case ICAL_METHOD_CANCEL:
3344 if (cancel_received_object (cbfile, comp, &old_component, &new_component)) {
3345 ECalComponentId *id;
3347 id = e_cal_component_get_id (comp);
3349 e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
3350 id, old_component, new_component);
3352 /* remove the component from the toplevel VCALENDAR */
3353 icalcomponent_remove_component (toplevel_comp, subcomp);
3354 icalcomponent_free (subcomp);
3355 e_cal_component_free_id (id);
3358 g_object_unref (new_component);
3360 g_object_unref (old_component);
3365 err = EDC_ERROR (UnsupportedMethod);
3371 g_list_free (comps);
3373 /* Now we remove the components we don't care about */
3374 for (l = del_comps; l; l = l->next) {
3377 icalcomponent_remove_component (toplevel_comp, subcomp);
3378 icalcomponent_free (subcomp);
3381 g_list_free (del_comps);
3383 /* check and patch timezones */
3385 if (!e_cal_client_check_timezones (toplevel_comp,
3387 e_cal_client_tzlookup_icomp,
3392 * This makes assumptions about what kind of
3393 * errors can occur inside e_cal_check_timezones().
3394 * We control it, so that should be safe, but
3395 * is the code really identical with the calendar
3402 /* Merge the iCalendar components with our existing VCALENDAR,
3403 * resolving any conflicting TZIDs. */
3404 icalcomponent_merge_component (priv->icalcomp, toplevel_comp);
3406 save (cbfile, TRUE);
3409 g_hash_table_destroy (tzdata.zones);
3410 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
3413 g_propagate_error (error, err);
3417 e_cal_backend_file_send_objects (ECalBackendSync *backend,
3419 GCancellable *cancellable,
3420 const gchar *calobj,
3422 gchar **modified_calobj,
3426 *modified_calobj = g_strdup (calobj);
3429 /* Object initialization function for the file backend */
3431 e_cal_backend_file_init (ECalBackendFile *cbfile)
3433 cbfile->priv = E_CAL_BACKEND_FILE_GET_PRIVATE (cbfile);
3435 cbfile->priv->file_name = g_strdup ("calendar.ics");
3437 g_static_rec_mutex_init (&cbfile->priv->idle_save_rmutex);
3439 cbfile->priv->refresh_lock = g_mutex_new ();
3442 * data access is serialized via idle_save_rmutex, so locking at the
3443 * backend method level is not needed
3445 e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbfile), FALSE);
3449 cal_backend_file_constructed (GObject *object)
3451 ECalBackend *backend;
3453 icalcomponent_kind kind;
3454 const gchar *source_dir;
3455 const gchar *user_data_dir;
3456 const gchar *component_type;
3457 gchar *mangled_source_dir;
3460 user_data_dir = e_get_user_data_dir ();
3462 /* Chain up to parent's constructed() method. */
3463 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->constructed (object);
3465 /* Override the cache directory that the parent class just set. */
3467 backend = E_CAL_BACKEND (object);
3468 kind = e_cal_backend_get_kind (backend);
3469 source = e_backend_get_source (E_BACKEND (backend));
3472 case ICAL_VEVENT_COMPONENT:
3473 component_type = "calendar";
3475 case ICAL_VTODO_COMPONENT:
3476 component_type = "tasks";
3478 case ICAL_VJOURNAL_COMPONENT:
3479 component_type = "memos";
3482 g_warn_if_reached ();
3483 component_type = "calendar";
3487 source_dir = e_source_peek_relative_uri (source);
3488 if (!source_dir || !g_str_equal (source_dir, "system"))
3489 source_dir = e_source_get_uid (source);
3491 /* Mangle the URI to not contain invalid characters. */
3492 mangled_source_dir = g_strdelimit (g_strdup (source_dir), ":/", '_');
3494 filename = g_build_filename (
3495 user_data_dir, component_type, mangled_source_dir, NULL);
3496 e_cal_backend_set_cache_dir (backend, filename);
3499 g_free (mangled_source_dir);
3502 /* Class initialization function for the file backend */
3504 e_cal_backend_file_class_init (ECalBackendFileClass *class)
3506 GObjectClass *object_class;
3507 ECalBackendClass *backend_class;
3508 ECalBackendSyncClass *sync_class;
3510 g_type_class_add_private (class, sizeof (ECalBackendFilePrivate));
3512 object_class = (GObjectClass *) class;
3513 backend_class = (ECalBackendClass *) class;
3514 sync_class = (ECalBackendSyncClass *) class;
3516 object_class->dispose = e_cal_backend_file_dispose;
3517 object_class->finalize = e_cal_backend_file_finalize;
3518 object_class->constructed = cal_backend_file_constructed;
3520 sync_class->get_backend_property_sync = e_cal_backend_file_get_backend_property;
3521 sync_class->open_sync = e_cal_backend_file_open;
3522 sync_class->remove_sync = e_cal_backend_file_remove;
3523 sync_class->create_objects_sync = e_cal_backend_file_create_objects;
3524 sync_class->modify_objects_sync = e_cal_backend_file_modify_objects;
3525 sync_class->remove_objects_sync = e_cal_backend_file_remove_objects;
3526 sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
3527 sync_class->send_objects_sync = e_cal_backend_file_send_objects;
3528 sync_class->get_object_sync = e_cal_backend_file_get_object;
3529 sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
3530 sync_class->get_attachment_uris_sync = e_cal_backend_file_get_attachment_uris;
3531 sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
3532 sync_class->get_free_busy_sync = e_cal_backend_file_get_free_busy;
3534 backend_class->start_view = e_cal_backend_file_start_view;
3535 backend_class->internal_get_timezone = e_cal_backend_file_internal_get_timezone;
3539 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile,
3540 const gchar *file_name)
3542 ECalBackendFilePrivate *priv;
3544 g_return_if_fail (cbfile != NULL);
3545 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
3546 g_return_if_fail (file_name != NULL);
3548 priv = cbfile->priv;
3549 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
3551 if (priv->file_name)
3552 g_free (priv->file_name);
3554 priv->file_name = g_strdup (file_name);
3556 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
3560 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
3562 ECalBackendFilePrivate *priv;
3564 g_return_val_if_fail (cbfile != NULL, NULL);
3565 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
3567 priv = cbfile->priv;
3569 return priv->file_name;
3573 e_cal_backend_file_reload (ECalBackendFile *cbfile,
3576 ECalBackendFilePrivate *priv;
3580 priv = cbfile->priv;
3581 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
3583 str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
3585 err = EDC_ERROR_NO_URI ();
3589 if (g_access (str_uri, R_OK) == 0) {
3590 reload_cal (cbfile, str_uri, &err);
3591 if (g_access (str_uri, W_OK) != 0)
3592 priv->read_only = TRUE;
3594 err = EDC_ERROR (NoSuchCal);
3599 if (!err && !priv->read_only) {
3602 source = e_backend_get_source (E_BACKEND (cbfile));
3604 if (e_source_get_property (source, "custom-file-readonly") && g_str_equal (e_source_get_property (source, "custom-file-readonly"), "1"))
3605 priv->read_only = TRUE;
3608 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
3609 e_cal_backend_notify_readonly (E_CAL_BACKEND (cbfile), cbfile->priv->read_only);
3612 g_propagate_error (perror, err);
3615 #ifdef TEST_QUERY_RESULT
3618 test_query_by_scanning_all_objects (ECalBackendFile *cbfile,
3622 MatchObjectData match_data;
3623 ECalBackendFilePrivate *priv;
3625 priv = cbfile->priv;
3627 match_data.search_needed = TRUE;
3628 match_data.query = sexp;
3629 match_data.comps_list = NULL;
3630 match_data.as_string = TRUE;
3631 match_data.backend = E_CAL_BACKEND (cbfile);
3633 if (!strcmp (sexp, "#t"))
3634 match_data.search_needed = FALSE;
3636 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
3637 if (!match_data.obj_sexp)
3640 g_static_rec_mutex_lock (&priv->idle_save_rmutex);
3642 if (!match_data.obj_sexp)
3644 g_message (G_STRLOC ": Getting object list (%s)", sexp);
3648 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
3651 g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
3653 *objects = g_slist_reverse (match_data.comps_list);
3655 g_object_unref (match_data.obj_sexp);
3659 write_list (GSList *list)
3663 for (l = list; l; l = l->next)
3665 const gchar *str = l->data;
3666 ECalComponent *comp = e_cal_component_new_from_string (str);
3668 e_cal_component_get_uid (comp, &uid);
3669 g_print ("%s\n", uid);
3674 get_difference_of_lists (ECalBackendFile *cbfile,
3678 GSList *l, *lsmaller;
3680 for (l = bigger; l; l = l->next) {
3681 gchar *str = l->data;
3683 ECalComponent *comp = e_cal_component_new_from_string (str);
3684 gboolean found = FALSE;
3685 e_cal_component_get_uid (comp, &uid);
3687 for (lsmaller = smaller; lsmaller && !found; lsmaller = lsmaller->next)
3689 gchar *strsmaller = lsmaller->data;
3690 const gchar *uidsmaller;
3691 ECalComponent *compsmaller = e_cal_component_new_from_string (strsmaller);
3692 e_cal_component_get_uid (compsmaller, &uidsmaller);
3694 found = strcmp (uid, uidsmaller) == 0;
3696 g_object_unref (compsmaller);
3701 time_t time_start, time_end;
3702 printf ("%s IS MISSING\n", uid);
3704 e_cal_util_get_component_occur_times (comp, &time_start, &time_end,
3705 resolve_tzid, cbfile->priv->icalcomp,
3706 icaltimezone_get_utc_timezone (),
3707 e_cal_backend_get_kind (E_CAL_BACKEND (cbfile)));
3709 d (printf ("start %s\n", asctime(gmtime(&time_start))));
3710 d (printf ("end %s\n", asctime(gmtime(&time_end))));
3713 g_object_unref (comp);
3718 test_query (ECalBackendFile *cbfile,
3721 GSList *objects = NULL, *all_objects = NULL;
3723 g_return_if_fail (query != NULL);
3725 d (g_print ("Query %s\n", query));
3727 test_query_by_scanning_all_objects (cbfile, query, &all_objects);
3728 e_cal_backend_file_get_object_list (E_CAL_BACKEND_SYNC (cbfile), NULL, NULL, query, &objects, NULL);
3729 if (objects == NULL)
3731 g_message (G_STRLOC " failed to get objects\n");
3735 if (g_slist_length (objects) < g_slist_length (all_objects) )
3737 g_print ("ERROR\n");
3738 get_difference_of_lists (cbfile, objects, all_objects);
3741 else if (g_slist_length (objects) > g_slist_length (all_objects) )
3743 g_print ("ERROR\n");
3744 write_list (all_objects);
3745 get_difference_of_lists (cbfile, all_objects, objects);
3749 g_slist_foreach (objects, (GFunc) g_free, NULL);
3750 g_slist_free (objects);
3751 g_slist_foreach (all_objects, (GFunc) g_free, NULL);
3752 g_slist_free (all_objects);
3756 execute_query (ECalBackendFile *cbfile,
3759 GSList *objects = NULL;
3761 g_return_if_fail (query != NULL);
3763 d (g_print ("Query %s\n", query));
3764 e_cal_backend_file_get_object_list (E_CAL_BACKEND_SYNC (cbfile), NULL, NULL, query, &objects, NULL);
3765 if (objects == NULL)
3767 g_message (G_STRLOC " failed to get objects\n");
3771 g_slist_foreach (objects, (GFunc) g_free, NULL);
3772 g_slist_free (objects);
3775 static gchar *fname = NULL;
3776 static gboolean only_execute = FALSE;
3777 static gchar *calendar_fname = NULL;
3779 static GOptionEntry entries[] =
3781 { "test-file", 't', 0, G_OPTION_ARG_STRING, &fname, "File with prepared queries", NULL },
3782 { "only-execute", 'e', 0, G_OPTION_ARG_NONE, &only_execute, "Only execute, do not test query", NULL },
3783 { "calendar-file", 'c', 0, G_OPTION_ARG_STRING, &calendar_fname, "Path to the calendar.ics file", NULL },
3787 /* Always add at least this many bytes when extending the buffer. */
3788 #define MIN_CHUNK 64
3791 private_getline (gchar **lineptr,
3798 if (!lineptr || !n || !stream)
3803 *lineptr = (char *)malloc (*n);
3808 nchars_avail = (gint) *n;
3809 read_pos = *lineptr;
3812 gint c = getc (stream);
3814 if (nchars_avail < 2) {
3820 nchars_avail = (gint)(*n + *lineptr - read_pos);
3821 *lineptr = (char *)realloc (*lineptr, *n);
3824 read_pos = *n - nchars_avail + *lineptr;
3827 if (ferror (stream) || c == EOF) {
3828 if (read_pos == *lineptr)
3838 /* Return the line. */
3844 return (gint)(read_pos - (*lineptr));
3851 gchar * line = NULL;
3853 ECalBackendFile * cbfile;
3855 GError *error = NULL;
3856 GOptionContext *context;
3861 context = g_option_context_new ("- test utility for e-d-s file backend");
3862 g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
3863 if (!g_option_context_parse (context, &argc, &argv, &error))
3865 g_print ("option parsing failed: %s\n", error->message);
3869 calendar_fname = g_strdup("calendar.ics");
3871 if (!calendar_fname)
3873 g_message (G_STRLOC " Please, use -c parameter");
3877 cbfile = g_object_new (E_TYPE_CAL_BACKEND_FILE, NULL);
3878 open_cal (cbfile, calendar_fname, &error);
3879 if (error != NULL) {
3880 g_message (G_STRLOC " Could not open calendar %s: %s", calendar_fname, error->message);
3886 fin = fopen (fname, "r");
3890 g_message (G_STRLOC " Could not open file %s", fname);
3896 g_message (G_STRLOC " Reading from stdin");
3900 while (private_getline (&line, &len, fin) != -1) {
3901 g_print ("Query %d: %s", num++, line);
3904 execute_query (cbfile, line);
3906 test_query (cbfile, line);
3916 g_object_unref (cbfile);