[evolution-ews/wip/mcrha/office365: 44/50] Calendar read/write improvements
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-ews/wip/mcrha/office365: 44/50] Calendar read/write improvements
- Date: Mon, 3 Aug 2020 15:24:08 +0000 (UTC)
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]