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 "e-cal-backend-file-events.h"
37 #include "e-source-local.h"
43 #define E_CAL_BACKEND_FILE_GET_PRIVATE(obj) \
44 (G_TYPE_INSTANCE_GET_PRIVATE \
45 ((obj), E_TYPE_CAL_BACKEND_FILE, ECalBackendFilePrivate))
47 #define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
48 #define EDC_ERROR_NO_URI() e_data_cal_create_error (OtherError, "Cannot get URI")
50 #define ECAL_REVISION_X_PROP "X-EVOLUTION-DATA-REVISION"
52 G_DEFINE_TYPE (ECalBackendFile, e_cal_backend_file, E_TYPE_CAL_BACKEND_SYNC)
54 /* Placeholder for each component and its recurrences */
56 ECalComponent *full_object;
57 GHashTable *recurrences;
58 GList *recurrences_list;
59 } ECalBackendFileObject;
61 /* Private part of the ECalBackendFile structure */
62 struct _ECalBackendFilePrivate {
63 /* path where the calendar data is stored */
66 /* Filename in the dir */
72 /* locked in high-level functions to ensure data is consistent
73 * in idle and CORBA thread(s?); because high-level functions
74 * may call other high-level functions the mutex must allow
77 GRecMutex idle_save_rmutex;
79 /* Toplevel VCALENDAR component */
80 icalcomponent *icalcomp;
82 /* All the objects in the calendar, hashed by UID. The
83 * hash key *is* the uid returned by cal_component_get_uid(); it is not
84 * copied, so don't free it when you remove an object from the hash
85 * table. Each item in the hash table is a ECalBackendFileObject.
87 GHashTable *comp_uid_hash;
89 EIntervalTree *interval_tree;
93 /* guards refresh members */
95 /* set to TRUE to indicate thread should stop */
96 gboolean refresh_thread_stop;
97 /* condition for refreshing, not NULL when thread exists */
99 /* cond to know the refresh thread gone */
100 GCond *refresh_gone_cond;
101 /* increased when backend saves the file */
104 GFileMonitor *refresh_monitor;
106 /* Just an incremental number to ensure uniqueness across revisions */
107 guint revision_counter;
114 static void e_cal_backend_file_dispose (GObject *object);
115 static void e_cal_backend_file_finalize (GObject *object);
117 static void free_refresh_data (ECalBackendFile *cbfile);
119 static icaltimezone *
120 e_cal_backend_file_internal_get_timezone (ECalBackend *backend, const gchar *tzid);
122 static void bump_revision (ECalBackendFile *cbfile);
124 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
126 free_object_data (gpointer data)
128 ECalBackendFileObject *obj_data = data;
130 if (obj_data->full_object)
131 g_object_unref (obj_data->full_object);
132 g_hash_table_destroy (obj_data->recurrences);
133 g_list_free (obj_data->recurrences_list);
138 /* Saves the calendar data */
140 save_file_when_idle (gpointer user_data)
142 ECalBackendFilePrivate *priv;
144 GFile *file, *backup_file;
145 GFileOutputStream *stream;
147 gchar *tmp, *backup_uristr;
149 ECalBackendFile *cbfile = user_data;
152 g_assert (priv->path != NULL);
153 g_assert (priv->icalcomp != NULL);
155 g_rec_mutex_lock (&priv->idle_save_rmutex);
156 if (!priv->is_dirty || priv->read_only) {
157 priv->dirty_idle_id = 0;
158 priv->is_dirty = FALSE;
159 g_rec_mutex_unlock (&priv->idle_save_rmutex);
163 file = g_file_new_for_path (priv->path);
165 goto error_malformed_uri;
167 /* save calendar to backup file */
168 tmp = g_file_get_uri (file);
170 g_object_unref (file);
171 goto error_malformed_uri;
174 backup_uristr = g_strconcat (tmp, "~", NULL);
175 backup_file = g_file_new_for_uri (backup_uristr);
178 g_free (backup_uristr);
181 g_object_unref (file);
182 goto error_malformed_uri;
185 priv->refresh_skip++;
186 stream = g_file_replace (backup_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &e);
189 g_object_unref (stream);
191 g_object_unref (file);
192 g_object_unref (backup_file);
193 priv->refresh_skip--;
197 buf = icalcomponent_as_ical_string_r (priv->icalcomp);
198 succeeded = g_output_stream_write_all (G_OUTPUT_STREAM (stream), buf, strlen (buf) * sizeof (gchar), NULL, NULL, &e);
201 if (!succeeded || e) {
202 g_object_unref (stream);
203 g_object_unref (file);
204 g_object_unref (backup_file);
208 succeeded = g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &e);
209 g_object_unref (stream);
211 if (!succeeded || e) {
212 g_object_unref (file);
213 g_object_unref (backup_file);
217 /* now copy the temporary file to the real file */
218 g_file_move (backup_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &e);
220 g_object_unref (file);
221 g_object_unref (backup_file);
225 priv->is_dirty = FALSE;
226 priv->dirty_idle_id = 0;
228 g_rec_mutex_unlock (&priv->idle_save_rmutex);
233 g_rec_mutex_unlock (&priv->idle_save_rmutex);
234 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
235 _("Cannot save calendar data: Malformed URI."));
239 g_rec_mutex_unlock (&priv->idle_save_rmutex);
242 gchar *msg = g_strdup_printf ("%s: %s", _("Cannot save calendar data"), e->message);
244 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), msg);
248 e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), _("Cannot save calendar data"));
254 save (ECalBackendFile *cbfile,
255 gboolean do_bump_revision)
257 ECalBackendFilePrivate *priv;
259 if (do_bump_revision)
260 bump_revision (cbfile);
264 g_rec_mutex_lock (&priv->idle_save_rmutex);
265 priv->is_dirty = TRUE;
267 if (!priv->dirty_idle_id)
268 priv->dirty_idle_id = g_idle_add ((GSourceFunc) save_file_when_idle, cbfile);
270 g_rec_mutex_unlock (&priv->idle_save_rmutex);
274 free_calendar_components (GHashTable *comp_uid_hash,
275 icalcomponent *top_icomp)
278 g_hash_table_destroy (comp_uid_hash);
281 icalcomponent_free (top_icomp);
285 free_calendar_data (ECalBackendFile *cbfile)
287 ECalBackendFilePrivate *priv;
291 e_intervaltree_destroy (priv->interval_tree);
292 priv->interval_tree = NULL;
294 free_calendar_components (priv->comp_uid_hash, priv->icalcomp);
295 priv->comp_uid_hash = NULL;
296 priv->icalcomp = NULL;
298 g_list_free (priv->comp);
302 /* Dispose handler for the file backend */
304 e_cal_backend_file_dispose (GObject *object)
306 ECalBackendFile *cbfile;
307 ECalBackendFilePrivate *priv;
310 cbfile = E_CAL_BACKEND_FILE (object);
313 /* Save if necessary */
315 save_file_when_idle (cbfile);
317 free_calendar_data (cbfile);
319 source = e_backend_get_source (E_BACKEND (cbfile));
321 g_signal_handlers_disconnect_matched (source, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, cbfile);
323 /* Chain up to parent's dispose() method. */
324 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->dispose (object);
327 /* Finalize handler for the file backend */
329 e_cal_backend_file_finalize (GObject *object)
331 ECalBackendFilePrivate *priv;
333 priv = E_CAL_BACKEND_FILE_GET_PRIVATE (object);
337 if (priv->dirty_idle_id)
338 g_source_remove (priv->dirty_idle_id);
340 free_refresh_data (E_CAL_BACKEND_FILE (object));
342 g_mutex_clear (&priv->refresh_lock);
344 g_rec_mutex_clear (&priv->idle_save_rmutex);
347 g_free (priv->file_name);
349 /* Chain up to parent's finalize() method. */
350 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->finalize (object);
355 /* Looks up an component by its UID on the backend's component hash table
356 * and returns TRUE if any event (regardless whether it is the master or a child)
357 * with that UID exists */
359 uid_in_use (ECalBackendFile *cbfile,
362 ECalBackendFilePrivate *priv;
363 ECalBackendFileObject *obj_data;
367 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
368 return obj_data != NULL;
373 static icalproperty *
374 get_revision_property (ECalBackendFile *cbfile)
376 ECalBackendFilePrivate *priv;
380 prop = icalcomponent_get_first_property (priv->icalcomp, ICAL_X_PROPERTY);
382 while (prop != NULL) {
383 const gchar *name = icalproperty_get_x_name (prop);
385 if (name && strcmp (name, ECAL_REVISION_X_PROP) == 0)
388 prop = icalcomponent_get_next_property (priv->icalcomp, ICAL_X_PROPERTY);
395 make_revision_string (ECalBackendFile *cbfile)
401 g_get_current_time (&timeval);
403 datestr = g_time_val_to_iso8601 (&timeval);
404 revision = g_strdup_printf ("%s(%d)", datestr, cbfile->priv->revision_counter++);
410 static icalproperty *
411 ensure_revision (ECalBackendFile *cbfile)
415 prop = get_revision_property (cbfile);
418 gchar *revision = make_revision_string (cbfile);
420 prop = icalproperty_new (ICAL_X_PROPERTY);
422 icalproperty_set_x_name (prop, ECAL_REVISION_X_PROP);
423 icalproperty_set_x (prop, revision);
425 icalcomponent_add_property (cbfile->priv->icalcomp, prop);
434 bump_revision (ECalBackendFile *cbfile)
436 /* Update the revision string */
437 icalproperty *prop = ensure_revision (cbfile);
438 gchar *revision = make_revision_string (cbfile);
440 icalproperty_set_x (prop, revision);
442 e_cal_backend_notify_property_changed (E_CAL_BACKEND (cbfile),
443 CAL_BACKEND_PROPERTY_REVISION,
449 /* Calendar backend methods */
451 /* Get_email_address handler for the file backend */
453 e_cal_backend_file_get_backend_property (ECalBackendSync *backend,
455 GCancellable *cancellable,
456 const gchar *prop_name,
460 gboolean processed = TRUE;
462 g_return_val_if_fail (prop_name != NULL, FALSE);
463 g_return_val_if_fail (prop_value != NULL, FALSE);
465 if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
466 *prop_value = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS ","
467 CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
468 CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED ","
469 CAL_STATIC_CAPABILITY_REMOVE_ONLY_THIS ","
470 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
471 CAL_STATIC_CAPABILITY_BULK_ADDS ","
472 CAL_STATIC_CAPABILITY_BULK_MODIFIES ","
473 CAL_STATIC_CAPABILITY_BULK_REMOVES);
474 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
475 g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
476 /* A file backend has no particular email address associated
477 * with it (although that would be a useful feature some day).
480 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
483 comp = e_cal_component_new ();
485 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
486 case ICAL_VEVENT_COMPONENT:
487 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
489 case ICAL_VTODO_COMPONENT:
490 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
492 case ICAL_VJOURNAL_COMPONENT:
493 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
496 g_object_unref (comp);
497 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
501 *prop_value = e_cal_component_get_as_string (comp);
502 g_object_unref (comp);
503 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_REVISION)) {
505 const gchar *revision;
507 prop = ensure_revision (E_CAL_BACKEND_FILE (backend));
508 revision = icalproperty_get_x (prop);
510 *prop_value = g_strdup (revision);
518 /* function to resolve timezones */
519 static icaltimezone *
520 resolve_tzid (const gchar *tzid,
523 icalcomponent *vcalendar_comp = user_data;
526 if (!tzid || !tzid[0])
528 else if (!strcmp (tzid, "UTC"))
529 return icaltimezone_get_utc_timezone ();
531 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
534 zone = icalcomponent_get_timezone (vcalendar_comp, tzid);
539 /* Checks if the specified component has a duplicated UID and if so changes it.
540 * UIDs may be shared between components if there is at most one component
541 * without RECURRENCE-ID (master) and all others have different RECURRENCE-ID
545 check_dup_uid (ECalBackendFile *cbfile,
548 ECalBackendFilePrivate *priv;
549 ECalBackendFileObject *obj_data;
550 const gchar *uid = NULL;
551 gchar *new_uid = NULL;
556 e_cal_component_get_uid (comp, &uid);
559 g_warning ("Checking for duplicate uid, the component does not have a valid UID skipping it\n");
563 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
565 return; /* Everything is fine */
567 rid = e_cal_component_get_recurid_as_string (comp);
569 /* new component has rid, must not be the same as in other detached recurrence */
570 if (!g_hash_table_lookup (obj_data->recurrences, rid))
573 /* new component has no rid, must not clash with existing master */
574 if (!obj_data->full_object)
579 g_message (G_STRLOC ": Got object with duplicated UID `%s' and rid `%s', changing it...",
583 new_uid = e_cal_component_gen_uid ();
584 e_cal_component_set_uid (comp, new_uid);
586 /* FIXME: I think we need to reset the SEQUENCE property and reset the
587 * CREATED/DTSTAMP/LAST-MODIFIED.
590 save (cbfile, FALSE);
597 static struct icaltimetype
598 get_rid_icaltime (ECalComponent *comp)
600 ECalComponentRange range;
601 struct icaltimetype tt;
603 e_cal_component_get_recurid (comp, &range);
604 if (!range.datetime.value)
605 return icaltime_null_time ();
606 tt = *range.datetime.value;
607 e_cal_component_free_range (&range);
612 /* Adds component to the interval tree
615 add_component_to_intervaltree (ECalBackendFile *cbfile,
618 time_t time_start = -1, time_end = -1;
619 ECalBackendFilePrivate *priv;
621 g_return_val_if_fail (cbfile != NULL, FALSE);
622 g_return_val_if_fail (comp != NULL, FALSE);
626 e_cal_util_get_component_occur_times (
627 comp, &time_start, &time_end,
628 resolve_tzid, priv->icalcomp, icaltimezone_get_utc_timezone (),
629 e_cal_backend_get_kind (E_CAL_BACKEND (cbfile)));
631 if (time_end != -1 && time_start > time_end)
632 g_print ("Bogus component %s\n", e_cal_component_get_as_string (comp));
634 e_intervaltree_insert (priv->interval_tree, time_start, time_end, comp);
640 remove_component_from_intervaltree (ECalBackendFile *cbfile,
643 const gchar *uid = NULL;
646 ECalBackendFilePrivate *priv;
648 g_return_val_if_fail (cbfile != NULL, FALSE);
649 g_return_val_if_fail (comp != NULL, FALSE);
653 rid = e_cal_component_get_recurid_as_string (comp);
654 e_cal_component_get_uid (comp, &uid);
655 res = e_intervaltree_remove (priv->interval_tree, uid, rid);
661 /* Tries to add an icalcomponent to the file backend. We only store the objects
662 * of the types we support; all others just remain in the toplevel component so
663 * that we don't lose them.
665 * The caller is responsible for ensuring that the component has a UID and that
666 * the UID is not in use already.
669 add_component (ECalBackendFile *cbfile,
671 gboolean add_to_toplevel)
673 ECalBackendFilePrivate *priv;
674 ECalBackendFileObject *obj_data;
675 const gchar *uid = NULL;
679 e_cal_component_get_uid (comp, &uid);
682 g_warning ("The component does not have a valid UID skipping it\n");
686 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
687 if (e_cal_component_is_instance (comp)) {
690 rid = e_cal_component_get_recurid_as_string (comp);
692 if (g_hash_table_lookup (obj_data->recurrences, rid)) {
693 g_warning (G_STRLOC ": Tried to add an already existing recurrence");
698 obj_data = g_new0 (ECalBackendFileObject, 1);
699 obj_data->full_object = NULL;
700 obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
701 g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
704 add_component_to_intervaltree (cbfile, comp);
705 g_hash_table_insert (obj_data->recurrences, rid, comp);
706 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
709 if (obj_data->full_object) {
710 g_warning (G_STRLOC ": Tried to add an already existing object");
714 obj_data->full_object = comp;
716 obj_data = g_new0 (ECalBackendFileObject, 1);
717 obj_data->full_object = comp;
718 obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
720 g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
721 add_component_to_intervaltree (cbfile, comp);
725 priv->comp = g_list_prepend (priv->comp, comp);
727 /* Put the object in the toplevel component if required */
729 if (add_to_toplevel) {
730 icalcomponent *icalcomp;
732 icalcomp = e_cal_component_get_icalcomponent (comp);
733 g_assert (icalcomp != NULL);
735 icalcomponent_add_component (priv->icalcomp, icalcomp);
739 /* g_hash_table_foreach_remove() callback to remove recurrences from the calendar */
741 remove_recurrence_cb (gpointer key,
746 icalcomponent *icalcomp;
747 ECalBackendFilePrivate *priv;
748 ECalComponent *comp = value;
749 ECalBackendFile *cbfile = data;
753 /* remove the recurrence from the top-level calendar */
754 icalcomp = e_cal_component_get_icalcomponent (comp);
755 g_assert (icalcomp != NULL);
757 if (!remove_component_from_intervaltree (cbfile, comp)) {
758 g_message (G_STRLOC " Could not remove component from interval tree!");
760 icalcomponent_remove_component (priv->icalcomp, icalcomp);
762 /* remove it from our mapping */
763 l = g_list_find (priv->comp, comp);
764 priv->comp = g_list_delete_link (priv->comp, l);
769 /* Removes a component from the backend's hash and lists. Does not perform
770 * notification on the clients. Also removes the component from the toplevel
774 remove_component (ECalBackendFile *cbfile,
776 ECalBackendFileObject *obj_data)
778 ECalBackendFilePrivate *priv;
779 icalcomponent *icalcomp;
784 /* Remove the icalcomp from the toplevel */
785 if (obj_data->full_object) {
786 icalcomp = e_cal_component_get_icalcomponent (obj_data->full_object);
787 g_assert (icalcomp != NULL);
789 icalcomponent_remove_component (priv->icalcomp, icalcomp);
791 /* Remove it from our mapping */
792 l = g_list_find (priv->comp, obj_data->full_object);
793 g_assert (l != NULL);
794 priv->comp = g_list_delete_link (priv->comp, l);
796 if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) {
797 g_message (G_STRLOC " Could not remove component from interval tree!");
801 /* remove the recurrences also */
802 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_recurrence_cb, cbfile);
804 g_hash_table_remove (priv->comp_uid_hash, uid);
809 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
811 scan_vcalendar (ECalBackendFile *cbfile)
813 ECalBackendFilePrivate *priv;
817 g_assert (priv->icalcomp != NULL);
818 g_assert (priv->comp_uid_hash != NULL);
820 for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_ANY_COMPONENT);
821 icalcompiter_deref (&iter) != NULL;
822 icalcompiter_next (&iter)) {
823 icalcomponent *icalcomp;
824 icalcomponent_kind kind;
827 icalcomp = icalcompiter_deref (&iter);
829 kind = icalcomponent_isa (icalcomp);
831 if (!(kind == ICAL_VEVENT_COMPONENT
832 || kind == ICAL_VTODO_COMPONENT
833 || kind == ICAL_VJOURNAL_COMPONENT))
836 comp = e_cal_component_new ();
838 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
841 check_dup_uid (cbfile, comp);
843 add_component (cbfile, comp, FALSE);
848 uri_to_path (ECalBackend *backend)
850 ECalBackendFile *cbfile;
851 ECalBackendFilePrivate *priv;
853 ESourceLocal *local_extension;
855 const gchar *extension_name;
856 const gchar *cache_dir;
857 gchar *filename = NULL;
859 cbfile = E_CAL_BACKEND_FILE (backend);
862 cache_dir = e_cal_backend_get_cache_dir (backend);
864 source = e_backend_get_source (E_BACKEND (backend));
866 extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
867 local_extension = e_source_get_extension (source, extension_name);
869 custom_file = e_source_local_dup_custom_file (local_extension);
870 if (custom_file != NULL) {
871 filename = g_file_get_path (custom_file);
872 g_object_unref (custom_file);
875 if (filename == NULL)
876 filename = g_build_filename (cache_dir, priv->file_name, NULL);
878 if (filename != NULL && *filename == '\0') {
887 refresh_thread_func (gpointer data)
889 ECalBackendFile *cbfile = data;
890 ECalBackendFilePrivate *priv;
892 ESourceLocal *extension;
895 const gchar *extension_name;
896 guint64 last_modified, modified;
898 g_return_val_if_fail (cbfile != NULL, NULL);
899 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
903 extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
904 source = e_backend_get_source (E_BACKEND (cbfile));
905 extension = e_source_get_extension (source, extension_name);
907 /* This returns a newly-created GFile. */
908 file = e_source_local_dup_custom_file (extension);
909 g_return_val_if_fail (G_IS_FILE (file), NULL);
911 info = g_file_query_info (
912 file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
913 G_FILE_QUERY_INFO_NONE, NULL, NULL);
914 g_return_val_if_fail (info != NULL, NULL);
916 last_modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
917 g_object_unref (info);
919 g_mutex_lock (&priv->refresh_lock);
920 while (!priv->refresh_thread_stop) {
921 g_cond_wait (priv->refresh_cond, &priv->refresh_lock);
923 g_rec_mutex_lock (&priv->idle_save_rmutex);
925 if (priv->refresh_skip > 0) {
926 priv->refresh_skip--;
927 g_rec_mutex_unlock (&priv->idle_save_rmutex);
931 if (priv->is_dirty) {
932 /* save before reload, if dirty */
933 if (priv->dirty_idle_id) {
934 g_source_remove (priv->dirty_idle_id);
935 priv->dirty_idle_id = 0;
937 save_file_when_idle (cbfile);
938 priv->refresh_skip = 0;
941 g_rec_mutex_unlock (&priv->idle_save_rmutex);
943 info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL);
947 modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
948 g_object_unref (info);
950 if (modified != last_modified) {
951 last_modified = modified;
952 e_cal_backend_file_reload (cbfile, NULL);
956 g_object_unref (file);
957 g_cond_signal (priv->refresh_gone_cond);
958 g_mutex_unlock (&priv->refresh_lock);
964 custom_file_changed (GFileMonitor *monitor,
967 GFileMonitorEvent event_type,
968 ECalBackendFilePrivate *priv)
970 if (priv->refresh_cond)
971 g_cond_signal (priv->refresh_cond);
975 prepare_refresh_data (ECalBackendFile *cbfile)
977 ECalBackendFilePrivate *priv;
979 ESourceLocal *local_extension;
981 const gchar *extension_name;
983 g_return_if_fail (cbfile != NULL);
987 g_mutex_lock (&priv->refresh_lock);
989 priv->refresh_thread_stop = FALSE;
990 priv->refresh_skip = 0;
992 source = e_backend_get_source (E_BACKEND (cbfile));
994 extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
995 local_extension = e_source_get_extension (source, extension_name);
997 custom_file = e_source_local_dup_custom_file (local_extension);
999 if (custom_file != NULL) {
1000 GError *error = NULL;
1002 priv->refresh_monitor = g_file_monitor_file (
1003 custom_file, G_FILE_MONITOR_WATCH_MOUNTS, NULL, &error);
1005 if (error == NULL) {
1007 priv->refresh_monitor, "changed",
1008 G_CALLBACK (custom_file_changed), priv);
1010 g_warning ("%s", error->message);
1011 g_error_free (error);
1014 g_object_unref (custom_file);
1017 if (priv->refresh_monitor) {
1020 priv->refresh_cond = g_new0 (GCond, 1);
1021 priv->refresh_gone_cond = g_new0 (GCond, 1);
1023 thread = g_thread_new (NULL, refresh_thread_func, cbfile);
1024 g_thread_unref (thread);
1027 g_mutex_unlock (&priv->refresh_lock);
1031 free_refresh_data (ECalBackendFile *cbfile)
1033 ECalBackendFilePrivate *priv;
1035 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
1037 priv = cbfile->priv;
1039 g_mutex_lock (&priv->refresh_lock);
1041 if (priv->refresh_monitor)
1042 g_object_unref (priv->refresh_monitor);
1043 priv->refresh_monitor = NULL;
1045 if (priv->refresh_cond) {
1046 priv->refresh_thread_stop = TRUE;
1047 g_cond_signal (priv->refresh_cond);
1048 g_cond_wait (priv->refresh_gone_cond, &priv->refresh_lock);
1050 g_cond_clear (priv->refresh_cond);
1051 priv->refresh_cond = NULL;
1052 g_cond_clear (priv->refresh_gone_cond);
1053 priv->refresh_gone_cond = NULL;
1056 priv->refresh_skip = 0;
1058 g_mutex_unlock (&priv->refresh_lock);
1061 /* Parses an open iCalendar file and loads it into the backend */
1063 open_cal (ECalBackendFile *cbfile,
1064 const gchar *uristr,
1067 ECalBackendFilePrivate *priv;
1068 icalcomponent *icalcomp;
1070 priv = cbfile->priv;
1072 free_refresh_data (cbfile);
1074 icalcomp = e_cal_util_parse_ics_file (uristr);
1076 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, "Cannot parse ISC file '%s'", uristr));
1080 /* FIXME: should we try to demangle XROOT components and
1081 * individual components as well?
1084 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
1085 icalcomponent_free (icalcomp);
1087 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, "File '%s' is not v VCALENDAR component", uristr));
1091 priv->icalcomp = icalcomp;
1092 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1094 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1095 priv->interval_tree = e_intervaltree_new ();
1096 scan_vcalendar (cbfile);
1098 prepare_refresh_data (cbfile);
1103 ECalBackend *backend;
1104 GHashTable *old_uid_hash;
1105 GHashTable *new_uid_hash;
1107 BackendDeltaContext;
1110 notify_removals_cb (gpointer key,
1114 BackendDeltaContext *context = data;
1115 const gchar *uid = key;
1116 ECalBackendFileObject *old_obj_data = value;
1118 if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
1119 ECalComponentId *id;
1121 /* Object was removed */
1123 if (!old_obj_data->full_object)
1126 id = e_cal_component_get_id (old_obj_data->full_object);
1128 e_cal_backend_notify_component_removed (context->backend, id, old_obj_data->full_object, NULL);
1130 e_cal_component_free_id (id);
1135 notify_adds_modifies_cb (gpointer key,
1139 BackendDeltaContext *context = data;
1140 const gchar *uid = key;
1141 ECalBackendFileObject *new_obj_data = value;
1142 ECalBackendFileObject *old_obj_data;
1144 old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
1146 if (!old_obj_data) {
1147 /* Object was added */
1148 if (!new_obj_data->full_object)
1151 e_cal_backend_notify_component_created (context->backend, new_obj_data->full_object);
1153 gchar *old_obj_str, *new_obj_str;
1155 if (!old_obj_data->full_object || !new_obj_data->full_object)
1158 /* There should be better ways to compare an icalcomponent
1159 * than serializing and comparing the strings...
1161 old_obj_str = e_cal_component_get_as_string (old_obj_data->full_object);
1162 new_obj_str = e_cal_component_get_as_string (new_obj_data->full_object);
1163 if (old_obj_str && new_obj_str && strcmp (old_obj_str, new_obj_str) != 0) {
1164 /* Object was modified */
1165 e_cal_backend_notify_component_modified (context->backend, old_obj_data->full_object, new_obj_data->full_object);
1168 g_free (old_obj_str);
1169 g_free (new_obj_str);
1174 notify_changes (ECalBackendFile *cbfile,
1175 GHashTable *old_uid_hash,
1176 GHashTable *new_uid_hash)
1178 BackendDeltaContext context;
1180 context.backend = E_CAL_BACKEND (cbfile);
1181 context.old_uid_hash = old_uid_hash;
1182 context.new_uid_hash = new_uid_hash;
1184 g_hash_table_foreach (old_uid_hash, (GHFunc) notify_removals_cb, &context);
1185 g_hash_table_foreach (new_uid_hash, (GHFunc) notify_adds_modifies_cb, &context);
1189 reload_cal (ECalBackendFile *cbfile,
1190 const gchar *uristr,
1193 ECalBackendFilePrivate *priv;
1194 icalcomponent *icalcomp, *icalcomp_old;
1195 GHashTable *comp_uid_hash_old;
1197 priv = cbfile->priv;
1199 icalcomp = e_cal_util_parse_ics_file (uristr);
1201 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, "Cannot parse ISC file '%s'", uristr));
1205 /* FIXME: should we try to demangle XROOT components and
1206 * individual components as well?
1209 if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
1210 icalcomponent_free (icalcomp);
1212 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, "File '%s' is not v VCALENDAR component", uristr));
1216 /* Keep old data for comparison - free later */
1218 icalcomp_old = priv->icalcomp;
1219 priv->icalcomp = NULL;
1221 comp_uid_hash_old = priv->comp_uid_hash;
1222 priv->comp_uid_hash = NULL;
1224 /* Load new calendar */
1226 free_calendar_data (cbfile);
1228 priv->icalcomp = icalcomp;
1230 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1231 priv->interval_tree = e_intervaltree_new ();
1232 scan_vcalendar (cbfile);
1234 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1236 /* Compare old and new versions of calendar */
1238 notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
1242 free_calendar_components (comp_uid_hash_old, icalcomp_old);
1246 create_cal (ECalBackendFile *cbfile,
1247 const gchar *uristr,
1251 ECalBackendFilePrivate *priv;
1253 free_refresh_data (cbfile);
1255 priv = cbfile->priv;
1257 /* Create the directory to contain the file */
1258 dirname = g_path_get_dirname (uristr);
1259 if (g_mkdir_with_parents (dirname, 0700) != 0) {
1261 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1267 /* Create the new calendar information */
1268 priv->icalcomp = e_cal_util_new_top_level ();
1270 /* Create our internal data */
1271 priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1272 priv->interval_tree = e_intervaltree_new ();
1274 priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1276 save (cbfile, TRUE);
1278 prepare_refresh_data (cbfile);
1282 get_uri_string (ECalBackend *backend)
1284 gchar *str_uri, *full_uri;
1286 str_uri = uri_to_path (backend);
1287 full_uri = g_uri_unescape_string (str_uri, "");
1294 source_changed_cb (ESource *source,
1295 ECalBackend *backend)
1297 ECalBackendFile *cbfile;
1298 ESourceLocal *extension;
1299 const gchar *extension_name;
1302 g_return_if_fail (source != NULL);
1303 g_return_if_fail (E_IS_CAL_BACKEND (backend));
1305 cbfile = E_CAL_BACKEND_FILE (backend);
1307 extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
1308 extension = e_source_get_extension (source, extension_name);
1310 if (e_source_local_get_custom_file (extension) == NULL)
1313 read_only = !e_source_get_writable (source);
1315 if (read_only != cbfile->priv->read_only) {
1316 cbfile->priv->read_only = read_only;
1317 if (e_source_get_writable (source)) {
1318 gchar *str_uri = get_uri_string (backend);
1320 g_return_if_fail (str_uri != NULL);
1322 cbfile->priv->read_only = g_access (str_uri, W_OK) != 0;
1327 e_cal_backend_notify_readonly (backend, cbfile->priv->read_only);
1331 /* Open handler for the file backend */
1333 e_cal_backend_file_open (ECalBackendSync *backend,
1335 GCancellable *cancellable,
1336 gboolean only_if_exists,
1339 ECalBackendFile *cbfile;
1340 ECalBackendFilePrivate *priv;
1344 cbfile = E_CAL_BACKEND_FILE (backend);
1345 priv = cbfile->priv;
1346 g_rec_mutex_lock (&priv->idle_save_rmutex);
1348 /* Claim a succesful open if we are already open */
1349 if (priv->path && priv->comp_uid_hash) {
1354 str_uri = get_uri_string (E_CAL_BACKEND (backend));
1356 err = EDC_ERROR_NO_URI ();
1360 priv->read_only = FALSE;
1361 if (g_access (str_uri, R_OK) == 0) {
1362 open_cal (cbfile, str_uri, &err);
1363 if (g_access (str_uri, W_OK) != 0)
1364 priv->read_only = TRUE;
1367 err = EDC_ERROR (NoSuchCal);
1369 create_cal (cbfile, str_uri, &err);
1373 if (!priv->read_only) {
1376 source = e_backend_get_source (E_BACKEND (backend));
1380 G_CALLBACK (source_changed_cb), backend);
1382 if (!e_source_get_writable (source))
1383 priv->read_only = TRUE;
1390 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1391 e_cal_backend_notify_readonly (E_CAL_BACKEND (backend), priv->read_only);
1392 e_cal_backend_notify_online (E_CAL_BACKEND (backend), TRUE);
1395 g_propagate_error (perror, g_error_copy (err));
1397 e_cal_backend_notify_opened (E_CAL_BACKEND (backend), err);
1401 add_detached_recur_to_vcalendar (gpointer key,
1405 ECalComponent *recurrence = value;
1406 icalcomponent *vcalendar = user_data;
1408 icalcomponent_add_component (
1410 icalcomponent_new_clone (e_cal_component_get_icalcomponent (recurrence)));
1413 /* Get_object_component handler for the file backend */
1415 e_cal_backend_file_get_object (ECalBackendSync *backend,
1417 GCancellable *cancellable,
1423 ECalBackendFile *cbfile;
1424 ECalBackendFilePrivate *priv;
1425 ECalBackendFileObject *obj_data;
1427 cbfile = E_CAL_BACKEND_FILE (backend);
1428 priv = cbfile->priv;
1430 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, InvalidObject);
1431 e_return_data_cal_error_if_fail (uid != NULL, ObjectNotFound);
1432 g_assert (priv->comp_uid_hash != NULL);
1434 g_rec_mutex_lock (&priv->idle_save_rmutex);
1436 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1438 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1439 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1444 ECalComponent *comp;
1446 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1448 *object = e_cal_component_get_as_string (comp);
1450 icalcomponent *icalcomp;
1451 struct icaltimetype itt;
1453 if (!obj_data->full_object) {
1454 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1455 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1459 itt = icaltime_from_string (rid);
1460 icalcomp = e_cal_util_construct_instance (
1461 e_cal_component_get_icalcomponent (obj_data->full_object),
1464 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1465 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1469 *object = icalcomponent_as_ical_string_r (icalcomp);
1471 icalcomponent_free (icalcomp);
1474 if (g_hash_table_size (obj_data->recurrences) > 0) {
1475 icalcomponent *icalcomp;
1477 /* if we have detached recurrences, return a VCALENDAR */
1478 icalcomp = e_cal_util_new_top_level ();
1480 /* detached recurrences don't have full_object */
1481 if (obj_data->full_object)
1482 icalcomponent_add_component (
1484 icalcomponent_new_clone (e_cal_component_get_icalcomponent (obj_data->full_object)));
1486 /* add all detached recurrences */
1487 g_hash_table_foreach (obj_data->recurrences, (GHFunc) add_detached_recur_to_vcalendar, icalcomp);
1489 *object = icalcomponent_as_ical_string_r (icalcomp);
1491 icalcomponent_free (icalcomp);
1492 } else if (obj_data->full_object)
1493 *object = e_cal_component_get_as_string (obj_data->full_object);
1496 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1499 /* Add_timezone handler for the file backend */
1501 e_cal_backend_file_add_timezone (ECalBackendSync *backend,
1503 GCancellable *cancellable,
1507 icalcomponent *tz_comp;
1508 ECalBackendFile *cbfile;
1509 ECalBackendFilePrivate *priv;
1511 cbfile = (ECalBackendFile *) backend;
1513 e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), InvalidArg);
1514 e_return_data_cal_error_if_fail (tzobj != NULL, InvalidArg);
1516 priv = cbfile->priv;
1518 tz_comp = icalparser_parse_string (tzobj);
1520 g_propagate_error (error, EDC_ERROR (InvalidObject));
1524 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
1527 zone = icaltimezone_new ();
1528 icaltimezone_set_component (zone, tz_comp);
1530 g_rec_mutex_lock (&priv->idle_save_rmutex);
1531 if (!icalcomponent_get_timezone (priv->icalcomp,
1532 icaltimezone_get_tzid (zone))) {
1533 icalcomponent_add_component (priv->icalcomp, tz_comp);
1535 save (cbfile, TRUE);
1537 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1539 icaltimezone_free (zone, 1);
1545 gboolean search_needed;
1547 ECalBackendSExp *obj_sexp;
1548 ECalBackend *backend;
1554 match_object_sexp_to_component (gpointer value,
1557 ECalComponent * comp = value;
1558 MatchObjectData *match_data = data;
1561 e_cal_component_get_uid (comp, &uid);
1563 g_return_if_fail (comp != NULL);
1565 g_return_if_fail (match_data->backend != NULL);
1567 if ((!match_data->search_needed) ||
1568 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
1569 if (match_data->as_string)
1570 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (comp));
1572 match_data->comps_list = g_slist_prepend (match_data->comps_list, comp);
1577 match_recurrence_sexp (gpointer key,
1581 ECalComponent *comp = value;
1582 MatchObjectData *match_data = data;
1584 if ((!match_data->search_needed) ||
1585 (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, match_data->backend))) {
1586 if (match_data->as_string)
1587 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (comp));
1589 match_data->comps_list = g_slist_prepend (match_data->comps_list, comp);
1594 match_object_sexp (gpointer key,
1598 ECalBackendFileObject *obj_data = value;
1599 MatchObjectData *match_data = data;
1601 if (obj_data->full_object) {
1602 if ((!match_data->search_needed) ||
1603 (e_cal_backend_sexp_match_comp (match_data->obj_sexp,
1604 obj_data->full_object,
1605 match_data->backend))) {
1606 if (match_data->as_string)
1607 match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (obj_data->full_object));
1609 match_data->comps_list = g_slist_prepend (match_data->comps_list, obj_data->full_object);
1613 /* match also recurrences */
1614 g_hash_table_foreach (obj_data->recurrences,
1615 (GHFunc) match_recurrence_sexp,
1619 /* Get_objects_in_range handler for the file backend */
1621 e_cal_backend_file_get_object_list (ECalBackendSync *backend,
1623 GCancellable *cancellable,
1628 ECalBackendFile *cbfile;
1629 ECalBackendFilePrivate *priv;
1630 MatchObjectData match_data = { 0, };
1631 time_t occur_start = -1, occur_end = -1;
1632 gboolean prunning_by_time;
1633 GList * objs_occuring_in_tw;
1634 cbfile = E_CAL_BACKEND_FILE (backend);
1635 priv = cbfile->priv;
1637 d (g_message (G_STRLOC ": Getting object list (%s)", sexp));
1639 match_data.search_needed = TRUE;
1640 match_data.query = sexp;
1641 match_data.comps_list = NULL;
1642 match_data.as_string = TRUE;
1643 match_data.backend = E_CAL_BACKEND (backend);
1645 if (!strcmp (sexp, "#t"))
1646 match_data.search_needed = FALSE;
1648 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1649 if (!match_data.obj_sexp) {
1650 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
1654 g_rec_mutex_lock (&priv->idle_save_rmutex);
1656 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (
1657 match_data.obj_sexp,
1661 objs_occuring_in_tw = NULL;
1663 if (!prunning_by_time) {
1664 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
1667 objs_occuring_in_tw = e_intervaltree_search (
1668 priv->interval_tree,
1669 occur_start, occur_end);
1671 g_list_foreach (objs_occuring_in_tw, (GFunc) match_object_sexp_to_component,
1675 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1677 *objects = g_slist_reverse (match_data.comps_list);
1679 if (objs_occuring_in_tw) {
1680 g_list_foreach (objs_occuring_in_tw, (GFunc) g_object_unref, NULL);
1681 g_list_free (objs_occuring_in_tw);
1684 g_object_unref (match_data.obj_sexp);
1688 add_attach_uris (GSList **attachment_uris,
1689 icalcomponent *icalcomp)
1693 g_return_if_fail (attachment_uris != NULL);
1694 g_return_if_fail (icalcomp != NULL);
1696 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
1698 prop = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
1699 icalattach *attach = icalproperty_get_attach (prop);
1701 if (attach && icalattach_get_is_url (attach)) {
1704 url = icalattach_get_url (attach);
1709 buf_size = strlen (url);
1710 buf = g_malloc0 (buf_size + 1);
1712 icalvalue_decode_ical_string (url, buf, buf_size);
1714 *attachment_uris = g_slist_prepend (*attachment_uris, g_strdup (buf));
1723 add_detached_recur_attach_uris (gpointer key,
1727 ECalComponent *recurrence = value;
1728 GSList **attachment_uris = user_data;
1730 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (recurrence));
1733 /* Gets the list of attachments */
1735 e_cal_backend_file_get_attachment_uris (ECalBackendSync *backend,
1737 GCancellable *cancellable,
1740 GSList **attachment_uris,
1743 ECalBackendFile *cbfile;
1744 ECalBackendFilePrivate *priv;
1745 ECalBackendFileObject *obj_data;
1747 cbfile = E_CAL_BACKEND_FILE (backend);
1748 priv = cbfile->priv;
1750 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, InvalidObject);
1751 e_return_data_cal_error_if_fail (uid != NULL, ObjectNotFound);
1752 e_return_data_cal_error_if_fail (attachment_uris != NULL, InvalidArg);
1753 g_assert (priv->comp_uid_hash != NULL);
1755 g_rec_mutex_lock (&priv->idle_save_rmutex);
1757 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1759 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1760 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1765 ECalComponent *comp;
1767 comp = g_hash_table_lookup (obj_data->recurrences, rid);
1769 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (comp));
1771 icalcomponent *icalcomp;
1772 struct icaltimetype itt;
1774 if (!obj_data->full_object) {
1775 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1776 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1780 itt = icaltime_from_string (rid);
1781 icalcomp = e_cal_util_construct_instance (
1782 e_cal_component_get_icalcomponent (obj_data->full_object),
1785 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1786 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
1790 add_attach_uris (attachment_uris, icalcomp);
1792 icalcomponent_free (icalcomp);
1795 if (g_hash_table_size (obj_data->recurrences) > 0) {
1796 /* detached recurrences don't have full_object */
1797 if (obj_data->full_object)
1798 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (obj_data->full_object));
1800 /* add all detached recurrences */
1801 g_hash_table_foreach (obj_data->recurrences, add_detached_recur_attach_uris, attachment_uris);
1802 } else if (obj_data->full_object)
1803 add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (obj_data->full_object));
1806 *attachment_uris = g_slist_reverse (*attachment_uris);
1808 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1811 /* get_query handler for the file backend */
1813 e_cal_backend_file_start_view (ECalBackend *backend,
1814 EDataCalView *query)
1816 ECalBackendFile *cbfile;
1817 ECalBackendFilePrivate *priv;
1818 ECalBackendSExp *sexp;
1819 MatchObjectData match_data = { 0, };
1820 time_t occur_start = -1, occur_end = -1;
1821 gboolean prunning_by_time;
1822 GList * objs_occuring_in_tw;
1823 cbfile = E_CAL_BACKEND_FILE (backend);
1824 priv = cbfile->priv;
1826 sexp = e_data_cal_view_get_sexp (query);
1828 d (g_message (G_STRLOC ": Starting query (%s)", e_cal_backend_sexp_text (sexp)));
1830 /* try to match all currently existing objects */
1831 match_data.search_needed = TRUE;
1832 match_data.query = e_cal_backend_sexp_text (sexp);
1833 match_data.comps_list = NULL;
1834 match_data.as_string = FALSE;
1835 match_data.backend = backend;
1836 match_data.obj_sexp = e_data_cal_view_get_sexp (query);
1837 match_data.view = query;
1839 if (!strcmp (match_data.query, "#t"))
1840 match_data.search_needed = FALSE;
1842 if (!match_data.obj_sexp) {
1843 GError *error = EDC_ERROR (InvalidQuery);
1844 e_data_cal_view_notify_complete (query, error);
1845 g_error_free (error);
1848 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (
1849 match_data.obj_sexp,
1853 objs_occuring_in_tw = NULL;
1855 g_rec_mutex_lock (&priv->idle_save_rmutex);
1857 if (!prunning_by_time) {
1859 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
1863 FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES, "---;%p;QUERY-ITEMS;%s;%s;%d", query,
1864 e_cal_backend_sexp_text (sexp), G_OBJECT_TYPE_NAME (backend),
1865 g_hash_table_size (priv->comp_uid_hash));
1867 /* matches objects in new "interval tree" way */
1868 /* events occuring in time window */
1869 objs_occuring_in_tw = e_intervaltree_search (priv->interval_tree, occur_start, occur_end);
1871 g_list_foreach (objs_occuring_in_tw, (GFunc) match_object_sexp_to_component,
1875 FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES, "---;%p;QUERY-ITEMS;%s;%s;%d", query,
1876 e_cal_backend_sexp_text (sexp), G_OBJECT_TYPE_NAME (backend),
1877 g_list_length (objs_occuring_in_tw));
1880 g_rec_mutex_unlock (&priv->idle_save_rmutex);
1882 /* notify listeners of all objects */
1883 if (match_data.comps_list) {
1884 match_data.comps_list = g_slist_reverse (match_data.comps_list);
1886 e_data_cal_view_notify_components_added (query, match_data.comps_list);
1889 g_slist_free (match_data.comps_list);
1892 if (objs_occuring_in_tw) {
1893 g_list_foreach (objs_occuring_in_tw, (GFunc) g_object_unref, NULL);
1894 g_list_free (objs_occuring_in_tw);
1897 e_data_cal_view_notify_complete (query, NULL /* Success */);
1901 free_busy_instance (ECalComponent *comp,
1902 time_t instance_start,
1903 time_t instance_end,
1906 icalcomponent *vfb = data;
1908 icalparameter *param;
1909 struct icalperiodtype ipt;
1910 icaltimezone *utc_zone;
1912 utc_zone = icaltimezone_get_utc_timezone ();
1914 ipt.start = icaltime_from_timet_with_zone (instance_start, FALSE, utc_zone);
1915 ipt.end = icaltime_from_timet_with_zone (instance_end, FALSE, utc_zone);
1916 ipt.duration = icaldurationtype_null_duration ();
1918 /* add busy information to the vfb component */
1919 prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
1920 icalproperty_set_freebusy (prop, ipt);
1922 param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
1923 icalproperty_add_parameter (prop, param);
1925 icalcomponent_add_property (vfb, prop);
1930 static icalcomponent *
1931 create_user_free_busy (ECalBackendFile *cbfile,
1932 const gchar *address,
1937 ECalBackendFilePrivate *priv;
1940 icaltimezone *utc_zone;
1941 ECalBackendSExp *obj_sexp;
1942 gchar *query, *iso_start, *iso_end;
1944 priv = cbfile->priv;
1946 /* create the (unique) VFREEBUSY object that we'll return */
1947 vfb = icalcomponent_new_vfreebusy ();
1948 if (address != NULL) {
1950 icalparameter *param;
1952 prop = icalproperty_new_organizer (address);
1953 if (prop != NULL && cn != NULL) {
1954 param = icalparameter_new_cn (cn);
1955 icalproperty_add_parameter (prop, param);
1958 icalcomponent_add_property (vfb, prop);
1960 utc_zone = icaltimezone_get_utc_timezone ();
1961 icalcomponent_set_dtstart (vfb, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
1962 icalcomponent_set_dtend (vfb, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
1964 /* add all objects in the given interval */
1965 iso_start = isodate_from_time_t (start);
1966 iso_end = isodate_from_time_t (end);
1967 query = g_strdup_printf (
1968 "occur-in-time-range? (make-time \"%s\") (make-time \"%s\")",
1969 iso_start, iso_end);
1970 obj_sexp = e_cal_backend_sexp_new (query);
1978 for (l = priv->comp; l; l = l->next) {
1979 ECalComponent *comp = l->data;
1980 icalcomponent *icalcomp, *vcalendar_comp;
1983 icalcomp = e_cal_component_get_icalcomponent (comp);
1987 /* If the event is TRANSPARENT, skip it. */
1988 prop = icalcomponent_get_first_property (
1990 ICAL_TRANSP_PROPERTY);
1992 icalproperty_transp transp_val = icalproperty_get_transp (prop);
1993 if (transp_val == ICAL_TRANSP_TRANSPARENT ||
1994 transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
1998 if (!e_cal_backend_sexp_match_comp (obj_sexp, l->data, E_CAL_BACKEND (cbfile)))
2001 vcalendar_comp = icalcomponent_get_parent (icalcomp);
2002 e_cal_recur_generate_instances (
2008 icaltimezone_get_utc_timezone ());
2010 g_object_unref (obj_sexp);
2015 /* Get_free_busy handler for the file backend */
2017 e_cal_backend_file_get_free_busy (ECalBackendSync *backend,
2019 GCancellable *cancellable,
2020 const GSList *users,
2026 ESourceRegistry *registry;
2027 ECalBackendFile *cbfile;
2028 ECalBackendFilePrivate *priv;
2029 gchar *address, *name;
2034 cbfile = E_CAL_BACKEND_FILE (backend);
2035 priv = cbfile->priv;
2037 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
2038 e_return_data_cal_error_if_fail (start != -1 && end != -1, InvalidRange);
2039 e_return_data_cal_error_if_fail (start <= end, InvalidRange);
2041 g_rec_mutex_lock (&priv->idle_save_rmutex);
2045 registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
2047 if (users == NULL) {
2048 if (e_cal_backend_mail_account_get_default (registry, &address, &name)) {
2049 vfb = create_user_free_busy (cbfile, address, name, start, end);
2050 calobj = icalcomponent_as_ical_string_r (vfb);
2051 *freebusy = g_slist_append (*freebusy, calobj);
2052 icalcomponent_free (vfb);
2057 for (l = users; l != NULL; l = l->next ) {
2059 if (e_cal_backend_mail_account_is_valid (registry, address, &name)) {
2060 vfb = create_user_free_busy (cbfile, address, name, start, end);
2061 calobj = icalcomponent_as_ical_string_r (vfb);
2062 *freebusy = g_slist_append (*freebusy, calobj);
2063 icalcomponent_free (vfb);
2069 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2072 static icaltimezone *
2073 e_cal_backend_file_internal_get_timezone (ECalBackend *backend,
2076 ECalBackendFile *cbfile;
2077 ECalBackendFilePrivate *priv;
2080 cbfile = E_CAL_BACKEND_FILE (backend);
2081 priv = cbfile->priv;
2083 g_return_val_if_fail (priv->icalcomp != NULL, NULL);
2085 g_rec_mutex_lock (&priv->idle_save_rmutex);
2087 if (!strcmp (tzid, "UTC"))
2088 zone = icaltimezone_get_utc_timezone ();
2090 zone = icalcomponent_get_timezone (priv->icalcomp, tzid);
2092 if (!zone && E_CAL_BACKEND_CLASS (e_cal_backend_file_parent_class)->internal_get_timezone)
2093 zone = E_CAL_BACKEND_CLASS (e_cal_backend_file_parent_class)->internal_get_timezone (backend, tzid);
2096 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2101 sanitize_component (ECalBackendFile *cbfile,
2102 ECalComponent *comp)
2104 ECalComponentDateTime dt;
2107 /* Check dtstart, dtend and due's timezone, and convert it to local
2108 * default timezone if the timezone is not in our builtin timezone
2110 e_cal_component_get_dtstart (comp, &dt);
2111 if (dt.value && dt.tzid) {
2112 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *) cbfile, dt.tzid);
2114 g_free ((gchar *) dt.tzid);
2115 dt.tzid = g_strdup ("UTC");
2116 e_cal_component_set_dtstart (comp, &dt);
2119 e_cal_component_free_datetime (&dt);
2121 e_cal_component_get_dtend (comp, &dt);
2122 if (dt.value && dt.tzid) {
2123 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *) cbfile, dt.tzid);
2125 g_free ((gchar *) dt.tzid);
2126 dt.tzid = g_strdup ("UTC");
2127 e_cal_component_set_dtend (comp, &dt);
2130 e_cal_component_free_datetime (&dt);
2132 e_cal_component_get_due (comp, &dt);
2133 if (dt.value && dt.tzid) {
2134 zone = e_cal_backend_file_internal_get_timezone ((ECalBackend *) cbfile, dt.tzid);
2136 g_free ((gchar *) dt.tzid);
2137 dt.tzid = g_strdup ("UTC");
2138 e_cal_component_set_due (comp, &dt);
2141 e_cal_component_free_datetime (&dt);
2142 e_cal_component_abort_sequence (comp);
2147 e_cal_backend_file_create_objects (ECalBackendSync *backend,
2149 GCancellable *cancellable,
2150 const GSList *in_calobjs,
2152 GSList **new_components,
2155 ECalBackendFile *cbfile;
2156 ECalBackendFilePrivate *priv;
2157 GSList *icalcomps = NULL;
2160 cbfile = E_CAL_BACKEND_FILE (backend);
2161 priv = cbfile->priv;
2163 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
2164 e_return_data_cal_error_if_fail (in_calobjs != NULL, ObjectNotFound);
2165 e_return_data_cal_error_if_fail (new_components != NULL, ObjectNotFound);
2170 g_rec_mutex_lock (&priv->idle_save_rmutex);
2172 /* First step, parse input strings and do uid verification: may fail */
2173 for (l = in_calobjs; l; l = l->next) {
2174 icalcomponent *icalcomp;
2175 const gchar *comp_uid;
2177 /* Parse the icalendar text */
2178 icalcomp = icalparser_parse_string ((gchar *) l->data);
2180 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2181 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2182 g_propagate_error (error, EDC_ERROR (InvalidObject));
2186 /* Append icalcomponent to icalcomps */
2187 icalcomps = g_slist_prepend (icalcomps, icalcomp);
2189 /* Check kind with the parent */
2190 if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2191 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2192 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2193 g_propagate_error (error, EDC_ERROR (InvalidObject));
2198 comp_uid = icalcomponent_get_uid (icalcomp);
2202 new_uid = e_cal_component_gen_uid ();
2204 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2205 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2206 g_propagate_error (error, EDC_ERROR (InvalidObject));
2210 icalcomponent_set_uid (icalcomp, new_uid);
2211 comp_uid = icalcomponent_get_uid (icalcomp);
2216 /* check that the object is not in our cache */
2217 if (uid_in_use (cbfile, comp_uid)) {
2218 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2219 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2220 g_propagate_error (error, EDC_ERROR (ObjectIdAlreadyExists));
2225 icalcomps = g_slist_reverse (icalcomps);
2227 /* Second step, add the objects */
2228 for (l = icalcomps; l; l = l->next) {
2229 ECalComponent *comp;
2230 struct icaltimetype current;
2231 icalcomponent *icalcomp = l->data;
2233 /* Create the cal component */
2234 comp = e_cal_component_new ();
2235 e_cal_component_set_icalcomponent (comp, icalcomp);
2237 /* Set the created and last modified times on the component */
2238 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2239 e_cal_component_set_created (comp, ¤t);
2240 e_cal_component_set_last_modified (comp, ¤t);
2242 /* sanitize the component*/
2243 sanitize_component (cbfile, comp);
2245 /* Add the object */
2246 add_component (cbfile, comp, TRUE);
2248 /* Keep the UID and the modified component to return them later */
2250 *uids = g_slist_prepend (*uids, g_strdup (icalcomponent_get_uid (icalcomp)));
2252 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
2255 g_slist_free (icalcomps);
2258 save (cbfile, TRUE);
2260 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2263 *uids = g_slist_reverse (*uids);
2265 *new_components = g_slist_reverse (*new_components);
2269 ECalBackendFile *cbfile;
2270 ECalBackendFileObject *obj_data;
2273 } RemoveRecurrenceData;
2276 remove_object_instance_cb (gpointer key,
2280 time_t fromtt, instancett;
2281 ECalComponent *instance = value;
2282 RemoveRecurrenceData *rrdata = user_data;
2284 fromtt = icaltime_as_timet (icaltime_from_string (rrdata->rid));
2285 instancett = icaltime_as_timet (get_rid_icaltime (instance));
2287 if (fromtt > 0 && instancett > 0) {
2288 if ((rrdata->mod == CALOBJ_MOD_THISANDPRIOR && instancett <= fromtt) ||
2289 (rrdata->mod == CALOBJ_MOD_THISANDFUTURE && instancett >= fromtt)) {
2290 /* remove the component from our data */
2291 icalcomponent_remove_component (
2292 rrdata->cbfile->priv->icalcomp,
2293 e_cal_component_get_icalcomponent (instance));
2294 rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
2296 rrdata->obj_data->recurrences_list = g_list_remove (rrdata->obj_data->recurrences_list, instance);
2306 e_cal_backend_file_modify_objects (ECalBackendSync *backend,
2308 GCancellable *cancellable,
2309 const GSList *calobjs,
2311 GSList **old_components,
2312 GSList **new_components,
2315 ECalBackendFile *cbfile;
2316 ECalBackendFilePrivate *priv;
2317 GSList *icalcomps = NULL;
2320 cbfile = E_CAL_BACKEND_FILE (backend);
2321 priv = cbfile->priv;
2323 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
2324 e_return_data_cal_error_if_fail (calobjs != NULL, ObjectNotFound);
2326 case CALOBJ_MOD_THIS:
2327 case CALOBJ_MOD_THISANDPRIOR:
2328 case CALOBJ_MOD_THISANDFUTURE:
2329 case CALOBJ_MOD_ALL:
2332 g_propagate_error (error, EDC_ERROR (NotSupported));
2337 *old_components = NULL;
2339 *new_components = NULL;
2341 g_rec_mutex_lock (&priv->idle_save_rmutex);
2343 /* First step, parse input strings and do uid verification: may fail */
2344 for (l = calobjs; l; l = l->next) {
2345 const gchar *comp_uid;
2346 icalcomponent *icalcomp;
2348 /* Parse the icalendar text */
2349 icalcomp = icalparser_parse_string (l->data);
2351 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2352 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2353 g_propagate_error (error, EDC_ERROR (InvalidObject));
2357 icalcomps = g_slist_prepend (icalcomps, icalcomp);
2359 /* Check kind with the parent */
2360 if (icalcomponent_isa (icalcomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2361 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2362 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2363 g_propagate_error (error, EDC_ERROR (InvalidObject));
2368 comp_uid = icalcomponent_get_uid (icalcomp);
2370 /* Get the object from our cache */
2371 if (!g_hash_table_lookup (priv->comp_uid_hash, comp_uid)) {
2372 g_slist_free_full (icalcomps, (GDestroyNotify) icalcomponent_free);
2373 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2374 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2379 icalcomps = g_slist_reverse (icalcomps);
2381 /* Second step, update the objects */
2382 for (l = icalcomps; l; l = l->next) {
2383 struct icaltimetype current;
2384 RemoveRecurrenceData rrdata;
2385 GList *detached = NULL;
2388 const gchar *comp_uid;
2389 icalcomponent * icalcomp = l->data;
2390 ECalComponent *comp, *recurrence;
2391 ECalBackendFileObject *obj_data;
2393 comp_uid = icalcomponent_get_uid (icalcomp);
2394 obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid);
2396 /* Create the cal component */
2397 comp = e_cal_component_new ();
2398 e_cal_component_set_icalcomponent (comp, icalcomp);
2400 /* Set the last modified time on the component */
2401 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2402 e_cal_component_set_last_modified (comp, ¤t);
2404 /* sanitize the component*/
2405 sanitize_component (cbfile, comp);
2406 rid = e_cal_component_get_recurid_as_string (comp);
2408 /* handle mod_type */
2410 case CALOBJ_MOD_THIS :
2411 if (!rid || !*rid) {
2413 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2415 /* replace only the full object */
2416 if (obj_data->full_object) {
2417 icalcomponent_remove_component (
2419 e_cal_component_get_icalcomponent (obj_data->full_object));
2420 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2422 g_object_unref (obj_data->full_object);
2425 /* add the new object */
2426 obj_data->full_object = comp;
2428 icalcomponent_add_component (
2430 e_cal_component_get_icalcomponent (obj_data->full_object));
2431 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2435 if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (gpointer *) &real_rid, (gpointer *) &recurrence)) {
2436 if (*old_components)
2437 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (recurrence));
2439 /* remove the component from our data */
2440 icalcomponent_remove_component (
2442 e_cal_component_get_icalcomponent (recurrence));
2443 priv->comp = g_list_remove (priv->comp, recurrence);
2444 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2445 g_hash_table_remove (obj_data->recurrences, rid);
2448 *old_components = g_slist_prepend (*old_components, NULL);
2451 /* add the detached instance */
2452 g_hash_table_insert (
2453 obj_data->recurrences,
2456 icalcomponent_add_component (
2458 e_cal_component_get_icalcomponent (comp));
2459 priv->comp = g_list_append (priv->comp, comp);
2460 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
2462 case CALOBJ_MOD_THISANDPRIOR :
2463 case CALOBJ_MOD_THISANDFUTURE :
2464 if (!rid || !*rid) {
2466 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2468 remove_component (cbfile, comp_uid, obj_data);
2470 /* Add the new object */
2471 add_component (cbfile, comp, TRUE);
2475 /* remove the component from our data, temporarily */
2476 if (obj_data->full_object) {
2477 icalcomponent_remove_component (
2479 e_cal_component_get_icalcomponent (obj_data->full_object));
2480 priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2483 /* now deal with the detached recurrence */
2484 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
2485 (gpointer *) &real_rid, (gpointer *) &recurrence)) {
2487 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (recurrence));
2489 /* remove the component from our data */
2490 icalcomponent_remove_component (
2492 e_cal_component_get_icalcomponent (recurrence));
2493 priv->comp = g_list_remove (priv->comp, recurrence);
2494 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2495 g_hash_table_remove (obj_data->recurrences, rid);
2497 if (*old_components)
2498 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2501 rrdata.cbfile = cbfile;
2502 rrdata.obj_data = obj_data;
2505 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2507 /* add the modified object to the beginning of the list,
2508 * so that it's always before any detached instance we
2510 if (obj_data->full_object) {
2511 icalcomponent_add_component (
2513 e_cal_component_get_icalcomponent (obj_data->full_object));
2514 priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2517 /* add the new detached recurrence */
2518 g_hash_table_insert (
2519 obj_data->recurrences,
2522 icalcomponent_add_component (
2524 e_cal_component_get_icalcomponent (comp));
2525 priv->comp = g_list_append (priv->comp, comp);
2526 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
2528 case CALOBJ_MOD_ALL :
2529 /* Remove the old version */
2531 *old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2533 if (obj_data->recurrences_list) {
2534 /* has detached components, preserve them */
2537 for (ll = obj_data->recurrences_list; ll; ll = ll->next) {
2538 detached = g_list_prepend (detached, g_object_ref (ll->data));
2542 remove_component (cbfile, comp_uid, obj_data);
2544 /* Add the new object */
2545 add_component (cbfile, comp, TRUE);
2548 /* it had some detached components, place them back */
2549 comp_uid = icalcomponent_get_uid (e_cal_component_get_icalcomponent (comp));
2551 if ((obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid)) != NULL) {
2554 for (ll = detached; ll; ll = ll->next) {
2555 ECalComponent *c = ll->data;
2557 g_hash_table_insert (obj_data->recurrences, e_cal_component_get_recurid_as_string (c), c);
2558 icalcomponent_add_component (priv->icalcomp, e_cal_component_get_icalcomponent (c));
2559 priv->comp = g_list_append (priv->comp, c);
2560 obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, c);
2564 g_list_free (detached);
2567 case CALOBJ_MOD_ONLY_THIS:
2568 /* not reached, keep compiler happy */
2574 if (new_components) {
2575 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
2579 g_slist_free (icalcomps);
2581 /* All the components were updated, now we save the file */
2582 save (cbfile, TRUE);
2584 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2587 *old_components = g_slist_reverse (*old_components);
2590 *new_components = g_slist_reverse (*new_components);
2594 * Remove one and only one instance. The object may be empty
2595 * afterwards, in which case it will be removed completely.
2597 * @mod CALOBJ_MOD_THIS or CAL_OBJ_MOD_ONLY_THIS: the later only removes
2598 * the instance, the former also adds an EXDATE if rid is set
2599 * TODO: CAL_OBJ_MOD_ONLY_THIS
2600 * @uid pointer to UID which must remain valid even if the object gets
2602 * @rid NULL, "", or non-empty string when manipulating a specific recurrence;
2603 * also must remain valid
2604 * @error may be NULL if caller is not interested in errors
2605 * @return modified object or NULL if it got removed
2607 static ECalBackendFileObject *
2608 remove_instance (ECalBackendFile *cbfile,
2609 ECalBackendFileObject *obj_data,
2613 ECalComponent **old_comp,
2614 ECalComponent **new_comp,
2618 ECalComponent *comp;
2619 struct icaltimetype current;
2621 /* only check for non-NULL below, empty string is detected here */
2626 /* remove recurrence */
2627 if (g_hash_table_lookup_extended (obj_data->recurrences, rid,
2628 (gpointer *) &hash_rid, (gpointer *) &comp)) {
2629 /* Removing without parent or not modifying parent?
2630 * Report removal to caller. */
2632 (!obj_data->full_object || mod == CALOBJ_MOD_ONLY_THIS)) {
2633 *old_comp = e_cal_component_clone (comp);
2636 /* Reporting parent modification to caller?
2637 * Report directly instead of going via caller. */
2638 if (obj_data->full_object &&
2639 mod != CALOBJ_MOD_ONLY_THIS) {
2640 /* old object string not provided,
2641 * instead rely on the view detecting
2642 * whether it contains the id */
2644 id.uid = (gchar *) uid;
2645 id.rid = (gchar *) rid;
2646 e_cal_backend_notify_component_removed (E_CAL_BACKEND (cbfile), &id, NULL, NULL);
2649 /* remove the component from our data */
2650 icalcomponent_remove_component (
2651 cbfile->priv->icalcomp,
2652 e_cal_component_get_icalcomponent (comp));
2653 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
2654 obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp);
2655 g_hash_table_remove (obj_data->recurrences, rid);
2656 } else if (mod == CALOBJ_MOD_ONLY_THIS) {
2658 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2661 /* not an error, only add EXDATE */
2663 /* component empty? */
2664 if (!obj_data->full_object) {
2665 if (!obj_data->recurrences_list) {
2666 /* empty now, remove it */
2667 remove_component (cbfile, uid, obj_data);
2674 /* avoid modifying parent? */
2675 if (mod == CALOBJ_MOD_ONLY_THIS)
2678 /* remove the main component from our data before modifying it */
2679 icalcomponent_remove_component (
2680 cbfile->priv->icalcomp,
2681 e_cal_component_get_icalcomponent (obj_data->full_object));
2682 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
2684 /* add EXDATE or EXRULE to parent, report as update */
2686 *old_comp = e_cal_component_clone (obj_data->full_object);
2689 e_cal_util_remove_instances (
2690 e_cal_component_get_icalcomponent (obj_data->full_object),
2691 icaltime_from_string (rid), CALOBJ_MOD_THIS);
2693 /* Since we are only removing one instance of recurrence
2694 * event, update the last modified time on the component */
2695 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2696 e_cal_component_set_last_modified (obj_data->full_object, ¤t);
2700 *new_comp = e_cal_component_clone (obj_data->full_object);
2703 /* add the modified object to the beginning of the list,
2704 * so that it's always before any detached instance we
2706 icalcomponent_add_component (
2707 cbfile->priv->icalcomp,
2708 e_cal_component_get_icalcomponent (obj_data->full_object));
2709 cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
2711 if (!obj_data->full_object) {
2712 /* Nothing to do, parent doesn't exist. Tell
2713 * caller about this? Not an error with
2714 * CALOBJ_MOD_THIS. */
2715 if (mod == CALOBJ_MOD_ONLY_THIS && error)
2716 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2720 /* remove the main component from our data before deleting it */
2721 if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) {
2722 /* return without changing anything */
2723 g_message (G_STRLOC " Could not remove component from interval tree!");
2726 icalcomponent_remove_component (
2727 cbfile->priv->icalcomp,
2728 e_cal_component_get_icalcomponent (obj_data->full_object));
2729 cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
2731 /* remove parent, report as removal */
2733 *old_comp = g_object_ref (obj_data->full_object);
2735 g_object_unref (obj_data->full_object);
2736 obj_data->full_object = NULL;
2738 /* component may be empty now, check that */
2739 if (!obj_data->recurrences_list) {
2740 remove_component (cbfile, uid, obj_data);
2745 /* component still exists in a modified form */
2749 static ECalComponent *
2750 clone_ecalcomp_from_fileobject (ECalBackendFileObject *obj_data,
2753 ECalComponent *comp = obj_data->full_object;
2760 if (!g_hash_table_lookup_extended (obj_data->recurrences, rid,
2761 (gpointer *) &real_rid, (gpointer *) &comp)) {
2762 /* FIXME remove this once we delete an instance from master object through
2763 * modify request by setting exception */
2764 comp = obj_data->full_object;
2768 return comp ? e_cal_component_clone (comp) : NULL;
2772 notify_comp_removed_cb (gpointer pecalcomp,
2775 ECalComponent *comp = pecalcomp;
2776 ECalBackend *backend = pbackend;
2777 ECalComponentId *id;
2779 g_return_if_fail (comp != NULL);
2780 g_return_if_fail (backend != NULL);
2782 id = e_cal_component_get_id (comp);
2783 g_return_if_fail (id != NULL);
2785 e_cal_backend_notify_component_removed (backend, id, comp, NULL);
2787 e_cal_component_free_id (id);
2790 /* Remove_object handler for the file backend */
2792 e_cal_backend_file_remove_objects (ECalBackendSync *backend,
2794 GCancellable *cancellable,
2797 GSList **old_components,
2798 GSList **new_components,
2801 ECalBackendFile *cbfile;
2802 ECalBackendFilePrivate *priv;
2805 cbfile = E_CAL_BACKEND_FILE (backend);
2806 priv = cbfile->priv;
2808 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
2809 e_return_data_cal_error_if_fail (ids != NULL, ObjectNotFound);
2810 e_return_data_cal_error_if_fail (old_components != NULL, ObjectNotFound);
2811 e_return_data_cal_error_if_fail (new_components != NULL, ObjectNotFound);
2814 case CALOBJ_MOD_THIS:
2815 case CALOBJ_MOD_THISANDPRIOR:
2816 case CALOBJ_MOD_THISANDFUTURE:
2817 case CALOBJ_MOD_ONLY_THIS:
2818 case CALOBJ_MOD_ALL:
2821 g_propagate_error (error, EDC_ERROR (NotSupported));
2825 *old_components = *new_components = NULL;
2827 g_rec_mutex_lock (&priv->idle_save_rmutex);
2829 /* First step, validate the input */
2830 for (l = ids; l; l = l->next) {
2831 ECalComponentId *id = l->data;
2832 /* Make the ID contains a uid */
2833 if (!id || !id->uid) {
2834 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2835 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2838 /* Check that it has a recurrence id if mod is CALOBJ_MOD_THISANDPRIOR
2839 or CALOBJ_MOD_THISANDFUTURE */
2840 if ((mod == CALOBJ_MOD_THISANDPRIOR || mod == CALOBJ_MOD_THISANDFUTURE) &&
2841 (!id->rid || !*(id->rid))) {
2842 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2843 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2846 /* Make sure the uid exists in the local hash table */
2847 if (!g_hash_table_lookup (priv->comp_uid_hash, id->uid)) {
2848 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2849 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
2854 /* Second step, remove objects from the calendar */
2855 for (l = ids; l; l = l->next) {
2856 const gchar *recur_id = NULL;
2857 ECalComponent *comp;
2858 RemoveRecurrenceData rrdata;
2859 ECalBackendFileObject *obj_data;
2860 ECalComponentId *id = l->data;
2862 obj_data = g_hash_table_lookup (priv->comp_uid_hash, id->uid);
2864 if (id->rid && *(id->rid))
2868 case CALOBJ_MOD_ALL :
2869 *old_components = g_slist_prepend (*old_components, clone_ecalcomp_from_fileobject (obj_data, recur_id));
2870 *new_components = g_slist_prepend (*new_components, NULL);
2872 if (obj_data->recurrences_list)
2873 g_list_foreach (obj_data->recurrences_list, notify_comp_removed_cb, cbfile);
2874 remove_component (cbfile, id->uid, obj_data);
2876 case CALOBJ_MOD_ONLY_THIS:
2877 case CALOBJ_MOD_THIS: {
2878 ECalComponent *old_component = NULL;
2879 ECalComponent *new_component = NULL;
2881 obj_data = remove_instance (
2882 cbfile, obj_data, id->uid, recur_id, mod,
2883 &old_component, &new_component, error);
2885 *old_components = g_slist_prepend (*old_components, old_component);
2886 *new_components = g_slist_prepend (*new_components, new_component);
2889 case CALOBJ_MOD_THISANDPRIOR :
2890 case CALOBJ_MOD_THISANDFUTURE :
2891 comp = obj_data->full_object;
2894 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (comp));
2896 /* remove the component from our data, temporarily */
2897 icalcomponent_remove_component (
2899 e_cal_component_get_icalcomponent (comp));
2900 priv->comp = g_list_remove (priv->comp, comp);
2902 e_cal_util_remove_instances (
2903 e_cal_component_get_icalcomponent (comp),
2904 icaltime_from_string (recur_id), mod);
2906 *old_components = g_slist_prepend (*old_components, NULL);
2909 /* now remove all detached instances */
2910 rrdata.cbfile = cbfile;
2911 rrdata.obj_data = obj_data;
2912 rrdata.rid = recur_id;
2914 g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2916 /* add the modified object to the beginning of the list,
2917 * so that it's always before any detached instance we
2920 priv->comp = g_list_prepend (priv->comp, comp);
2922 if (obj_data->full_object) {
2923 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (obj_data->full_object));
2925 *new_components = g_slist_prepend (*new_components, NULL);
2931 save (cbfile, TRUE);
2933 g_rec_mutex_unlock (&priv->idle_save_rmutex);
2935 *old_components = g_slist_reverse (*old_components);
2936 *new_components = g_slist_reverse (*new_components);
2940 cancel_received_object (ECalBackendFile *cbfile,
2941 ECalComponent *comp,
2942 ECalComponent **old_comp,
2943 ECalComponent **new_comp)
2945 ECalBackendFileObject *obj_data;
2946 ECalBackendFilePrivate *priv;
2948 const gchar *uid = NULL;
2950 priv = cbfile->priv;
2955 e_cal_component_get_uid (comp, &uid);
2957 /* Find the old version of the component. */
2958 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2963 rid = e_cal_component_get_recurid_as_string (comp);
2965 obj_data = remove_instance (
2966 cbfile, obj_data, uid, rid, CALOBJ_MOD_THIS,
2967 old_comp, new_comp, NULL);
2968 if (obj_data && obj_data->full_object && !*new_comp) {
2969 *new_comp = e_cal_component_clone (obj_data->full_object);
2972 /* report as removal by keeping *new_component NULL */
2973 if (obj_data->full_object) {
2974 *old_comp = e_cal_component_clone (obj_data->full_object);
2976 remove_component (cbfile, uid, obj_data);
2988 } ECalBackendFileTzidData;
2991 check_tzids (icalparameter *param,
2994 ECalBackendFileTzidData *tzdata = data;
2997 tzid = icalparameter_get_tzid (param);
2998 if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
2999 tzdata->found = FALSE;
3002 /* This function is largely duplicated in
3003 * ../groupwise/e-cal-backend-groupwise.c
3006 fetch_attachments (ECalBackendSync *backend,
3007 ECalComponent *comp)
3009 GSList *attach_list = NULL, *new_attach_list = NULL;
3011 gchar *dest_url, *dest_file;
3015 e_cal_component_get_attachment_list (comp, &attach_list);
3016 e_cal_component_get_uid (comp, &uid);
3018 for (l = attach_list, fileindex = 0; l; l = l->next, fileindex++) {
3019 gchar *sfname = g_filename_from_uri ((const gchar *) l->data, NULL, NULL);
3021 GMappedFile *mapped_file;
3022 GError *error = NULL;
3027 mapped_file = g_mapped_file_new (sfname, FALSE, &error);
3030 "DEBUG: could not map %s: %s\n",
3031 sfname, error ? error->message : "???");
3032 g_error_free (error);
3036 filename = g_path_get_basename (sfname);
3037 dest_file = e_cal_backend_create_cache_filename (E_CAL_BACKEND (backend), uid, filename, fileindex);
3039 fd = g_open (dest_file, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
3041 /* TODO handle error conditions */
3043 "DEBUG: could not open %s for writing\n",
3045 } else if (write (fd, g_mapped_file_get_contents (mapped_file),
3046 g_mapped_file_get_length (mapped_file)) == -1) {
3047 /* TODO handle error condition */
3048 g_message ("DEBUG: attachment write failed.\n");
3051 g_mapped_file_unref (mapped_file);
3055 dest_url = g_filename_to_uri (dest_file, NULL, NULL);
3057 new_attach_list = g_slist_append (new_attach_list, dest_url);
3061 e_cal_component_set_attachment_list (comp, new_attach_list);
3064 /* Update_objects handler for the file backend. */
3066 e_cal_backend_file_receive_objects (ECalBackendSync *backend,
3068 GCancellable *cancellable,
3069 const gchar *calobj,
3072 ESourceRegistry *registry;
3073 ECalBackendFile *cbfile;
3074 ECalBackendFilePrivate *priv;
3075 icalcomponent *toplevel_comp, *icalcomp = NULL;
3076 icalcomponent_kind kind;
3077 icalproperty_method toplevel_method, method;
3078 icalcomponent *subcomp;
3079 GList *comps, *del_comps, *l;
3080 ECalComponent *comp;
3081 struct icaltimetype current;
3082 ECalBackendFileTzidData tzdata;
3085 cbfile = E_CAL_BACKEND_FILE (backend);
3086 priv = cbfile->priv;
3088 e_return_data_cal_error_if_fail (priv->icalcomp != NULL, InvalidArg);
3089 e_return_data_cal_error_if_fail (calobj != NULL, InvalidObject);
3091 /* Pull the component from the string and ensure that it is sane */
3092 toplevel_comp = icalparser_parse_string ((gchar *) calobj);
3093 if (!toplevel_comp) {
3094 g_propagate_error (error, EDC_ERROR (InvalidObject));
3098 g_rec_mutex_lock (&priv->idle_save_rmutex);
3100 registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
3102 kind = icalcomponent_isa (toplevel_comp);
3103 if (kind != ICAL_VCALENDAR_COMPONENT) {
3104 /* If its not a VCALENDAR, make it one to simplify below */
3105 icalcomp = toplevel_comp;
3106 toplevel_comp = e_cal_util_new_top_level ();
3107 if (icalcomponent_get_method (icalcomp) == ICAL_METHOD_CANCEL)
3108 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_CANCEL);
3110 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_PUBLISH);
3111 icalcomponent_add_component (toplevel_comp, icalcomp);
3113 if (!icalcomponent_get_first_property (toplevel_comp, ICAL_METHOD_PROPERTY))
3114 icalcomponent_set_method (toplevel_comp, ICAL_METHOD_PUBLISH);
3117 toplevel_method = icalcomponent_get_method (toplevel_comp);
3119 /* Build a list of timezones so we can make sure all the objects have valid info */
3120 tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
3122 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
3126 zone = icaltimezone_new ();
3127 if (icaltimezone_set_component (zone, subcomp))
3128 g_hash_table_insert (tzdata.zones, g_strdup (icaltimezone_get_tzid (zone)), NULL);
3130 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_VTIMEZONE_COMPONENT);
3133 /* First we make sure all the components are usuable */
3134 comps = del_comps = NULL;
3135 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
3137 subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
3139 icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
3141 if (child_kind != kind) {
3142 /* remove the component from the toplevel VCALENDAR */
3143 if (child_kind != ICAL_VTIMEZONE_COMPONENT)
3144 del_comps = g_list_prepend (del_comps, subcomp);
3146 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
3150 tzdata.found = TRUE;
3151 icalcomponent_foreach_tzid (subcomp, check_tzids, &tzdata);
3153 if (!tzdata.found) {
3154 err = EDC_ERROR (InvalidObject);
3158 if (!icalcomponent_get_uid (subcomp)) {
3159 if (toplevel_method == ICAL_METHOD_PUBLISH) {
3161 gchar *new_uid = NULL;
3163 new_uid = e_cal_component_gen_uid ();
3164 icalcomponent_set_uid (subcomp, new_uid);
3167 err = EDC_ERROR (InvalidObject);
3173 comps = g_list_prepend (comps, subcomp);
3174 subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
3177 /* Now we manipulate the components we care about */
3178 for (l = comps; l; l = l->next) {
3179 ECalComponent *old_component = NULL;
3180 ECalComponent *new_component = NULL;
3183 ECalBackendFileObject *obj_data;
3184 gboolean is_declined;
3188 /* Create the cal component */
3189 comp = e_cal_component_new ();
3190 e_cal_component_set_icalcomponent (comp, subcomp);
3192 /* Set the created and last modified times on the component */
3193 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3194 e_cal_component_set_created (comp, ¤t);
3195 e_cal_component_set_last_modified (comp, ¤t);
3197 e_cal_component_get_uid (comp, &uid);
3198 rid = e_cal_component_get_recurid_as_string (comp);
3200 if (icalcomponent_get_first_property (subcomp, ICAL_METHOD_PROPERTY))
3201 method = icalcomponent_get_method (subcomp);
3203 method = toplevel_method;
3206 case ICAL_METHOD_PUBLISH:
3207 case ICAL_METHOD_REQUEST:
3208 case ICAL_METHOD_REPLY:
3209 is_declined = e_cal_backend_user_declined (registry, subcomp);
3211 /* handle attachments */
3212 if (!is_declined && e_cal_component_has_attachments (comp))
3213 fetch_attachments (backend, comp);
3214 obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
3218 ECalComponent *ignore_comp = NULL;
3221 cbfile, obj_data, uid, rid, CALOBJ_MOD_THIS,
3222 &old_component, &ignore_comp, NULL);
3225 g_object_unref (ignore_comp);
3227 if (obj_data->full_object) {
3228 old_component = e_cal_component_clone (obj_data->full_object);
3230 remove_component (cbfile, uid, obj_data);
3234 add_component (cbfile, comp, FALSE);
3237 e_cal_backend_notify_component_modified (E_CAL_BACKEND (backend),
3238 old_component, comp);
3240 ECalComponentId *id = e_cal_component_get_id (comp);
3242 e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
3246 e_cal_component_free_id (id);
3250 g_object_unref (old_component);
3252 } else if (!is_declined) {
3253 add_component (cbfile, comp, FALSE);
3255 e_cal_backend_notify_component_created (E_CAL_BACKEND (backend), comp);
3259 case ICAL_METHOD_ADD:
3260 /* FIXME This should be doable once all the recurid stuff is done */
3261 err = EDC_ERROR (UnsupportedMethod);
3265 case ICAL_METHOD_COUNTER:
3266 err = EDC_ERROR (UnsupportedMethod);
3270 case ICAL_METHOD_DECLINECOUNTER:
3271 err = EDC_ERROR (UnsupportedMethod);
3275 case ICAL_METHOD_CANCEL:
3276 if (cancel_received_object (cbfile, comp, &old_component, &new_component)) {
3277 ECalComponentId *id;
3279 id = e_cal_component_get_id (comp);
3281 e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
3282 id, old_component, new_component);
3284 /* remove the component from the toplevel VCALENDAR */
3285 icalcomponent_remove_component (toplevel_comp, subcomp);
3286 icalcomponent_free (subcomp);
3287 e_cal_component_free_id (id);
3290 g_object_unref (new_component);
3292 g_object_unref (old_component);
3297 err = EDC_ERROR (UnsupportedMethod);
3303 g_list_free (comps);
3305 /* Now we remove the components we don't care about */
3306 for (l = del_comps; l; l = l->next) {
3309 icalcomponent_remove_component (toplevel_comp, subcomp);
3310 icalcomponent_free (subcomp);
3313 g_list_free (del_comps);
3315 /* check and patch timezones */
3317 if (!e_cal_client_check_timezones (toplevel_comp,
3319 e_cal_client_tzlookup_icomp,
3324 * This makes assumptions about what kind of
3325 * errors can occur inside e_cal_check_timezones().
3326 * We control it, so that should be safe, but
3327 * is the code really identical with the calendar
3334 /* Merge the iCalendar components with our existing VCALENDAR,
3335 * resolving any conflicting TZIDs. */
3336 icalcomponent_merge_component (priv->icalcomp, toplevel_comp);
3338 save (cbfile, TRUE);
3341 g_hash_table_destroy (tzdata.zones);
3342 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3345 g_propagate_error (error, err);
3349 e_cal_backend_file_send_objects (ECalBackendSync *backend,
3351 GCancellable *cancellable,
3352 const gchar *calobj,
3354 gchar **modified_calobj,
3358 *modified_calobj = g_strdup (calobj);
3361 /* Object initialization function for the file backend */
3363 e_cal_backend_file_init (ECalBackendFile *cbfile)
3365 cbfile->priv = E_CAL_BACKEND_FILE_GET_PRIVATE (cbfile);
3367 cbfile->priv->file_name = g_strdup ("calendar.ics");
3369 g_rec_mutex_init (&cbfile->priv->idle_save_rmutex);
3371 g_mutex_init (&cbfile->priv->refresh_lock);
3374 * data access is serialized via idle_save_rmutex, so locking at the
3375 * backend method level is not needed
3377 e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbfile), FALSE);
3381 cal_backend_file_constructed (GObject *object)
3383 ECalBackend *backend;
3384 ESourceRegistry *registry;
3385 ESource *builtin_source;
3387 icalcomponent_kind kind;
3388 const gchar *user_data_dir;
3389 const gchar *component_type;
3393 user_data_dir = e_get_user_data_dir ();
3395 /* Chain up to parent's constructed() method. */
3396 G_OBJECT_CLASS (e_cal_backend_file_parent_class)->constructed (object);
3398 /* Override the cache directory that the parent class just set. */
3400 backend = E_CAL_BACKEND (object);
3401 kind = e_cal_backend_get_kind (backend);
3402 source = e_backend_get_source (E_BACKEND (backend));
3403 registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
3405 uid = e_source_get_uid (source);
3406 g_return_if_fail (uid != NULL);
3409 case ICAL_VEVENT_COMPONENT:
3410 component_type = "calendar";
3411 builtin_source = e_source_registry_ref_builtin_calendar (registry);
3413 case ICAL_VTODO_COMPONENT:
3414 component_type = "tasks";
3415 builtin_source = e_source_registry_ref_builtin_task_list (registry);
3417 case ICAL_VJOURNAL_COMPONENT:
3418 component_type = "memos";
3419 builtin_source = e_source_registry_ref_builtin_memo_list (registry);
3422 g_warn_if_reached ();
3423 component_type = "calendar";
3424 builtin_source = e_source_registry_ref_builtin_calendar (registry);
3428 /* XXX Backward-compatibility hack:
3430 * The special built-in "Personal" data source UIDs are now named
3431 * "system-$COMPONENT" but since the data directories are already
3432 * split out by component, we'll continue to use the old "system"
3433 * directories for these particular data sources. */
3434 if (builtin_source != NULL && e_source_equal (source, builtin_source))
3437 filename = g_build_filename (user_data_dir, component_type, uid, NULL);
3438 e_cal_backend_set_cache_dir (backend, filename);
3442 g_object_unref (builtin_source);
3445 /* Class initialization function for the file backend */
3447 e_cal_backend_file_class_init (ECalBackendFileClass *class)
3449 GObjectClass *object_class;
3450 ECalBackendClass *backend_class;
3451 ECalBackendSyncClass *sync_class;
3453 g_type_class_add_private (class, sizeof (ECalBackendFilePrivate));
3455 object_class = (GObjectClass *) class;
3456 backend_class = (ECalBackendClass *) class;
3457 sync_class = (ECalBackendSyncClass *) class;
3459 object_class->dispose = e_cal_backend_file_dispose;
3460 object_class->finalize = e_cal_backend_file_finalize;
3461 object_class->constructed = cal_backend_file_constructed;
3463 sync_class->get_backend_property_sync = e_cal_backend_file_get_backend_property;
3464 sync_class->open_sync = e_cal_backend_file_open;
3465 sync_class->create_objects_sync = e_cal_backend_file_create_objects;
3466 sync_class->modify_objects_sync = e_cal_backend_file_modify_objects;
3467 sync_class->remove_objects_sync = e_cal_backend_file_remove_objects;
3468 sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
3469 sync_class->send_objects_sync = e_cal_backend_file_send_objects;
3470 sync_class->get_object_sync = e_cal_backend_file_get_object;
3471 sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
3472 sync_class->get_attachment_uris_sync = e_cal_backend_file_get_attachment_uris;
3473 sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
3474 sync_class->get_free_busy_sync = e_cal_backend_file_get_free_busy;
3476 backend_class->start_view = e_cal_backend_file_start_view;
3477 backend_class->internal_get_timezone = e_cal_backend_file_internal_get_timezone;
3479 /* Register our ESource extension. */
3480 E_TYPE_SOURCE_LOCAL;
3484 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile,
3485 const gchar *file_name)
3487 ECalBackendFilePrivate *priv;
3489 g_return_if_fail (cbfile != NULL);
3490 g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
3491 g_return_if_fail (file_name != NULL);
3493 priv = cbfile->priv;
3494 g_rec_mutex_lock (&priv->idle_save_rmutex);
3496 if (priv->file_name)
3497 g_free (priv->file_name);
3499 priv->file_name = g_strdup (file_name);
3501 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3505 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
3507 ECalBackendFilePrivate *priv;
3509 g_return_val_if_fail (cbfile != NULL, NULL);
3510 g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
3512 priv = cbfile->priv;
3514 return priv->file_name;
3518 e_cal_backend_file_reload (ECalBackendFile *cbfile,
3521 ECalBackendFilePrivate *priv;
3525 priv = cbfile->priv;
3526 g_rec_mutex_lock (&priv->idle_save_rmutex);
3528 str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
3530 err = EDC_ERROR_NO_URI ();
3534 if (g_access (str_uri, R_OK) == 0) {
3535 reload_cal (cbfile, str_uri, &err);
3536 if (g_access (str_uri, W_OK) != 0)
3537 priv->read_only = TRUE;
3539 err = EDC_ERROR (NoSuchCal);
3544 if (!err && !priv->read_only) {
3547 source = e_backend_get_source (E_BACKEND (cbfile));
3549 if (!e_source_get_writable (source))
3550 priv->read_only = TRUE;
3553 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3554 e_cal_backend_notify_readonly (E_CAL_BACKEND (cbfile), cbfile->priv->read_only);
3557 g_propagate_error (perror, err);
3560 #ifdef TEST_QUERY_RESULT
3563 test_query_by_scanning_all_objects (ECalBackendFile *cbfile,
3567 MatchObjectData match_data;
3568 ECalBackendFilePrivate *priv;
3570 priv = cbfile->priv;
3572 match_data.search_needed = TRUE;
3573 match_data.query = sexp;
3574 match_data.comps_list = NULL;
3575 match_data.as_string = TRUE;
3576 match_data.backend = E_CAL_BACKEND (cbfile);
3578 if (!strcmp (sexp, "#t"))
3579 match_data.search_needed = FALSE;
3581 match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
3582 if (!match_data.obj_sexp)
3585 g_rec_mutex_lock (&priv->idle_save_rmutex);
3587 if (!match_data.obj_sexp)
3589 g_message (G_STRLOC ": Getting object list (%s)", sexp);
3593 g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
3596 g_rec_mutex_unlock (&priv->idle_save_rmutex);
3598 *objects = g_slist_reverse (match_data.comps_list);
3600 g_object_unref (match_data.obj_sexp);
3604 write_list (GSList *list)
3608 for (l = list; l; l = l->next)
3610 const gchar *str = l->data;
3611 ECalComponent *comp = e_cal_component_new_from_string (str);
3613 e_cal_component_get_uid (comp, &uid);
3614 g_print ("%s\n", uid);
3619 get_difference_of_lists (ECalBackendFile *cbfile,
3623 GSList *l, *lsmaller;
3625 for (l = bigger; l; l = l->next) {
3626 gchar *str = l->data;
3628 ECalComponent *comp = e_cal_component_new_from_string (str);
3629 gboolean found = FALSE;
3630 e_cal_component_get_uid (comp, &uid);
3632 for (lsmaller = smaller; lsmaller && !found; lsmaller = lsmaller->next)
3634 gchar *strsmaller = lsmaller->data;
3635 const gchar *uidsmaller;
3636 ECalComponent *compsmaller = e_cal_component_new_from_string (strsmaller);
3637 e_cal_component_get_uid (compsmaller, &uidsmaller);
3639 found = strcmp (uid, uidsmaller) == 0;
3641 g_object_unref (compsmaller);
3646 time_t time_start, time_end;
3647 printf ("%s IS MISSING\n", uid);
3649 e_cal_util_get_component_occur_times (
3650 comp, &time_start, &time_end,
3651 resolve_tzid, cbfile->priv->icalcomp,
3652 icaltimezone_get_utc_timezone (),
3653 e_cal_backend_get_kind (E_CAL_BACKEND (cbfile)));
3655 d (printf ("start %s\n", asctime (gmtime (&time_start))));
3656 d (printf ("end %s\n", asctime (gmtime (&time_end))));
3659 g_object_unref (comp);
3664 test_query (ECalBackendFile *cbfile,
3667 GSList *objects = NULL, *all_objects = NULL;
3669 g_return_if_fail (query != NULL);
3671 d (g_print ("Query %s\n", query));
3673 test_query_by_scanning_all_objects (cbfile, query, &all_objects);
3674 e_cal_backend_file_get_object_list (E_CAL_BACKEND_SYNC (cbfile), NULL, NULL, query, &objects, NULL);
3675 if (objects == NULL)
3677 g_message (G_STRLOC " failed to get objects\n");
3681 if (g_slist_length (objects) < g_slist_length (all_objects) )
3683 g_print ("ERROR\n");
3684 get_difference_of_lists (cbfile, objects, all_objects);
3687 else if (g_slist_length (objects) > g_slist_length (all_objects) )
3689 g_print ("ERROR\n");
3690 write_list (all_objects);
3691 get_difference_of_lists (cbfile, all_objects, objects);
3695 g_slist_foreach (objects, (GFunc) g_free, NULL);
3696 g_slist_free (objects);
3697 g_slist_foreach (all_objects, (GFunc) g_free, NULL);
3698 g_slist_free (all_objects);
3702 execute_query (ECalBackendFile *cbfile,
3705 GSList *objects = NULL;
3707 g_return_if_fail (query != NULL);
3709 d (g_print ("Query %s\n", query));
3710 e_cal_backend_file_get_object_list (E_CAL_BACKEND_SYNC (cbfile), NULL, NULL, query, &objects, NULL);
3711 if (objects == NULL)
3713 g_message (G_STRLOC " failed to get objects\n");
3717 g_slist_foreach (objects, (GFunc) g_free, NULL);
3718 g_slist_free (objects);
3721 static gchar *fname = NULL;
3722 static gboolean only_execute = FALSE;
3723 static gchar *calendar_fname = NULL;
3725 static GOptionEntry entries[] =
3727 { "test-file", 't', 0, G_OPTION_ARG_STRING, &fname, "File with prepared queries", NULL },
3728 { "only-execute", 'e', 0, G_OPTION_ARG_NONE, &only_execute, "Only execute, do not test query", NULL },
3729 { "calendar-file", 'c', 0, G_OPTION_ARG_STRING, &calendar_fname, "Path to the calendar.ics file", NULL },
3733 /* Always add at least this many bytes when extending the buffer. */
3734 #define MIN_CHUNK 64
3737 private_getline (gchar **lineptr,
3744 if (!lineptr || !n || !stream)
3749 *lineptr = (char *)malloc (*n);
3754 nchars_avail = (gint) *n;
3755 read_pos = *lineptr;
3758 gint c = getc (stream);
3760 if (nchars_avail < 2) {
3766 nchars_avail = (gint)(*n + *lineptr - read_pos);
3767 *lineptr = (char *)realloc (*lineptr, *n);
3770 read_pos = *n - nchars_avail + *lineptr;
3773 if (ferror (stream) || c == EOF) {
3774 if (read_pos == *lineptr)
3784 /* Return the line. */
3790 return (gint)(read_pos - (*lineptr));
3797 gchar * line = NULL;
3799 ECalBackendFile * cbfile;
3801 GError *error = NULL;
3802 GOptionContext *context;
3807 context = g_option_context_new ("- test utility for e-d-s file backend");
3808 g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
3809 if (!g_option_context_parse (context, &argc, &argv, &error))
3811 g_print ("option parsing failed: %s\n", error->message);
3815 calendar_fname = g_strdup ("calendar.ics");
3817 if (!calendar_fname)
3819 g_message (G_STRLOC " Please, use -c parameter");
3823 cbfile = g_object_new (E_TYPE_CAL_BACKEND_FILE, NULL);
3824 open_cal (cbfile, calendar_fname, &error);
3825 if (error != NULL) {
3826 g_message (G_STRLOC " Could not open calendar %s: %s", calendar_fname, error->message);
3832 fin = fopen (fname, "r");
3836 g_message (G_STRLOC " Could not open file %s", fname);
3842 g_message (G_STRLOC " Reading from stdin");
3846 while (private_getline (&line, &len, fin) != -1) {
3847 g_print ("Query %d: %s", num++, line);
3850 execute_query (cbfile, line);
3852 test_query (cbfile, line);
3862 g_object_unref (cbfile);