[evolution-data-server] Bug 620088 - Enable "This and future" recurrence change option



commit adf31279ccd604b1ee44ab2693781579a9d6e22f
Author: Milan Crha <mcrha redhat com>
Date:   Tue Nov 18 12:15:04 2014 +0100

    Bug 620088 - Enable "This and future" recurrence change option

 calendar/backends/caldav/e-cal-backend-caldav.c |  121 ++++++++-
 calendar/backends/file/e-cal-backend-file.c     |   83 ++++--
 calendar/libecal/e-cal-util.c                   |  340 ++++++++++++++++++++---
 calendar/libecal/e-cal-util.h                   |    7 +
 4 files changed, 486 insertions(+), 65 deletions(-)
---
diff --git a/calendar/backends/caldav/e-cal-backend-caldav.c b/calendar/backends/caldav/e-cal-backend-caldav.c
index 3add622..92ca0b8 100644
--- a/calendar/backends/caldav/e-cal-backend-caldav.c
+++ b/calendar/backends/caldav/e-cal-backend-caldav.c
@@ -2654,7 +2654,6 @@ caldav_get_backend_property (ECalBackend *backend,
                const gchar *extension_name;
 
                caps = g_string_new (
-                       CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
                        CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
                        CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED);
 
@@ -4025,8 +4024,8 @@ do_modify_objects (ECalBackendCalDAV *cbdav,
                    GError **error)
 {
        ECalComponent            *comp;
-       icalcomponent            *cache_comp;
-       gboolean                  online, did_put = FALSE;
+       icalcomponent            *cache_comp, *master_comp;
+       gboolean                  online, did_put = FALSE, success = TRUE;
        ECalComponentId          *id;
        struct icaltimetype current;
        gchar *href = NULL, *etag = NULL;
@@ -4158,6 +4157,112 @@ do_modify_objects (ECalBackendCalDAV *cbdav,
                break;
        case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
        case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
+               master_comp = get_master_comp (cbdav, cache_comp);
+               if (e_cal_component_is_instance (comp) && master_comp) {
+                       ECalComponent *mcomp;
+                       gboolean processed = FALSE;
+                       struct icaltimetype rid, master_dtstart;
+                       icalcomponent *icalcomp = e_cal_component_get_icalcomponent (comp);
+                       icalcomponent *split_icalcomp;
+                       icalproperty *prop;
+
+                       rid = icalcomponent_get_recurrenceid (icalcomp);
+                       mcomp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone 
(master_comp));
+
+                       if (mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE &&
+                           e_cal_util_is_first_instance (mcomp, icalcomponent_get_recurrenceid (icalcomp), 
resolve_tzid, cbdav)) {
+                               icalproperty *prop = icalcomponent_get_first_property (icalcomp, 
ICAL_RECURRENCEID_PROPERTY);
+
+                               if (prop)
+                                       icalcomponent_remove_property (icalcomp, prop);
+
+                               e_cal_component_rescan (comp);
+
+                               /* Then do it like for "mod_all" */
+                               cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone 
(e_cal_component_get_icalcomponent (comp)));
+                               g_clear_object (&mcomp);
+
+                               if (new_components) {
+                                       /* read the comp from cache again, as some servers can modify it on 
put */
+                                       *new_components = g_slist_prepend (*new_components, 
get_ecalcomp_master_from_cache_or_fallback (cbdav, id->uid, NULL, comp));
+                               }
+                               break;
+                       }
+
+                       prop = icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY);
+                       if (prop)
+                               icalcomponent_remove_property (icalcomp, prop);
+                       e_cal_component_rescan (comp);
+
+                       master_dtstart = icalcomponent_get_dtstart (master_comp);
+                       if (master_dtstart.zone && master_dtstart.zone != rid.zone)
+                               rid = icaltime_convert_to_zone (rid, (icaltimezone *) master_dtstart.zone);
+                       split_icalcomp = e_cal_util_split_at_instance (icalcomp, rid, master_dtstart);
+                       if (split_icalcomp) {
+                               ECalComponent *prev_comp;
+
+                               prev_comp = e_cal_component_clone (mcomp);
+
+                               rid = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ());
+                               e_cal_util_remove_instances (master_comp, rid, mod);
+                               e_cal_component_rescan (mcomp);
+
+                               if (new_components) {
+                                       *new_components = g_slist_prepend (*new_components,
+                                               get_ecalcomp_master_from_cache_or_fallback (cbdav, id->uid, 
NULL, mcomp));
+                               }
+
+                               g_clear_object (&prev_comp);
+                       }
+
+                       processed = TRUE;
+
+                       cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone 
(master_comp));
+                       if (split_icalcomp) {
+                               gchar *new_uid;
+
+                               new_uid = e_cal_component_gen_uid ();
+                               icalcomponent_set_uid (split_icalcomp, new_uid);
+                               g_free (new_uid);
+
+                               g_warn_if_fail (e_cal_component_set_icalcomponent (comp, split_icalcomp));
+
+                               /* sanitize the component */
+                               sanitize_component ((ECalBackend *) cbdav, comp);
+
+                               if (online) {
+                                       CalDAVObject object;
+
+                                       object.href = ecalcomp_gen_href (comp);
+                                       object.etag = NULL;
+                                       object.cdata = pack_cobj (cbdav, split_icalcomp);
+
+                                       success = caldav_server_put_object (cbdav, &object, split_icalcomp, 
cancellable, error);
+                                       if (success && new_components) {
+                                               ECalComponent *new_comp;
+
+                                               /* read the comp from cache again, as some servers can modify 
it on put */
+                                               new_comp = get_ecalcomp_master_from_cache_or_fallback (cbdav, 
icalcomponent_get_uid (split_icalcomp), NULL, comp);
+                                               if (new_comp)
+                                                       e_cal_backend_notify_component_created (E_CAL_BACKEND 
(cbdav), new_comp);
+
+                                               g_clear_object (&new_comp);
+                                       }
+
+                                       caldav_object_free (&object, FALSE);
+                               } else {
+                                       /* mark component as out of synch */
+                                       /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
+                               }
+                       }
+
+                       g_clear_object (&mcomp);
+
+                       if (!processed)
+                               cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone 
(e_cal_component_get_icalcomponent (comp)));
+               } else {
+                       cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone 
(e_cal_component_get_icalcomponent (comp)));
+               }
                break;
        }
 
@@ -4168,7 +4273,7 @@ do_modify_objects (ECalBackendCalDAV *cbdav,
                object.etag = etag;
                object.cdata = pack_cobj (cbdav, cache_comp);
 
-               did_put = caldav_server_put_object (cbdav, &object, cache_comp, cancellable, error);
+               did_put = success && caldav_server_put_object (cbdav, &object, cache_comp, cancellable, 
error);
 
                caldav_object_free (&object, FALSE);
                href = NULL;
@@ -4269,6 +4374,14 @@ do_remove_objects (ECalBackendCalDAV *cbdav,
                break;
        case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
        case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
+               if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, TRUE)) {
+                       if (new_components) {
+                               icalcomponent *master = get_master_comp (cbdav, cache_comp);
+                               if (master) {
+                                       *new_components = g_slist_prepend (*new_components, 
e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
+                               }
+                       }
+               }
                break;
        }
 
diff --git a/calendar/backends/file/e-cal-backend-file.c b/calendar/backends/file/e-cal-backend-file.c
index b7479ee..b39505f 100644
--- a/calendar/backends/file/e-cal-backend-file.c
+++ b/calendar/backends/file/e-cal-backend-file.c
@@ -472,10 +472,9 @@ e_cal_backend_file_get_backend_property (ECalBackend *backend,
                return g_strjoin (
                        ",",
                        CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS,
-                       CAL_STATIC_CAPABILITY_NO_THISANDFUTURE,
+                       CAL_STATIC_CAPABILITY_NO_THISANDPRIOR,
                        CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED,
                        CAL_STATIC_CAPABILITY_REMOVE_ONLY_THIS,
-                       CAL_STATIC_CAPABILITY_NO_THISANDPRIOR,
                        CAL_STATIC_CAPABILITY_BULK_ADDS,
                        CAL_STATIC_CAPABILITY_BULK_MODIFIES,
                        CAL_STATIC_CAPABILITY_BULK_REMOVES,
@@ -2414,7 +2413,7 @@ e_cal_backend_file_modify_objects (ECalBackendSync *backend,
                gchar *rid = NULL;
                gchar *real_rid;
                const gchar *comp_uid;
-               icalcomponent * icalcomp = l->data;
+               icalcomponent * icalcomp = l->data, *split_icalcomp = NULL;
                ECalComponent *comp, *recurrence;
                ECalBackendFileObject *obj_data;
 
@@ -2489,19 +2488,23 @@ e_cal_backend_file_modify_objects (ECalBackendSync *backend,
                        break;
                case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
                case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
-                       if (!rid || !*rid) {
-                               if (old_components)
-                                       *old_components = g_slist_prepend (*old_components, 
obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
-
-                               remove_component (cbfile, comp_uid, obj_data);
-
-                               /* Add the new object */
-                               add_component (cbfile, comp, TRUE);
-                               break;
-                       }
+                       if (!rid || !*rid)
+                               goto like_mod_all;
 
                        /* remove the component from our data, temporarily */
                        if (obj_data->full_object) {
+                               if (mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE &&
+                                   e_cal_util_is_first_instance (obj_data->full_object, 
icalcomponent_get_recurrenceid (icalcomp), resolve_tzid, priv->icalcomp)) {
+                                       icalproperty *prop = icalcomponent_get_first_property (icalcomp, 
ICAL_RECURRENCEID_PROPERTY);
+
+                                       if (prop)
+                                               icalcomponent_remove_property (icalcomp, prop);
+
+                                       e_cal_component_rescan (comp);
+
+                                       goto like_mod_all;
+                               }
+
                                icalcomponent_remove_component (
                                        priv->icalcomp,
                                        e_cal_component_get_icalcomponent (obj_data->full_object));
@@ -2536,24 +2539,58 @@ e_cal_backend_file_modify_objects (ECalBackendSync *backend,
                         * so that it's always before any detached instance we
                         * might have */
                        if (obj_data->full_object) {
+                               struct icaltimetype rid_struct = icalcomponent_get_recurrenceid (icalcomp), 
master_dtstart;
+                               icalcomponent *master_icalcomp = e_cal_component_get_icalcomponent 
(obj_data->full_object);
+                               icalproperty *prop = icalcomponent_get_first_property (icalcomp, 
ICAL_RECURRENCEID_PROPERTY);
+
+                               if (prop)
+                                       icalcomponent_remove_property (icalcomp, prop);
+
+                               master_dtstart = icalcomponent_get_dtstart (master_icalcomp);
+                               if (master_dtstart.zone && master_dtstart.zone != rid_struct.zone)
+                                       rid_struct = icaltime_convert_to_zone (rid_struct, (icaltimezone *) 
master_dtstart.zone);
+                               split_icalcomp = e_cal_util_split_at_instance (icalcomp, rid_struct, 
master_dtstart);
+                               if (split_icalcomp) {
+                                       ECalComponent *prev_comp;
+                                       prev_comp = e_cal_component_clone (obj_data->full_object);
+
+                                       rid_struct = icaltime_convert_to_zone (rid_struct, 
icaltimezone_get_utc_timezone ());
+                                       e_cal_util_remove_instances (e_cal_component_get_icalcomponent 
(obj_data->full_object), rid_struct, mod);
+                                       e_cal_component_rescan (obj_data->full_object);
+
+                                       e_cal_backend_notify_component_modified (E_CAL_BACKEND (backend), 
prev_comp, obj_data->full_object);
+
+                                       g_clear_object (&prev_comp);
+                               }
+
                                icalcomponent_add_component (
                                        priv->icalcomp,
                                        e_cal_component_get_icalcomponent (obj_data->full_object));
                                priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
+                       } else {
+                               struct icaltimetype rid_struct = icalcomponent_get_recurrenceid (icalcomp);
+
+                               split_icalcomp = e_cal_util_split_at_instance (icalcomp, rid_struct, 
icaltime_null_time ());
                        }
 
-                       /* add the new detached recurrence */
-                       g_hash_table_insert (
-                               obj_data->recurrences,
-                               g_strdup (rid),
-                               comp);
-                       icalcomponent_add_component (
-                               priv->icalcomp,
-                               e_cal_component_get_icalcomponent (comp));
-                       priv->comp = g_list_append (priv->comp, comp);
-                       obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
+                       if (split_icalcomp) {
+                               gchar *new_uid;
+
+                               new_uid = e_cal_component_gen_uid ();
+                               icalcomponent_set_uid (split_icalcomp, new_uid);
+                               g_free (new_uid);
+
+                               g_warn_if_fail (e_cal_component_set_icalcomponent (comp, split_icalcomp));
+
+                               /* sanitize the component */
+                               sanitize_component (cbfile, comp);
+
+                               /* Add the object */
+                               add_component (cbfile, comp, TRUE);
+                       }
                        break;
                case E_CAL_OBJ_MOD_ALL :
+ like_mod_all:
                        /* Remove the old version */
                        if (old_components)
                                *old_components = g_slist_prepend (*old_components, obj_data->full_object ? 
e_cal_component_clone (obj_data->full_object) : NULL);
diff --git a/calendar/libecal/e-cal-util.c b/calendar/libecal/e-cal-util.c
index 7c81dba..0034665 100644
--- a/calendar/libecal/e-cal-util.c
+++ b/calendar/libecal/e-cal-util.c
@@ -1081,26 +1081,18 @@ time_matches_rid (struct icaltimetype itt,
        return FALSE;
 }
 
-/**
- * e_cal_util_remove_instances:
- * @icalcomp: A (recurring) #icalcomponent
- * @rid: The base RECURRENCE-ID to remove
- * @mod: How to interpret @rid
- *
- * Removes one or more instances from @comp according to @rid and @mod.
- *
- * FIXME: should probably have a return value indicating whether @icalcomp
- *        still has any instances
- **/
-void
-e_cal_util_remove_instances (icalcomponent *icalcomp,
-                             struct icaltimetype rid,
-                             ECalObjModType mod)
+static void
+e_cal_util_remove_instances_ex (icalcomponent *icalcomp,
+                               struct icaltimetype rid,
+                               ECalObjModType mod,
+                               gboolean keep_rid,
+                               gboolean can_add_exrule)
 {
        icalproperty *prop;
        struct icaltimetype itt, recur;
        struct icalrecurrencetype rule;
        icalrecur_iterator *iter;
+       GSList *remove_props = NULL, *rrules = NULL, *link;
 
        g_return_if_fail (icalcomp != NULL);
        g_return_if_fail (mod != E_CAL_OBJ_MOD_ALL);
@@ -1112,17 +1104,28 @@ e_cal_util_remove_instances (icalcomponent *icalcomp,
                struct icaldatetimeperiodtype period;
 
                period = icalproperty_get_rdate (prop);
-               if (time_matches_rid (period.time, rid, mod))
-                       icalcomponent_remove_property (icalcomp, prop);
+               if (time_matches_rid (period.time, rid, mod) && (!keep_rid ||
+                   icaltime_compare (itt, rid) != 0))
+                       remove_props = g_slist_prepend (remove_props, prop);
        }
        for (prop = icalcomponent_get_first_property (icalcomp, ICAL_EXDATE_PROPERTY);
             prop;
             prop = icalcomponent_get_next_property (icalcomp, ICAL_EXDATE_PROPERTY)) {
                itt = icalproperty_get_exdate (prop);
-               if (time_matches_rid (itt, rid, mod))
-                       icalcomponent_remove_property (icalcomp, prop);
+               if (time_matches_rid (itt, rid, mod) && (!keep_rid ||
+                   icaltime_compare (itt, rid) != 0))
+                       remove_props = g_slist_prepend (remove_props, prop);
        }
 
+       for (link = remove_props; link; link = g_slist_next (link)) {
+               prop = link->data;
+
+               icalcomponent_remove_property (icalcomp, prop);
+       }
+
+       g_slist_free (remove_props);
+       remove_props = NULL;
+
        /* If we're only removing one instance, just add an EXDATE. */
        if (mod == E_CAL_OBJ_MOD_THIS) {
                prop = icalproperty_new_exdate (rid);
@@ -1135,25 +1138,56 @@ e_cal_util_remove_instances (icalcomponent *icalcomp,
        for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
             prop;
             prop = icalcomponent_get_next_property (icalcomp, ICAL_RRULE_PROPERTY)) {
+               rrules = g_slist_prepend (rrules, prop);
+       }
+
+       for (link = rrules; link; link = g_slist_next (link)) {
+               prop = link->data;
                rule = icalproperty_get_rrule (prop);
 
                iter = icalrecur_iterator_new (rule, rid);
                recur = icalrecur_iterator_next (iter);
 
                if (mod & E_CAL_OBJ_MOD_THIS_AND_FUTURE) {
-                       /* If there is a recurrence on or after rid,
-                        * use the UNTIL parameter to truncate the rule
-                        * at rid.
-                        */
+                       /* Truncate the rule at rid. */
                        if (!icaltime_is_null_time (recur)) {
-                               rule.count = 0;
-                               rule.until = rid;
-                               icaltime_adjust (&rule.until, 0, 0, 0, -1);
+                               /* Use count if it was used */
+                               if (rule.count > 0) {
+                                       gint occurrences_count = 0;
+                                       icalrecur_iterator *count_iter;
+                                       struct icaltimetype count_recur;
+
+                                       count_iter = icalrecur_iterator_new (rule, icalcomponent_get_dtstart 
(icalcomp));
+                                       while (count_recur = icalrecur_iterator_next (count_iter), 
!icaltime_is_null_time (count_recur) && occurrences_count < rule.count) {
+                                               if (icaltime_compare (count_recur, rid) >= 0)
+                                                       break;
+
+                                               occurrences_count++;
+                                       }
+
+                                       icalrecur_iterator_free (count_iter);
+
+                                       if (keep_rid && icaltime_compare (count_recur, rid) == 0)
+                                               occurrences_count++;
+
+                                       /* The caller should make sure that the remove will keep at least one 
instance */
+                                       g_warn_if_fail (occurrences_count > 0);
+
+                                       rule.count = occurrences_count;
+                               } else {
+                                       if (keep_rid && icaltime_compare (recur, rid) == 0)
+                                               rule.until = icaltime_add (rid, icalcomponent_get_duration 
(icalcomp));
+                                       else
+                                               rule.until = rid;
+                                       icaltime_adjust (&rule.until, 0, 0, 0, -1);
+                               }
+
                                icalproperty_set_rrule (prop, rule);
+                               icalproperty_remove_parameter_by_name (prop, "X-EVOLUTION-ENDDATE");
                        }
                } else {
                        /* (If recur == rid, skip to the next occurrence) */
-                       if (icaltime_compare (recur, rid) == 0)
+                       if (!keep_rid && icaltime_compare (recur, rid) == 0)
                                recur = icalrecur_iterator_next (iter);
 
                        /* If there is a recurrence after rid, add
@@ -1161,21 +1195,251 @@ e_cal_util_remove_instances (icalcomponent *icalcomp,
                         * Otherwise, just remove the RRULE.
                         */
                        if (!icaltime_is_null_time (recur)) {
-                               rule.count = 0;
-                               /* iCalendar says we should just use rid
-                                * here, but Outlook/Exchange handle
-                                * UNTIL incorrectly.
-                                */
-                               rule.until = icaltime_add (
-                                       rid, icalcomponent_get_duration (icalcomp));
-                               prop = icalproperty_new_exrule (rule);
-                               icalcomponent_add_property (icalcomp, prop);
-                       } else
-                               icalcomponent_remove_property (icalcomp, prop);
+                               if (can_add_exrule) {
+                                       rule.count = 0;
+                                       /* iCalendar says we should just use rid
+                                        * here, but Outlook/Exchange handle
+                                        * UNTIL incorrectly.
+                                        */
+                                       if (keep_rid && icaltime_compare (recur, rid) == 0) {
+                                               struct icaldurationtype duration = icalcomponent_get_duration 
(icalcomp);
+                                               duration.is_neg = !duration.is_neg;
+                                               rule.until = icaltime_add (rid, duration);
+                                       } else
+                                               rule.until = icaltime_add (rid, icalcomponent_get_duration 
(icalcomp));
+                                       prop = icalproperty_new_exrule (rule);
+                                       icalcomponent_add_property (icalcomp, prop);
+                               }
+                       } else {
+                               remove_props = g_slist_prepend (remove_props, prop);
+                       }
                }
 
                icalrecur_iterator_free (iter);
        }
+
+       for (link = remove_props; link; link = g_slist_next (link)) {
+               prop = link->data;
+
+               icalcomponent_remove_property (icalcomp, prop);
+       }
+
+       g_slist_free (remove_props);
+       g_slist_free (rrules);
+}
+
+/**
+ * e_cal_util_remove_instances:
+ * @icalcomp: A (recurring) #icalcomponent
+ * @rid: The base RECURRENCE-ID to remove
+ * @mod: How to interpret @rid
+ *
+ * Removes one or more instances from @comp according to @rid and @mod.
+ *
+ * FIXME: should probably have a return value indicating whether @icalcomp
+ *        still has any instances
+ **/
+void
+e_cal_util_remove_instances (icalcomponent *icalcomp,
+                             struct icaltimetype rid,
+                             ECalObjModType mod)
+{
+       g_return_if_fail (icalcomp != NULL);
+       g_return_if_fail (mod != E_CAL_OBJ_MOD_ALL);
+
+       e_cal_util_remove_instances_ex (icalcomp, rid, mod, FALSE, TRUE);
+}
+
+/**
+ * e_cal_util_split_at_instance:
+ * @icalcomp: A (recurring) #icalcomponent
+ * @rid: The base RECURRENCE-ID to remove
+ * @master_dtstart: The DTSTART of the master object
+ *
+ * Splits a recurring @icalcomp into two at time @rid. The returned icalcomponent
+ * is modified @icalcomp which contains recurrences beginning at @rid, inclusive.
+ * The instance identified by @rid should exist. The @master_dtstart can be
+ * a null time, then it is read from the @icalcomp.
+ *
+ * Use e_cal_util_remove_instances() with E_CAL_OBJ_MOD_THIS_AND_FUTURE mode
+ * on the @icalcomp to remove the overlapping interval from it, if needed.
+ *
+ * Returns: the split icalcomponent, or %NULL.
+ *
+ * Since: 3.14
+ **/
+icalcomponent *
+e_cal_util_split_at_instance (icalcomponent *icalcomp,
+                             struct icaltimetype rid,
+                             struct icaltimetype master_dtstart)
+{
+       icalproperty *prop;
+       struct instance_data instance;
+       struct icaltimetype start, end;
+       struct icaldurationtype duration;
+       GSList *remove_props = NULL, *link;
+
+       g_return_val_if_fail (icalcomp != NULL, NULL);
+       g_return_val_if_fail (!icaltime_is_null_time (rid), NULL);
+
+       /* Make sure this is really recurring */
+       if (!icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY) &&
+           !icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY))
+               return NULL;
+
+       /* Make sure the specified instance really exists */
+       start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ());
+       end = start;
+       icaltime_adjust (&end, 0, 0, 0, 1);
+
+       instance.start = icaltime_as_timet (start);
+       instance.found = FALSE;
+       icalcomponent_foreach_recurrence (icalcomp, start, end,
+                                         check_instance, &instance);
+       /* Make the copy */
+       icalcomp = icalcomponent_new_clone (icalcomp);
+
+       e_cal_util_remove_instances_ex (icalcomp, rid, E_CAL_OBJ_MOD_THIS_AND_PRIOR, TRUE, FALSE);
+
+       start = rid;
+       if (icaltime_is_null_time (master_dtstart))
+               master_dtstart = icalcomponent_get_dtstart (icalcomp);
+       duration = icalcomponent_get_duration (icalcomp);
+
+       /* Expect that DTSTART and DTEND are already set when the instance could not be found */
+       if (instance.found) {
+               icalcomponent_set_dtstart (icalcomp, start);
+               /* Update either DURATION or DTEND */
+               if (icaltime_is_null_time (icalcomponent_get_dtend (icalcomp))) {
+                       icalcomponent_set_duration (icalcomp, duration);
+               } else {
+                       end = start;
+                       if (duration.is_neg)
+                               icaltime_adjust (&end, -duration.days - 7 * duration.weeks, -duration.hours, 
-duration.minutes, -duration.seconds);
+                       else
+                               icaltime_adjust (&end, duration.days + 7 * duration.weeks, duration.hours, 
duration.minutes, duration.seconds);
+                       icalcomponent_set_dtend (icalcomp, end);
+               }
+       }
+
+       /* any RRULE with 'count' should be shortened */
+       for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
+            prop;
+            prop = icalcomponent_get_next_property (icalcomp, ICAL_RRULE_PROPERTY)) {
+               struct icaltimetype recur;
+               struct icalrecurrencetype rule;
+
+               rule = icalproperty_get_rrule (prop);
+
+               if (rule.count != 0) {
+                       gint occurrences_count = 0;
+                       icalrecur_iterator *iter;
+
+                       iter = icalrecur_iterator_new (rule, master_dtstart);
+                       while (recur = icalrecur_iterator_next (iter), !icaltime_is_null_time (recur) && 
occurrences_count < rule.count) {
+                               if (icaltime_compare (recur, rid) >= 0)
+                                       break;
+
+                               occurrences_count++;
+                       }
+
+                       icalrecur_iterator_free (iter);
+
+                       if (icaltime_is_null_time (recur)) {
+                               remove_props = g_slist_prepend (remove_props, prop);
+                       } else {
+                               rule.count -= occurrences_count;
+                               icalproperty_set_rrule (prop, rule);
+                               icalproperty_remove_parameter_by_name (prop, "X-EVOLUTION-ENDDATE");
+                       }
+               }
+       }
+
+       for (link = remove_props; link; link = g_slist_next (link)) {
+               prop = link->data;
+
+               icalcomponent_remove_property (icalcomp, prop);
+       }
+
+       g_slist_free (remove_props);
+
+       return icalcomp;
+}
+
+typedef struct {
+       struct icaltimetype rid;
+       gboolean matches;
+} CheckFirstInstanceData;
+
+static gboolean
+check_first_instance_cb (ECalComponent *comp,
+                        time_t instance_start,
+                        time_t instance_end,
+                        gpointer user_data)
+{
+       CheckFirstInstanceData *ifs = user_data;
+       icalcomponent *icalcomp;
+       struct icaltimetype rid;
+
+       g_return_val_if_fail (ifs != NULL, FALSE);
+
+       icalcomp = e_cal_component_get_icalcomponent (comp);
+       if (icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY) != NULL) {
+               rid = icalcomponent_get_recurrenceid (icalcomp);
+       } else {
+               struct icaltimetype dtstart;
+
+               dtstart = icalcomponent_get_dtstart (icalcomp);
+               if (dtstart.zone) {
+                       rid = icaltime_from_timet_with_zone (instance_start, dtstart.is_date, dtstart.zone);
+               } else {
+                       rid = icaltime_from_timet (instance_start, dtstart.is_date);
+               }
+       }
+
+       ifs->matches = icaltime_compare (ifs->rid, rid) == 0;
+
+       return FALSE;
+}
+
+/**
+ * e_cal_util_is_first_instance:
+ * @comp: an #ECalComponent instance
+ * @rid: a recurrence ID
+ * @tz_cb: (closure tz_cb_data) (scope call): The #ECalRecurResolveTimezoneFn to call
+ * @tz_cb_data: (closure): User data to be passed to the @tz_cb callback
+ *
+ * Returns whether the given @rid is the first instance of
+ * the recurrence defined in the @comp.
+ *
+ * Return: Whether the @rid identifies the first instance of @comp.
+ *
+ * Since: 3.14
+ **/
+gboolean
+e_cal_util_is_first_instance (ECalComponent *comp,
+                             struct icaltimetype rid,
+                             ECalRecurResolveTimezoneFn tz_cb,
+                             gpointer tz_cb_data)
+{
+       CheckFirstInstanceData ifs;
+       icalcomponent *icalcomp;
+       time_t start, end;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
+       g_return_val_if_fail (!icaltime_is_null_time (rid), FALSE);
+
+       ifs.rid = rid;
+       ifs.matches = FALSE;
+
+       icalcomp = e_cal_component_get_icalcomponent (comp);
+       start = icaltime_as_timet (icalcomponent_get_dtstart (icalcomp)) - 24 * 60 * 60;
+       end = icaltime_as_timet (icalcomponent_get_dtend (icalcomp)) + 24 * 60 * 60;
+
+       e_cal_recur_generate_instances (comp, start, end, check_first_instance_cb, &ifs,
+               tz_cb, tz_cb_data, icaltimezone_get_utc_timezone ());
+
+       return ifs.matches;
 }
 
 /**
diff --git a/calendar/libecal/e-cal-util.h b/calendar/libecal/e-cal-util.h
index 3e51498..cad1fe5 100644
--- a/calendar/libecal/e-cal-util.h
+++ b/calendar/libecal/e-cal-util.h
@@ -201,6 +201,13 @@ icalcomponent *    e_cal_util_construct_instance   (icalcomponent *icalcomp,
 void           e_cal_util_remove_instances     (icalcomponent *icalcomp,
                                                 struct icaltimetype rid,
                                                 ECalObjModType mod);
+icalcomponent *        e_cal_util_split_at_instance    (icalcomponent *icalcomp,
+                                                struct icaltimetype rid,
+                                                struct icaltimetype master_dtstart);
+gboolean       e_cal_util_is_first_instance    (ECalComponent *comp,
+                                                struct icaltimetype rid,
+                                                ECalRecurResolveTimezoneFn tz_cb,
+                                                gpointer tz_cb_data);
 
 gchar *                e_cal_util_get_system_timezone_location (void);
 icaltimezone * e_cal_util_get_system_timezone (void);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]