[evolution-data-server] Calendar: Add functions to clamp a VTIMEZONE component



commit ad3e1ce81defa2c6147991e8bcc8727e37391a91
Author: Milan Crha <mcrha redhat com>
Date:   Thu Feb 18 13:39:58 2021 +0100

    Calendar: Add functions to clamp a VTIMEZONE component
    
    Can be used to limit the data transfer for simple events where
    the whole history of the timezone shifts doesn't matter.

 src/calendar/libecal/e-cal-util.c              | 137 +++++++++++++++++++
 src/calendar/libecal/e-cal-util.h              |   6 +
 src/calendar/libedata-cal/e-cal-meta-backend.c |  81 ++++++++++-
 tests/libecal/CMakeLists.txt                   |   1 +
 tests/libecal/test-cal-utils.c                 | 182 +++++++++++++++++++++++++
 5 files changed, 406 insertions(+), 1 deletion(-)
---
diff --git a/src/calendar/libecal/e-cal-util.c b/src/calendar/libecal/e-cal-util.c
index 09121eeba..d18fbbabf 100644
--- a/src/calendar/libecal/e-cal-util.c
+++ b/src/calendar/libecal/e-cal-util.c
@@ -2996,3 +2996,140 @@ e_cal_util_set_alarm_acknowledged (ECalComponent *component,
 
        return TRUE;
 }
+
+static void
+e_cal_util_clamp_vtimezone_subcomps (ICalComponent *vtimezone,
+                                    ICalComponentKind kind,
+                                    const ICalTime *from,
+                                    const ICalTime *to)
+{
+       ICalComponent *subcomp;
+       ICalComponent *nearest_from_comp = NULL, *nearest_to_comp = NULL;
+       ICalTime *nearest_from_time = NULL, *nearest_to_time = NULL;
+       GSList *remove = NULL, *link;
+
+       for (subcomp = i_cal_component_get_first_component (vtimezone, kind);
+            subcomp;
+            g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (vtimezone, kind)) {
+               ICalTime *dtstart;
+
+               dtstart = i_cal_component_get_dtstart (subcomp);
+               if (dtstart && !i_cal_time_is_null_time (dtstart) && i_cal_time_is_valid_time (dtstart)) {
+                       gint cmp;
+
+                       cmp = i_cal_time_compare (dtstart, from);
+                       if (cmp < 0) {
+                               if (nearest_from_time) {
+                                       if (i_cal_time_compare (dtstart, nearest_from_time) > 0) {
+                                               g_clear_object (&nearest_from_time);
+                                               nearest_from_time = g_object_ref (dtstart);
+                                               remove = g_slist_prepend (remove, nearest_from_comp);
+                                               nearest_from_comp = g_object_ref (subcomp);
+                                       } else {
+                                               remove = g_slist_prepend (remove, g_object_ref (subcomp));
+                                       }
+                               } else {
+                                       nearest_from_time = g_object_ref (dtstart);
+                                       nearest_from_comp = g_object_ref (subcomp);
+                               }
+                       } else if (cmp > 0 && to) {
+                               cmp = i_cal_time_compare (to, dtstart);
+                               if (cmp < 0)
+                                       remove = g_slist_prepend (remove, g_object_ref (subcomp));
+                       }
+               }
+
+               g_clear_object (&dtstart);
+       }
+
+       g_clear_object (&nearest_from_comp);
+       g_clear_object (&nearest_from_time);
+       g_clear_object (&nearest_to_comp);
+       g_clear_object (&nearest_to_time);
+
+       for (link = remove; link; link = g_slist_next (link)) {
+               subcomp = link->data;
+
+               i_cal_component_remove_component (vtimezone, subcomp);
+       }
+
+       g_slist_free_full (remove, g_object_unref);
+}
+
+/**
+ * e_cal_util_clamp_vtimezone:
+ * @vtimezone: (inout): a VTIMEZONE component to modify
+ * @from: an #ICalTime for the minimum time
+ * @to: (nullable): until which time to clamp, or %NULL for infinity
+ *
+ * Modifies the @vtimezone to include only subcomponents influencing
+ * the passed-in time interval between @from and @to.
+ *
+ * Since: 3.40
+ **/
+void
+e_cal_util_clamp_vtimezone (ICalComponent *vtimezone,
+                           const ICalTime *from,
+                           const ICalTime *to)
+{
+       g_return_if_fail (I_CAL_IS_COMPONENT (vtimezone));
+       g_return_if_fail (i_cal_component_isa (vtimezone) == I_CAL_VTIMEZONE_COMPONENT);
+       g_return_if_fail (I_CAL_IS_TIME (from));
+       if (to)
+               g_return_if_fail (I_CAL_IS_TIME (to));
+
+       e_cal_util_clamp_vtimezone_subcomps (vtimezone, I_CAL_XSTANDARD_COMPONENT, from, to);
+       e_cal_util_clamp_vtimezone_subcomps (vtimezone, I_CAL_XDAYLIGHT_COMPONENT, from, to);
+}
+
+/**
+ * e_cal_util_clamp_vtimezone_by_component:
+ * @vtimezone: (inout): a VTIMEZONE component to modify
+ * @component: an #ICalComponent to read the times from
+ *
+ * Similar to e_cal_util_clamp_vtimezone(), only reads the clamp
+ * times from the @component.
+ *
+ * Since: 3.40
+ **/
+void
+e_cal_util_clamp_vtimezone_by_component (ICalComponent *vtimezone,
+                                        ICalComponent *component)
+{
+       ICalProperty *prop;
+       ICalTime *dtstart, *dtend = NULL;
+
+       g_return_if_fail (I_CAL_IS_COMPONENT (vtimezone));
+       g_return_if_fail (i_cal_component_isa (vtimezone) == I_CAL_VTIMEZONE_COMPONENT);
+       g_return_if_fail (I_CAL_IS_COMPONENT (component));
+
+       dtstart = i_cal_component_get_dtstart (component);
+       if (!dtstart)
+               return;
+
+       prop = i_cal_component_get_first_property (component, I_CAL_RECURRENCEID_PROPERTY);
+       if (prop) {
+               ICalTime *recurid;
+
+               recurid = i_cal_property_get_recurrenceid (prop);
+
+               dtend = i_cal_component_get_dtend (component);
+               if (dtend && i_cal_time_compare (recurid, dtend) >= 0) {
+                       g_clear_object (&dtend);
+                       dtend = recurid;
+                       recurid = NULL;
+               }
+
+               g_clear_object (&recurid);
+               g_object_unref (prop);
+       } else if (!e_cal_util_component_has_rrules (component)) {
+               dtend = i_cal_component_get_dtend (component);
+               if (!dtend)
+                       dtend = g_object_ref (dtstart);
+       }
+
+       e_cal_util_clamp_vtimezone (vtimezone, dtstart, dtend);
+
+       g_clear_object (&dtstart);
+       g_clear_object (&dtend);
+}
diff --git a/src/calendar/libecal/e-cal-util.h b/src/calendar/libecal/e-cal-util.h
index e9bb1a65a..425eef9e8 100644
--- a/src/calendar/libecal/e-cal-util.h
+++ b/src/calendar/libecal/e-cal-util.h
@@ -351,6 +351,12 @@ gboolean   e_cal_util_set_alarm_acknowledged
                                                (ECalComponent *component,
                                                 const gchar *auid,
                                                 gint64 when); /* as time_t in UTC */
+void           e_cal_util_clamp_vtimezone      (ICalComponent *vtimezone,
+                                                const ICalTime *from,
+                                                const ICalTime *to);
+void           e_cal_util_clamp_vtimezone_by_component
+                                               (ICalComponent *vtimezone,
+                                                ICalComponent *component);
 
 G_END_DECLS
 
diff --git a/src/calendar/libedata-cal/e-cal-meta-backend.c b/src/calendar/libedata-cal/e-cal-meta-backend.c
index 023a2fbe2..159e35558 100644
--- a/src/calendar/libedata-cal/e-cal-meta-backend.c
+++ b/src/calendar/libedata-cal/e-cal-meta-backend.c
@@ -3982,7 +3982,9 @@ e_cal_meta_backend_merge_instances (ECalMetaBackend *meta_backend,
 {
        ForeachTzidData f_data;
        ICalComponent *vcalendar;
+       ICalTime *min_start = NULL, *max_end = NULL;
        GSList *link, *sorted;
+       gboolean has_rrules = FALSE;
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), NULL);
        g_return_val_if_fail (instances != NULL, NULL);
@@ -3997,6 +3999,8 @@ e_cal_meta_backend_merge_instances (ECalMetaBackend *meta_backend,
 
        for (link = sorted; link; link = g_slist_next (link)) {
                ECalComponent *comp = link->data;
+               ICalProperty *prop;
+               ICalTime *tt;
 
                if (!E_IS_CAL_COMPONENT (comp)) {
                        g_warn_if_reached ();
@@ -4006,11 +4010,86 @@ e_cal_meta_backend_merge_instances (ECalMetaBackend *meta_backend,
                f_data.icomp = i_cal_component_clone (e_cal_component_get_icalcomponent (comp));
 
                i_cal_component_foreach_tzid (f_data.icomp, add_timezone_cb, &f_data);
-               i_cal_component_take_component (vcalendar, f_data.icomp);
+               i_cal_component_add_component (vcalendar, f_data.icomp);
+
+               has_rrules = has_rrules || e_cal_util_component_has_rrules (f_data.icomp);
+               tt = i_cal_component_get_dtstart (f_data.icomp);
+
+               if (!min_start && tt) {
+                       min_start = tt;
+                       tt = NULL;
+               } else if (tt && i_cal_time_compare (tt, min_start) < 0) {
+                       g_clear_object (&min_start);
+                       min_start = tt;
+                       tt = NULL;
+               }
+
+               prop = has_rrules ? NULL : i_cal_component_get_first_property (f_data.icomp, 
I_CAL_RECURRENCEID_PROPERTY);
+               if (prop) {
+                       ICalTime *recurid, *dtend;
+
+                       recurid = i_cal_property_get_recurrenceid (prop);
+                       g_object_unref (prop);
+
+                       dtend = i_cal_component_get_dtend (f_data.icomp);
+                       if (dtend && i_cal_time_compare (recurid, dtend) >= 0) {
+                               g_clear_object (&dtend);
+                               dtend = recurid;
+                               recurid = NULL;
+                       }
+
+                       if (max_end && dtend && i_cal_time_compare (max_end, dtend) < 0) {
+                               g_clear_object (&max_end);
+                               max_end = dtend;
+                               dtend = NULL;
+                       } else if (!max_end) {
+                               max_end = dtend;
+                               dtend = NULL;
+                       }
+
+                       g_clear_object (&recurid);
+                       g_clear_object (&dtend);
+               } else if (has_rrules) {
+                       g_clear_object (&max_end);
+               } else {
+                       ICalTime *dtend;
+
+                       dtend = i_cal_component_get_dtend (f_data.icomp);
+
+                       if (!dtend)
+                               dtend = tt ? g_object_ref (tt) : (min_start ? g_object_ref (min_start) : 
NULL);
+
+                       if (max_end && dtend && i_cal_time_compare (max_end, dtend) < 0) {
+                               g_clear_object (&max_end);
+                               max_end = dtend;
+                               dtend = NULL;
+                       } else if (!max_end) {
+                               max_end = dtend;
+                               dtend = NULL;
+                       }
+
+                       g_clear_object (&dtend);
+               }
+
+               g_clear_object (&f_data.icomp);
+               g_clear_object (&tt);
        }
 
        g_slist_free (sorted);
 
+       if (min_start) {
+               ICalComponent *subcomp;
+
+               for (subcomp = i_cal_component_get_first_component (vcalendar, I_CAL_VTIMEZONE_COMPONENT);
+                    subcomp;
+                    g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (vcalendar, 
I_CAL_VTIMEZONE_COMPONENT)) {
+                       e_cal_util_clamp_vtimezone (subcomp, min_start, max_end);
+               }
+       }
+
+       g_clear_object (&min_start);
+       g_clear_object (&max_end);
+
        return vcalendar;
 }
 
diff --git a/tests/libecal/CMakeLists.txt b/tests/libecal/CMakeLists.txt
index 6d740442c..fc9aa5412 100644
--- a/tests/libecal/CMakeLists.txt
+++ b/tests/libecal/CMakeLists.txt
@@ -35,6 +35,7 @@ set(TESTS
        test-cal-component
        test-cal-recur
        test-cal-reminders
+       test-cal-utils
 )
 
 foreach(_test ${TESTS})
diff --git a/tests/libecal/test-cal-utils.c b/tests/libecal/test-cal-utils.c
new file mode 100644
index 000000000..0d076a1ea
--- /dev/null
+++ b/tests/libecal/test-cal-utils.c
@@ -0,0 +1,182 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <libecal/libecal.h>
+
+#include "e-test-server-utils.h"
+
+static ETestServerClosure test_closure = { E_TEST_SERVER_CALENDAR, NULL, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, 
FALSE, NULL, FALSE };
+
+#define DEF_SUBCOMP(x, dt) \
+       "BEGIN:" x "\r\n" \
+       "TZNAME:NM" x "\r\n" \
+       "DTSTART:" dt "T230000\r\n" \
+       "TZOFFSETFROM:+0100\r\n" \
+       "TZOFFSETTO:+0200\r\n" \
+       "RRULE:FREQ=YEARLY;UNTIL=" dt "T220000Z;BYDAY=-1SU;BYMONTH=4\r\n" \
+       "END:" x "\r\n"
+
+#define DEF_VTIMEZONE(location, content) \
+       "BEGIN:VTIMEZONE\r\n" \
+       "TZID:/id.no.where/" location "\r\n" \
+       "X-LIC-LOCATION:" location "\r\n" \
+       content \
+       "END:VTIMEZONE\r\n"
+
+static void
+test_clamp_vtimezone (ETestServerFixture *fixture,
+                     gconstpointer user_data)
+{
+       const gchar *vtimezone_str =
+               DEF_VTIMEZONE ("Some/Location",
+                       DEF_SUBCOMP ("DAYLIGHT", "19810301")
+                       DEF_SUBCOMP ("STANDARD", "19811001")
+                       DEF_SUBCOMP ("DAYLIGHT", "19820301")
+                       DEF_SUBCOMP ("STANDARD", "19821001")
+                       DEF_SUBCOMP ("DAYLIGHT", "19830301")
+                       DEF_SUBCOMP ("STANDARD", "19831001")
+                       DEF_SUBCOMP ("DAYLIGHT", "19840301")
+                       DEF_SUBCOMP ("STANDARD", "19841001")
+                       DEF_SUBCOMP ("DAYLIGHT", "19850301")
+                       DEF_SUBCOMP ("STANDARD", "19851001")
+               );
+       ICalComponent *comp, *vtimezone;
+       ICalTime *from, *to;
+
+       vtimezone = i_cal_component_new_from_string (vtimezone_str);
+
+       g_assert_nonnull (vtimezone);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XDAYLIGHT_COMPONENT), ==, 5);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XSTANDARD_COMPONENT), ==, 5);
+
+       from = i_cal_time_new_from_string ("19830101T000000Z");
+       to = i_cal_time_new_from_string ("19830815T000000Z");
+
+       g_assert_nonnull (from);
+       g_assert_nonnull (to);
+
+       e_cal_util_clamp_vtimezone (vtimezone, from, NULL);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XDAYLIGHT_COMPONENT), ==, 4);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XSTANDARD_COMPONENT), ==, 4);
+
+       g_object_unref (vtimezone);
+       vtimezone = i_cal_component_new_from_string (vtimezone_str);
+
+       e_cal_util_clamp_vtimezone (vtimezone, from, to);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XDAYLIGHT_COMPONENT), ==, 2);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XSTANDARD_COMPONENT), ==, 1);
+
+       g_object_unref (vtimezone);
+       vtimezone = i_cal_component_new_from_string (vtimezone_str);
+
+       comp = i_cal_component_new_from_string (
+               "BEGIN:VEVENT\r\n"
+               "UID:1\r\n"
+               "DTSTART;VALUE=DATE:19821003\r\n"
+               "END:VEVENT\r\n");
+       g_assert_nonnull (comp);
+
+       e_cal_util_clamp_vtimezone_by_component (vtimezone, comp);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XDAYLIGHT_COMPONENT), ==, 1);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XSTANDARD_COMPONENT), ==, 1);
+
+       g_object_unref (comp);
+       g_object_unref (vtimezone);
+       vtimezone = i_cal_component_new_from_string (vtimezone_str);
+
+       comp = i_cal_component_new_from_string (
+               "BEGIN:VEVENT\r\n"
+               "UID:1\r\n"
+               "DTSTART;VALUE=DATE:19820903\r\n"
+               "DTEND;VALUE=DATE:19831103\r\n"
+               "END:VEVENT\r\n");
+       g_assert_nonnull (comp);
+
+       e_cal_util_clamp_vtimezone_by_component (vtimezone, comp);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XDAYLIGHT_COMPONENT), ==, 2);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XSTANDARD_COMPONENT), ==, 3);
+
+       g_object_unref (comp);
+       g_object_unref (vtimezone);
+       vtimezone = i_cal_component_new_from_string (vtimezone_str);
+
+       comp = i_cal_component_new_from_string (
+               "BEGIN:VEVENT\r\n"
+               "UID:1\r\n"
+               "DTSTART:19820903T080000Z\r\n"
+               "DTEND:19820903T090000Z\r\n"
+               "END:VEVENT\r\n");
+       g_assert_nonnull (comp);
+
+       e_cal_util_clamp_vtimezone_by_component (vtimezone, comp);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XDAYLIGHT_COMPONENT), ==, 1);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XSTANDARD_COMPONENT), ==, 1);
+
+       g_object_unref (comp);
+       g_object_unref (vtimezone);
+       vtimezone = i_cal_component_new_from_string (vtimezone_str);
+
+       comp = i_cal_component_new_from_string (
+               "BEGIN:VEVENT\r\n"
+               "UID:1\r\n"
+               "DTSTART:19820903T080000Z\r\n"
+               "DTEND:19820903T090000Z\r\n"
+               "RRULE:FREQ=DAILY;UNTIL=19840101T010000Z\r\n"
+               "END:VEVENT\r\n");
+       g_assert_nonnull (comp);
+
+       e_cal_util_clamp_vtimezone_by_component (vtimezone, comp);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XDAYLIGHT_COMPONENT), ==, 4);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XSTANDARD_COMPONENT), ==, 5);
+
+       g_object_unref (comp);
+       g_object_unref (vtimezone);
+       vtimezone = i_cal_component_new_from_string (vtimezone_str);
+
+       comp = i_cal_component_new_from_string (
+               "BEGIN:VEVENT\r\n"
+               "UID:1\r\n"
+               "DTSTART:19821004T080000Z\r\n"
+               "DTEND:19821004T090000Z\r\n"
+               "RRULE:FREQ=DAILY;UNTIL=20000101T010000Z\r\n"
+               "RECURRENCE-ID:19841004T090000Z\r\n"
+               "END:VEVENT\r\n");
+       g_assert_nonnull (comp);
+
+       e_cal_util_clamp_vtimezone_by_component (vtimezone, comp);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XDAYLIGHT_COMPONENT), ==, 3);
+       g_assert_cmpint (i_cal_component_count_components (vtimezone, I_CAL_XSTANDARD_COMPONENT), ==, 3);
+       g_object_unref (comp);
+
+       g_object_unref (vtimezone);
+       g_object_unref (from);
+       g_object_unref (to);
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+       g_test_bug_base ("https://gitlab.gnome.org/GNOME/evolution-data-server/issues/";);
+
+       g_test_add ("/ECalUtils/ClampVTIMEZONE", ETestServerFixture, &test_closure,
+               e_test_server_utils_setup, test_clamp_vtimezone, e_test_server_utils_teardown);
+
+       return e_test_server_utils_run (argc, argv);
+}


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