From ba88feadc788ab9a2961afd6a3575d7079928c32 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 11 May 2011 16:59:51 +0200 Subject: [PATCH] calendar file backend: support removing parent event with CALOBJ_MOD_THIS It was possible to create a meeting series with just a detached event (RECURRENCE-ID set) by importing a meeting invitation for that single recurrence. It was not possible to arrive at that same state after adding the parent event (the one with the RRULE) because e_cal_remove_object_with_mod() removed all instances for CALOBJ_MOD_THIS and empty rid. This contradicts the intended semantic of e_cal_remove_object_with_mod(): "By using a combination of the @uid, @rid and @mod arguments, you can remove specific instances. If what you want is to remove all instances, use e_cal_remove_object instead." This patch implements the desired semantic: - e_cal_backend_file_remove_object(CALOBJ_MOD_THIS) now always calls remove_instance(). - remove_instance() was extended to also work for the parent event. - That call removes the complete object if nothing is left after removing the instance. This case must be handled by the caller. The return value is the original object (if it still exists) and NULL if not. - Because the uid pointer into the object may become invalid as part of the removal, a more permanent pointer has to be provided by the caller. --- calendar/backends/file/e-cal-backend-file.c | 134 ++++++++++++++++++---------- 1 file changed, 88 insertions(+), 46 deletions(-) diff --git a/calendar/backends/file/e-cal-backend-file.c b/calendar/backends/file/e-cal-backend-file.c index 9f522d3..eb22a4c 100644 --- a/calendar/backends/file/e-cal-backend-file.c +++ b/calendar/backends/file/e-cal-backend-file.c @@ -2405,47 +2405,94 @@ e_cal_backend_file_modify_object (ECalBackendSync *backend, EDataCal *cal, GCanc g_static_rec_mutex_unlock (&priv->idle_save_rmutex); } -static void -remove_instance (ECalBackendFile *cbfile, ECalBackendFileObject *obj_data, const gchar *rid) +/** + * Remove one and only one instance. The object may be empty + * afterwards, in which case it will be removed completely. + * + * @uid pointer to UID which must remain valid even if the object gets + * removed + * @rid NULL, "", or non-empty string when manipulating a specific recurrence; + * also must remain valid + * @return modified object or NULL if it got removed + */ +static ECalBackendFileObject * +remove_instance (ECalBackendFile *cbfile, ECalBackendFileObject *obj_data, const gchar *uid, const gchar *rid) { gchar *hash_rid; ECalComponent *comp; struct icaltimetype current; - if (!rid || !*rid) - return; + /* only check for non-NULL below, empty string is detected here */ + if (rid && !*rid) + rid = NULL; - if (g_hash_table_lookup_extended (obj_data->recurrences, rid, (gpointer *)&hash_rid, (gpointer *)&comp)) { - /* remove the component from our data */ + if (rid) { + /* remove recurrence */ + if (g_hash_table_lookup_extended (obj_data->recurrences, rid, + (gpointer *)&hash_rid, (gpointer *)&comp)) { + /* remove the component from our data */ + icalcomponent_remove_component (cbfile->priv->icalcomp, + e_cal_component_get_icalcomponent (comp)); + cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp); + obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp); + g_hash_table_remove (obj_data->recurrences, rid); + } else { + /* not an error, only add EXDATE */ + } + /* component empty? */ + if (!obj_data->full_object) { + if (!obj_data->recurrences_list) { + /* empty now, remove it */ + remove_component (cbfile, uid, obj_data); + return NULL; + } else { + return obj_data; + } + } + /* remove the main component from our data before modifying it */ icalcomponent_remove_component (cbfile->priv->icalcomp, - e_cal_component_get_icalcomponent (comp)); - cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp); - obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp); - g_hash_table_remove (obj_data->recurrences, rid); - } + e_cal_component_get_icalcomponent (obj_data->full_object)); + cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object); - if (!obj_data->full_object) - return; + /* add EXDATE or EXRULE to parent */ + e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object), + icaltime_from_string (rid), CALOBJ_MOD_THIS); - /* remove the component from our data, temporarily */ - icalcomponent_remove_component (cbfile->priv->icalcomp, - e_cal_component_get_icalcomponent (obj_data->full_object)); - cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object); + /* Since we are only removing one instance of recurrence + event, update the last modified time on the component */ + current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ()); + e_cal_component_set_last_modified (obj_data->full_object, ¤t); + + /* add the modified object to the beginning of the list, + so that it's always before any detached instance we + might have */ + icalcomponent_add_component (cbfile->priv->icalcomp, + e_cal_component_get_icalcomponent (obj_data->full_object)); + cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object); + } else { + /* remove the main component from our data before deleting it */ + if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) { + /* return without changing anything */ + g_message (G_STRLOC " Could not remove component from interval tree!"); + return obj_data; + } + icalcomponent_remove_component (cbfile->priv->icalcomp, + e_cal_component_get_icalcomponent (obj_data->full_object)); + cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object); - e_cal_util_remove_instances (e_cal_component_get_icalcomponent (obj_data->full_object), - icaltime_from_string (rid), CALOBJ_MOD_THIS); + /* remove parent */ + g_object_unref (obj_data->full_object); + obj_data->full_object = NULL; - /* Since we are only removing one instance of recurrence - event, update the last modified time on the component */ - current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ()); - e_cal_component_set_last_modified (obj_data->full_object, ¤t); + /* component may be empty now, check that */ + if (!obj_data->recurrences_list) { + remove_component (cbfile, uid, obj_data); + return NULL; + } + } - /* add the modified object to the beginning of the list, - so that it's always before any detached instance we - might have */ - icalcomponent_add_component (cbfile->priv->icalcomp, - e_cal_component_get_icalcomponent (obj_data->full_object)); - cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object); + /* component still exists in a modified form */ + return obj_data; } static gchar * @@ -2506,8 +2553,6 @@ e_cal_backend_file_remove_object (ECalBackendSync *backend, EDataCal *cal, GCanc if (rid && *rid) recur_id = rid; - comp = obj_data->full_object; - switch (mod) { case CALOBJ_MOD_ALL : *old_object = get_object_string_from_fileobject (obj_data, recur_id); @@ -2516,20 +2561,16 @@ e_cal_backend_file_remove_object (ECalBackendSync *backend, EDataCal *cal, GCanc *object = NULL; break; case CALOBJ_MOD_THIS : - if (!recur_id) { - *old_object = get_object_string_from_fileobject (obj_data, recur_id); - remove_component (cbfile, uid, obj_data); - *object = NULL; - } else { - *old_object = get_object_string_from_fileobject (obj_data, recur_id); + *old_object = get_object_string_from_fileobject (obj_data, recur_id); - remove_instance (cbfile, obj_data, recur_id); - if (comp) - *object = e_cal_component_get_as_string (comp); - } + obj_data = remove_instance (cbfile, obj_data, uid, recur_id); + if (obj_data && obj_data->full_object) + *object = e_cal_component_get_as_string (obj_data->full_object); break; case CALOBJ_MOD_THISANDPRIOR : case CALOBJ_MOD_THISANDFUTURE : + comp = obj_data->full_object; + if (!recur_id || !*recur_id) { g_static_rec_mutex_unlock (&priv->idle_save_rmutex); g_propagate_error (error, EDC_ERROR (ObjectNotFound)); @@ -2578,6 +2619,7 @@ cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp, gchar ECalBackendFilePrivate *priv; gchar *rid; ECalComponent *comp; + const gchar *uid = icalcomponent_get_uid (icalcomp); priv = cbfile->priv; @@ -2585,7 +2627,7 @@ cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp, gchar *new_object = NULL; /* Find the old version of the component. */ - obj_data = g_hash_table_lookup (priv->comp_uid_hash, icalcomponent_get_uid (icalcomp)); + obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid); if (!obj_data) return FALSE; @@ -2602,11 +2644,11 @@ cancel_received_object (ECalBackendFile *cbfile, icalcomponent *icalcomp, gchar /* new_object is kept NULL if not removing the instance */ rid = e_cal_component_get_recurid_as_string (comp); if (rid && *rid) { - remove_instance (cbfile, obj_data, rid); - if (obj_data->full_object) + obj_data = remove_instance (cbfile, obj_data, uid, rid); + if (obj_data && obj_data->full_object) *new_object = e_cal_component_get_as_string (obj_data->full_object); } else - remove_component (cbfile, icalcomponent_get_uid (icalcomp), obj_data); + remove_component (cbfile, uid, obj_data); g_free (rid); @@ -2847,7 +2889,7 @@ e_cal_backend_file_receive_objects (ECalBackendSync *backend, EDataCal *cal, GCa if (obj_data->full_object) old_object = e_cal_component_get_as_string (obj_data->full_object); if (rid) - remove_instance (cbfile, obj_data, rid); + remove_instance (cbfile, obj_data, uid, rid); else remove_component (cbfile, uid, obj_data); -- 2.7.4