[evolution-ews/wip/mcrha/office365: 44/50] Calendar read/write improvements




commit bcb1b417a8b82c127dea2e5cea40b2c3e88fb471
Author: Milan Crha <mcrha redhat com>
Date:   Tue Jul 28 17:33:36 2020 +0200

    Calendar read/write improvements

 src/Microsoft365/calendar/e-cal-backend-m365.c | 195 ++++++++++++++++++++++++-
 src/Microsoft365/common/CMakeLists.txt         |   3 +
 src/Microsoft365/common/e-m365-json-utils.c    |  30 +++-
 src/Microsoft365/common/e-m365-tz-utils.c      | 184 +++++++++++++++++++++++
 src/Microsoft365/common/e-m365-tz-utils.h      |  21 +++
 5 files changed, 424 insertions(+), 9 deletions(-)
---
diff --git a/src/Microsoft365/calendar/e-cal-backend-m365.c b/src/Microsoft365/calendar/e-cal-backend-m365.c
index b054bb22..a34180d9 100644
--- a/src/Microsoft365/calendar/e-cal-backend-m365.c
+++ b/src/Microsoft365/calendar/e-cal-backend-m365.c
@@ -16,6 +16,7 @@
 
 #include "common/camel-m365-settings.h"
 #include "common/e-m365-connection.h"
+#include "common/e-m365-tz-utils.h"
 #include "common/e-source-m365-folder.h"
 
 #include "e-cal-backend-m365.h"
@@ -221,6 +222,11 @@ ecb_m365_get_date_time_zone (ECalBackendM365 *cbm365,
        /* Reads the time in UTC, just make sure it's still a true expectation */
        g_warn_if_fail (!zone || !*zone || g_strcmp0 (zone, "UTC") == 0);
 
+       tzid = e_m365_tz_utils_get_ical_equivalent (tzid);
+
+       if (!tzid)
+               tzid = "UTC";
+
        tz = ecb_m365_get_timezone_sync (cbm365, tzid);
        itt = i_cal_time_new_from_timet_with_zone (tt, e_m365_event_get_is_all_day (m365_event), 
i_cal_timezone_get_utc_timezone ());
 
@@ -242,7 +248,75 @@ ecb_m365_add_date_time_zone (ECalBackendM365 *cbm365,
                             ICalPropertyKind prop_kind,
                             JsonBuilder *builder)
 {
-       /* TODO */                      
+       ICalProperty *new_prop;
+       ICalParameter *new_param;
+       ICalTime *old_value, *new_value;
+       const gchar *new_tzid = NULL;
+       void (* add_func) (JsonBuilder *builder, time_t date_time, const gchar *zone) = NULL;
+       gboolean same = FALSE;
+
+       if (prop_kind == I_CAL_DTSTART_PROPERTY) {
+               new_value = i_cal_component_get_dtstart (new_comp);
+               old_value = old_comp ? i_cal_component_get_dtstart (old_comp) : NULL;
+               add_func = e_m365_event_add_start;
+       } else if (prop_kind == I_CAL_DTEND_PROPERTY) {
+               new_value = i_cal_component_get_dtend (new_comp);
+               old_value = old_comp ? i_cal_component_get_dtend (old_comp) : NULL;
+               add_func = e_m365_event_add_end;
+       } else {
+               g_warn_if_reached ();
+               return;
+       }
+
+       if (!new_value && !old_value)
+               return;
+
+       new_prop = i_cal_component_get_first_property (new_comp, prop_kind);
+       new_param = new_prop ? i_cal_property_get_first_parameter (new_prop, I_CAL_TZID_PARAMETER) : NULL;
+
+       if (new_param)
+               new_tzid = i_cal_parameter_get_tzid (new_param);
+
+       if (new_value && old_value) {
+               same = i_cal_time_compare (new_value, old_value) == 0;
+
+               if (same) {
+                       ICalProperty *old_prop;
+                       ICalParameter *old_param;
+                       const gchar *old_tzid;
+
+                       old_prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+                       old_param = old_prop ? i_cal_property_get_first_parameter (old_prop, 
I_CAL_TZID_PARAMETER) : NULL;
+                       old_tzid = old_param ? i_cal_parameter_get_tzid (old_param) : NULL;
+
+                       same = g_strcmp0 (old_tzid, new_tzid) == 0;
+
+                       g_clear_object (&old_param);
+                       g_clear_object (&old_prop);
+               }
+       }
+
+       if (!same) {
+               ICalTimezone *izone = NULL;
+               const gchar *wzone = NULL;
+               time_t tt;
+
+               if (new_tzid) {
+                       izone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cbm365), new_tzid);
+
+                       if (izone)
+                               wzone = e_m365_tz_utils_get_msdn_equivalent (i_cal_timezone_get_location 
(izone));
+               }
+
+               tt = i_cal_time_as_timet_with_zone (new_value, wzone ? NULL : izone);
+
+               add_func (builder, tt, wzone);
+       }
+
+       g_clear_object (&new_prop);
+       g_clear_object (&new_param);
+       g_clear_object (&new_value);
+       g_clear_object (&old_value);
 }
 
 static void
@@ -290,6 +364,67 @@ ecb_m365_get_categories (ECalBackendM365 *cbm365,
        }
 }
 
+static void
+ecb_m365_extract_categories (ICalComponent *comp,
+                            GHashTable **out_hash, /* gchar * ~> NULL */
+                            GSList **out_slist) /* gchar * */
+{
+       ICalProperty *prop;
+
+       if (!comp)
+               return;
+
+       for (prop = i_cal_component_get_first_property (comp, I_CAL_CATEGORIES_PROPERTY);
+            prop;
+            g_object_unref (prop), prop = i_cal_component_get_next_property (comp, 
I_CAL_CATEGORIES_PROPERTY)) {
+               const gchar *categories;
+
+               categories = i_cal_property_get_categories (prop);
+
+               if (categories && *categories) {
+                       if (out_hash && !*out_hash)
+                               *out_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+                       if (strchr (categories, ',')) {
+                               gchar **strv;
+                               guint ii;
+
+                               strv = g_strsplit (categories, ",", -1);
+
+                               for (ii = 0; strv[ii]; ii++) {
+                                       gchar *category = g_strchomp (strv[ii]);
+
+                                       if (*category) {
+                                               if (out_hash) {
+                                                       g_hash_table_insert (*out_hash, category, NULL);
+                                               } else if (out_slist) {
+                                                       *out_slist = g_slist_prepend (*out_slist, category);
+                                               } else {
+                                                       g_warn_if_reached ();
+                                                       g_free (category);
+                                               }
+                                       } else {
+                                               g_free (category);
+                                       }
+                               }
+
+                               g_free (strv);
+                       } else if (out_hash) {
+                               g_hash_table_insert (*out_hash, g_strchomp (g_strdup (categories)), NULL);
+                       } else if (out_slist) {
+                               *out_slist = g_slist_prepend (*out_slist, g_strchomp (g_strdup (categories)));
+                       } else {
+                               g_warn_if_reached ();
+                       }
+               }
+       }
+
+       g_clear_object (&prop);
+
+       if (out_slist && *out_slist)
+               *out_slist = g_slist_reverse (*out_slist);
+}
+
 static void
 ecb_m365_add_categories (ECalBackendM365 *cbm365,
                         ICalComponent *new_comp,
@@ -297,6 +432,49 @@ ecb_m365_add_categories (ECalBackendM365 *cbm365,
                         ICalPropertyKind prop_kind,
                         JsonBuilder *builder)
 {
+       GHashTable *old_value = NULL;
+       GSList *new_value = NULL;
+
+       ecb_m365_extract_categories (new_comp, NULL, &new_value);
+       ecb_m365_extract_categories (old_comp, &old_value, NULL);
+
+       if (!new_value && !old_value)
+               return;
+
+       if (new_value) {
+               GSList *link;
+               gboolean same = FALSE;
+
+               if (old_value && g_hash_table_size (old_value) == g_slist_length (new_value)) {
+                       same = TRUE;
+
+                       for (link = new_value; link && same; link = g_slist_next (link)) {
+                               const gchar *category = link->data;
+
+                               same = g_hash_table_contains (old_value, category);
+                       }
+               }
+
+               if (!same) {
+                       e_m365_event_begin_categories (builder);
+
+                       for (link = new_value; link; link = g_slist_next (link)) {
+                               const gchar *category = link->data;
+
+                               e_m365_event_add_category (builder, category);
+                       }
+
+                       e_m365_event_end_categories (builder);
+               }
+       } else {
+               e_m365_event_begin_categories (builder);
+               e_m365_event_end_categories (builder);
+       }
+
+       if (new_value)
+               g_slist_free_full (new_value, g_free);
+       if (old_value)
+               g_hash_table_destroy (old_value);
 }
 
 static void
@@ -2168,12 +2346,20 @@ ecb_m365_save_component_sync (ECalMetaBackend *meta_backend,
        gboolean success = FALSE;
 
        g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
+       g_return_val_if_fail (instances != NULL, FALSE);
+
+       if (instances->next) {
+               g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
+                       _("Can store only simple events into Microsoft 365 calendar")));
+
+               return FALSE;
+       }
 
        cbm365 = E_CAL_BACKEND_M365 (meta_backend);
 
        LOCK (cbm365);
 
-       new_comp = e_cal_meta_backend_merge_instances (meta_backend, instances, TRUE);
+       new_comp = e_cal_component_get_icalcomponent (instances->data);
 
        if (extra && *extra) {
                const gchar *comp_str;
@@ -2245,7 +2431,6 @@ ecb_m365_save_component_sync (ECalMetaBackend *meta_backend,
        ecb_m365_convert_error_to_client_error (error);
        ecb_m365_maybe_disconnect_sync (cbm365, error, cancellable);
 
-       g_clear_object (&new_comp);
        g_clear_object (&old_comp);
 
        return success;
@@ -2386,6 +2571,8 @@ ecb_m365_constructed (GObject *object)
        g_mkdir_with_parents (cbm365->priv->attachments_dir, 0777);
 
        g_free (cache_dirname);
+
+       e_m365_tz_utils_ref_windows_zones ();
 }
 
 static void
@@ -2408,6 +2595,8 @@ ecb_m365_finalize (GObject *object)
 
        g_rec_mutex_clear (&cbm365->priv->property_lock);
 
+       e_m365_tz_utils_unref_windows_zones ();
+
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (e_cal_backend_m365_parent_class)->finalize (object);
 }
diff --git a/src/Microsoft365/common/CMakeLists.txt b/src/Microsoft365/common/CMakeLists.txt
index 48737d7b..671ae495 100644
--- a/src/Microsoft365/common/CMakeLists.txt
+++ b/src/Microsoft365/common/CMakeLists.txt
@@ -10,6 +10,8 @@ set(SOURCES
        e-m365-enums.h
        e-m365-json-utils.c
        e-m365-json-utils.h
+       e-m365-tz-utils.c
+       e-m365-tz-utils.h
        e-oauth2-service-microsoft365.c
        e-oauth2-service-microsoft365.h
        e-source-m365-folder.c
@@ -24,6 +26,7 @@ add_library(evolution-microsoft365 SHARED
 
 target_compile_definitions(evolution-microsoft365 PRIVATE
        -DG_LOG_DOMAIN=\"evolution-microsoft365\"
+       -DM365_DATADIR=\"${ewsdatadir}\"
 )
 
 target_compile_options(evolution-microsoft365 PUBLIC
diff --git a/src/Microsoft365/common/e-m365-json-utils.c b/src/Microsoft365/common/e-m365-json-utils.c
index b9f7fb85..020adbc3 100644
--- a/src/Microsoft365/common/e-m365-json-utils.c
+++ b/src/Microsoft365/common/e-m365-json-utils.c
@@ -656,10 +656,11 @@ e_m365_get_date_time_offset_member (JsonObject *object,
        return res;
 }
 
-void
-e_m365_add_date_time_offset_member (JsonBuilder *builder,
-                                   const gchar *member_name,
-                                   time_t value)
+static void
+e_m365_add_date_time_offset_member_ex (JsonBuilder *builder,
+                                      const gchar *member_name,
+                                      time_t value,
+                                      gboolean with_utc_zone_char)
 {
        GDateTime *dt;
        gchar *value_str;
@@ -674,12 +675,29 @@ e_m365_add_date_time_offset_member (JsonBuilder *builder,
 
        value_str = g_date_time_format_iso8601 (dt);
 
+       if (value_str && !with_utc_zone_char) {
+               gchar *z_pos;
+
+               z_pos = strrchr (value_str, 'Z');
+
+               if (z_pos)
+                       *z_pos = '\0';
+       }
+
        e_m365_json_add_string_member (builder, member_name, value_str);
 
        g_date_time_unref (dt);
        g_free (value_str);
 }
 
+void
+e_m365_add_date_time_offset_member (JsonBuilder *builder,
+                                   const gchar *member_name,
+                                   time_t value)
+{
+       e_m365_add_date_time_offset_member_ex (builder, member_name, value, TRUE);
+}
+
 /* https://docs.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0 */
 
 time_t
@@ -709,8 +727,8 @@ e_m365_add_date_time (JsonBuilder *builder,
 
        e_m365_json_begin_object_member (builder, member_name);
 
-       e_m365_add_date_time_offset_member (builder, "dateTime", date_time);
-       e_m365_json_add_nonempty_string_member (builder, "timeZone", zone);
+       e_m365_add_date_time_offset_member_ex (builder, "dateTime", date_time, FALSE);
+       e_m365_json_add_string_member (builder, "timeZone", (zone && *zone) ? zone : "UTC");
 
        e_m365_json_end_object_member (builder);
 }
diff --git a/src/Microsoft365/common/e-m365-tz-utils.c b/src/Microsoft365/common/e-m365-tz-utils.c
new file mode 100644
index 00000000..bc0cf6bd
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-tz-utils.c
@@ -0,0 +1,184 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include "e-m365-tz-utils.h"
+
+/*
+ * A bunch of global variables used to map the ICalTimezone to MSDN[0] format.
+ * Also, some auxiliar functions to translate from one tz type to another.
+ *
+ * [0]: http://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx
+ */
+static GRecMutex tz_mutex;
+static GHashTable *ical_to_msdn = NULL;
+static GHashTable *msdn_to_ical = NULL;
+static guint tables_counter = 0;
+
+void
+e_m365_tz_utils_ref_windows_zones (void)
+{
+       const gchar *xpath_eval_exp;
+       gchar *filename = NULL;
+       xmlDocPtr doc;
+       xmlXPathContextPtr xpath_ctxt;
+       xmlXPathObjectPtr xpath_obj;
+       xmlNodeSetPtr nodes;
+       gint i, len;
+
+       g_rec_mutex_lock (&tz_mutex);
+       if (ical_to_msdn != NULL && msdn_to_ical != NULL) {
+               g_hash_table_ref (ical_to_msdn);
+               g_hash_table_ref (msdn_to_ical);
+               tables_counter++;
+
+               g_rec_mutex_unlock (&tz_mutex);
+               return;
+       }
+
+       filename = g_build_filename (M365_DATADIR, "windowsZones.xml", NULL);
+
+       doc = xmlReadFile (filename, NULL, 0);
+
+       if (doc == NULL) {
+               g_warning (G_STRLOC "Could not map %s file.", filename);
+               g_free (filename);
+
+               g_rec_mutex_unlock (&tz_mutex);
+               return;
+       }
+
+       xpath_eval_exp = "/supplementalData/windowsZones/mapTimezones/mapZone";
+
+       xpath_ctxt = xmlXPathNewContext (doc);
+       xpath_obj = xmlXPathEvalExpression (BAD_CAST xpath_eval_exp, xpath_ctxt);
+
+       if (xpath_obj == NULL) {
+               g_warning (G_STRLOC "Unable to evaluate xpath expression \"%s\".", xpath_eval_exp);
+               xmlXPathFreeContext (xpath_ctxt);
+               xmlFreeDoc (doc);
+               g_free (filename);
+
+               g_rec_mutex_unlock (&tz_mutex);
+               return;
+       }
+
+       nodes = xpath_obj->nodesetval;
+       len = nodes->nodeNr;
+
+       msdn_to_ical = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+       ical_to_msdn = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+       tables_counter++;
+
+       for (i = 0; i < len; i++) {
+               xmlChar *msdn = xmlGetProp (nodes->nodeTab[i], BAD_CAST "other");
+               xmlChar *ical = xmlGetProp (nodes->nodeTab[i], BAD_CAST "type");
+               gchar **tokens;
+               gint tokens_len;
+
+               tokens = g_strsplit ((gchar *) ical, " ", 0);
+               tokens_len = g_strv_length (tokens);
+               if (tokens_len == 1) {
+                       if (!g_hash_table_lookup (msdn_to_ical, msdn))
+                               g_hash_table_insert (msdn_to_ical, g_strdup ((gchar *) msdn), g_strdup 
((gchar *) ical));
+
+                       if (!g_hash_table_lookup (ical_to_msdn, ical))
+                               g_hash_table_insert (ical_to_msdn, g_strdup ((gchar *) ical), g_strdup 
((gchar *) msdn));
+               } else {
+                       gint j;
+                       for (j = 0; j < tokens_len; j++) {
+                               if (!g_hash_table_lookup (msdn_to_ical, msdn))
+                                       g_hash_table_insert (msdn_to_ical, g_strdup ((gchar *) msdn), 
g_strdup (tokens[j]));
+
+                               if (!g_hash_table_lookup (ical_to_msdn, tokens[j]))
+                                       g_hash_table_insert (ical_to_msdn, g_strdup (tokens[j]), g_strdup 
((gchar *) msdn));
+                       }
+               }
+
+               g_strfreev (tokens);
+               xmlFree (ical);
+               xmlFree (msdn);
+       }
+
+       xmlXPathFreeObject (xpath_obj);
+       xmlXPathFreeContext (xpath_ctxt);
+       xmlFreeDoc (doc);
+       g_free (filename);
+
+       g_rec_mutex_unlock (&tz_mutex);
+}
+
+void
+e_m365_tz_utils_unref_windows_zones (void)
+{
+       g_rec_mutex_lock (&tz_mutex);
+       if (ical_to_msdn != NULL)
+               g_hash_table_unref (ical_to_msdn);
+
+       if (msdn_to_ical != NULL)
+               g_hash_table_unref (msdn_to_ical);
+
+       if (tables_counter > 0) {
+               tables_counter--;
+
+               if (tables_counter == 0) {
+                       ical_to_msdn = NULL;
+                       msdn_to_ical = NULL;
+               }
+       }
+
+       g_rec_mutex_unlock (&tz_mutex);
+}
+
+const gchar *
+e_m365_tz_utils_get_msdn_equivalent (const gchar *ical_tz_location)
+{
+       const gchar *msdn_tz_location = NULL;
+
+       if (!ical_tz_location || !*ical_tz_location)
+               return NULL;
+
+       g_rec_mutex_lock (&tz_mutex);
+       if (ical_to_msdn == NULL) {
+               g_rec_mutex_unlock (&tz_mutex);
+
+               g_warn_if_reached ();
+               return NULL;
+       }
+
+       msdn_tz_location = g_hash_table_lookup (ical_to_msdn, ical_tz_location);
+       g_rec_mutex_unlock (&tz_mutex);
+
+       return msdn_tz_location;
+}
+
+const gchar *
+e_m365_tz_utils_get_ical_equivalent (const gchar *msdn_tz_location)
+{
+       const gchar *ical_tz_location = NULL;
+
+       if (!msdn_tz_location || !*msdn_tz_location)
+               return NULL;
+
+       g_rec_mutex_lock (&tz_mutex);
+       if (msdn_to_ical == NULL) {
+               g_rec_mutex_unlock (&tz_mutex);
+
+               g_warn_if_reached ();
+               return NULL;
+       }
+
+       ical_tz_location = g_hash_table_lookup (msdn_to_ical, msdn_tz_location);
+       g_rec_mutex_unlock (&tz_mutex);
+
+       return ical_tz_location;
+}
diff --git a/src/Microsoft365/common/e-m365-tz-utils.h b/src/Microsoft365/common/e-m365-tz-utils.h
new file mode 100644
index 00000000..0aa6002c
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-tz-utils.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_M365_TZ_UTILS_H
+#define E_M365_TZ_UTILS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void           e_m365_tz_utils_ref_windows_zones       (void);
+void           e_m365_tz_utils_unref_windows_zones     (void);
+const gchar *  e_m365_tz_utils_get_msdn_equivalent     (const gchar *ical_tz_location);
+const gchar *  e_m365_tz_utils_get_ical_equivalent     (const gchar *msdn_tz_location);
+
+G_END_DECLS
+
+#endif /* E_M365_TZ_UTILS_H */


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