[evolution-data-server] Add functions to manipulate Google Task API



commit 14a6e82f0bd5a3b2eb5a4bf8ea3c1d604f270c9f
Author: Milan Crha <mcrha redhat com>
Date:   Thu Jul 7 18:03:26 2022 +0200

    Add functions to manipulate Google Task API
    
    Also add some JSON utility functions.

 .../evolution-data-server-docs.sgml.in             |    3 +
 po/POTFILES.in                                     |    1 +
 src/libedataserver/CMakeLists.txt                  |    6 +
 src/libedataserver/e-dataserver-autocleanups.h     |    2 +
 src/libedataserver/e-gdata-query.c                 |  610 +++++++
 src/libedataserver/e-gdata-query.h                 |   71 +
 src/libedataserver/e-gdata-session.c               | 1846 ++++++++++++++++++++
 src/libedataserver/e-gdata-session.h               |  229 +++
 src/libedataserver/e-json-utils.c                  |  775 ++++++++
 src/libedataserver/e-json-utils.h                  |   90 +
 src/libedataserver/libedataserver.h                |    3 +
 src/libedataserver/libedataserver.pc.in            |    2 +-
 12 files changed, 3637 insertions(+), 1 deletion(-)
---
diff --git a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in 
b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
index 6b1e6b3eb..1e4121b36 100644
--- a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
+++ b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
@@ -244,6 +244,9 @@
       <xi:include href="xml/e-flag.xml"/>
       <xi:include href="xml/e-free-form-exp.xml"/>
       <xi:include href="xml/e-gdata-oauth2-authorizer.xml"/>
+      <xi:include href="xml/e-gdata-query.xml"/>
+      <xi:include href="xml/e-gdata-session.xml"/>
+      <xi:include href="xml/e-json-utils.xml"/>
       <xi:include href="xml/e-memory.xml"/>
       <xi:include href="xml/e-named-parameters.xml"/>
       <xi:include href="xml/e-network-monitor.xml"/>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 9d96051ab..f57c6a347 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -193,6 +193,7 @@ src/libebackend/e-subprocess-factory.c
 src/libebackend/e-user-prompter-server.c
 src/libedataserver/e-categories.c
 src/libedataserver/e-client.c
+src/libedataserver/e-gdata-session.c
 src/libedataserver/e-oauth2-service.c
 src/libedataserver/e-oauth2-service-google.c
 src/libedataserver/e-oauth2-service-outlook.c
diff --git a/src/libedataserver/CMakeLists.txt b/src/libedataserver/CMakeLists.txt
index 89f251f09..88759ec0b 100644
--- a/src/libedataserver/CMakeLists.txt
+++ b/src/libedataserver/CMakeLists.txt
@@ -58,7 +58,10 @@ set(SOURCES
        e-flag.c
        e-free-form-exp.c
        e-gdata-oauth2-authorizer.c
+       e-gdata-query.c
+       e-gdata-session.c
        e-iterator.c
+       e-json-utils.c
        e-list.c
        e-list-iterator.c
        e-memory.c
@@ -149,7 +152,10 @@ set(HEADERS
        e-flag.h
        e-free-form-exp.h
        e-gdata-oauth2-authorizer.h
+       e-gdata-query.h
+       e-gdata-session.h
        e-iterator.h
+       e-json-utils.h
        e-list.h
        e-list-iterator.h
        e-memory.h
diff --git a/src/libedataserver/e-dataserver-autocleanups.h b/src/libedataserver/e-dataserver-autocleanups.h
index bc3c81ad4..4339656fe 100644
--- a/src/libedataserver/e-dataserver-autocleanups.h
+++ b/src/libedataserver/e-dataserver-autocleanups.h
@@ -33,6 +33,8 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(EExtensible, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(EExtension, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(EFlag, e_flag_free)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(EGDataOAuth2Authorizer, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EGDataQuery, e_gdata_query_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EGDataSession, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(EMemChunk, e_memchunk_destroy)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(EModule, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(ENetworkMonitor, g_object_unref)
diff --git a/src/libedataserver/e-gdata-query.c b/src/libedataserver/e-gdata-query.c
new file mode 100644
index 000000000..ea75338d2
--- /dev/null
+++ b/src/libedataserver/e-gdata-query.c
@@ -0,0 +1,610 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+/**
+ * SECTION: e-gdata-query
+ * @include: libedataserver/libedataserver.h
+ * @short_description: A GData (Google Data) query parameters
+ *
+ * The #EGDataQuery is a structure to limit listing of GData objects.
+ * Not every parameter can be used with every object list function.
+ **/
+
+#include "evolution-data-server-config.h"
+
+#include <glib.h>
+
+#include "e-json-utils.h"
+#include "e-gdata-query.h"
+
+G_DEFINE_BOXED_TYPE (EGDataQuery, e_gdata_query, e_gdata_query_ref, e_gdata_query_unref)
+
+#define PARAM_MAX_RESULTS "maxResults"
+#define PARAM_COMPLETED_MAX "completedMax"
+#define PARAM_COMPLETED_MIN "completedMin"
+#define PARAM_DUE_MAX "dueMax"
+#define PARAM_DUE_MIN "dueMin"
+#define PARAM_SHOW_COMPLETED "showCompleted"
+#define PARAM_SHOW_DELETED "showDeleted"
+#define PARAM_SHOW_HIDDEN "showHidden"
+#define PARAM_UPDATED_MIN "updatedMin"
+
+#define VALUE_TRUE "True"
+#define VALUE_FALSE "False"
+
+/**
+ * e_gdata_query_new:
+ *
+ * Creates a new #EGDataQuery. Free it with e_gdata_query_unref(),
+ * when no longer needed.
+ *
+ * Returns: (transfer full): a new #EGDataQuery
+ *
+ * Since: 3.46
+ **/
+EGDataQuery *
+e_gdata_query_new (void)
+{
+       /* Static gchar *, param name ~> gchar *, the value */
+       return (EGDataQuery *) g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+}
+
+/**
+ * e_gdata_query_ref:
+ * @self: an #GEDataQuery
+ *
+ * Increases the reference count of the @self.
+ * The added reference shuld be removed with e_gdata_query_unref().
+ *
+ * Returns: the @self
+ *
+ * Since: 3.46
+ **/
+EGDataQuery *
+e_gdata_query_ref (EGDataQuery *self)
+{
+       g_return_val_if_fail (self != NULL, NULL);
+
+       g_hash_table_ref ((GHashTable *) self);
+
+       return self;
+}
+
+/**
+ * e_gdata_query_unref:
+ * @self: an #EGDataQuery
+ *
+ * Decreases the reference count of the @self. When the reference count
+ * reaches 0, the @self is freed.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_unref (EGDataQuery *self)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_unref ((GHashTable *) self);
+}
+
+/**
+ * e_gdata_query_to_string:
+ * @self: an #EGDataQuery
+ *
+ * Converts the @self into a string, which can be used as a URI query. The returned
+ * string should be freed with g_free(), when no longer needed.
+ *
+ * Returns: (transfer full) (nullable): the @self converted into a string, or %NULL,
+ *    when the @self doesn't have set any parameter.
+ *
+ * Since: 3.46
+ **/
+gchar *
+e_gdata_query_to_string (EGDataQuery *self)
+{
+       GString *str = NULL;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_return_val_if_fail (self != NULL, NULL);
+
+       g_hash_table_iter_init (&iter, (GHashTable *) self);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               gchar *escaped_value;
+
+               if (!value)
+                       continue;
+
+               escaped_value = g_uri_escape_string (value, NULL, FALSE);
+               if (escaped_value) {
+                       if (!str)
+                               str = g_string_new (NULL);
+                       else
+                               g_string_append_c (str, '&');
+
+                       g_string_append_printf (str, "%s=%s", (const gchar *) key, escaped_value);
+               }
+
+               g_free (escaped_value);
+       }
+
+       if (str)
+               return g_string_free (str, FALSE);
+
+       return NULL;
+}
+
+static gboolean
+e_gdata_query_get_boolean_property (EGDataQuery *self,
+                                   const gchar *prop_name,
+                                   gboolean *out_exists)
+{
+       gpointer value = NULL;
+
+       g_return_val_if_fail (self != NULL, FALSE);
+
+       if (!g_hash_table_lookup_extended ((GHashTable *) self, prop_name, NULL, &value) || !value) {
+               if (out_exists)
+                       *out_exists = FALSE;
+               return FALSE;
+       }
+
+       if (out_exists)
+               *out_exists = TRUE;
+
+       if (g_strcmp0 (value, VALUE_TRUE) == 0)
+               return TRUE;
+
+       g_warn_if_fail (g_strcmp0 (value, VALUE_FALSE) == 0);
+
+       return FALSE;
+}
+
+static gint
+e_gdata_query_get_int_property (EGDataQuery *self,
+                               const gchar *prop_name,
+                               gboolean *out_exists)
+{
+       gpointer value = NULL;
+
+       g_return_val_if_fail (self != NULL, 0);
+
+       if (!g_hash_table_lookup_extended ((GHashTable *) self, prop_name, NULL, &value) || !value) {
+               if (out_exists)
+                       *out_exists = FALSE;
+               return 0;
+       }
+
+       if (out_exists)
+               *out_exists = TRUE;
+
+       return (gint) g_ascii_strtoll (value, NULL, 10);
+}
+
+static gint64
+e_gdata_query_get_datetime_property (EGDataQuery *self,
+                                    const gchar *prop_name,
+                                    gboolean *out_exists)
+{
+       gpointer value = NULL;
+
+       if (!g_hash_table_lookup_extended ((GHashTable *) self, prop_name, NULL, &value) || !value) {
+               if (out_exists)
+                       *out_exists = FALSE;
+               return -1;
+       }
+
+       if (out_exists)
+               *out_exists = TRUE;
+
+       return e_json_util_decode_iso8601 (value, -1);
+}
+
+/**
+ * e_gdata_query_set_max_results:
+ * @self: an #EGDataQuery
+ * @value: a value to set
+ *
+ * Sets max results to be returned in one call.
+ *
+ * This can be used for any object query.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_max_results (EGDataQuery *self,
+                              gint value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_MAX_RESULTS,
+               g_strdup_printf ("%d", value));
+}
+
+/**
+ * e_gdata_query_get_max_results:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the max results property.
+ * When not set, returns 0. The optional @out_exists
+ * can be used to see whether the property is set.
+ *
+ * Returns: the set value, or 0
+ *
+ * Since: 3.46
+ **/
+gint
+e_gdata_query_get_max_results (EGDataQuery *self,
+                              gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, 0);
+
+       return e_gdata_query_get_int_property (self, PARAM_MAX_RESULTS, out_exists);
+}
+
+/**
+ * e_gdata_query_set_completed_max:
+ * @self: an #EGDataQuery
+ * @value: a value to set, as a Unix date/time
+ *
+ * Sets upper bound for a task's completion date, as a Unix date/time, to filter by.
+ * The default is not to filter by completion date.
+ *
+ * This can be used for Task object query only.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_completed_max (EGDataQuery *self,
+                                gint64 value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_COMPLETED_MAX,
+               e_json_util_encode_iso8601 (value));
+}
+
+/**
+ * e_gdata_query_get_completed_max:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the completed max property, as a Unix
+ * date/time. When not set, returns -1. The optional @out_exists
+ * can be used to see whether the property is set.
+ *
+ * Returns: the set value, or -1
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_query_get_completed_max (EGDataQuery *self,
+                                gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, -1);
+
+       return e_gdata_query_get_datetime_property (self, PARAM_COMPLETED_MAX, out_exists);
+}
+
+/**
+ * e_gdata_query_set_completed_min:
+ * @self: an #EGDataQuery
+ * @value: a value to set, as a Unix date/time
+ *
+ * Sets lower bound for a task's completion date, as a Unix date/time, to filter by.
+ * The default is not to filter by completion date.
+ *
+ * This can be used for Task object query only.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_completed_min (EGDataQuery *self,
+                                gint64 value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_COMPLETED_MIN,
+               e_json_util_encode_iso8601 (value));
+}
+
+/**
+ * e_gdata_query_get_completed_min:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the completed min property, as a Unix
+ * date/time. When not set, returns -1. The optional @out_exists
+ * can be used to see whether the property is set.
+ *
+ * Returns: the set value, or -1
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_query_get_completed_min (EGDataQuery *self,
+                                gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, -1);
+
+       return e_gdata_query_get_datetime_property (self, PARAM_COMPLETED_MIN, out_exists);
+}
+
+/**
+ * e_gdata_query_set_due_max:
+ * @self: an #EGDataQuery
+ * @value: a value to set, as a Unix date/time
+ *
+ * Sets upper bound for a task's due date, as a Unix date/time, to filter by.
+ * The default is not to filter by due date.
+ *
+ * This can be used for Task object query only.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_due_max (EGDataQuery *self,
+                          gint64 value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_DUE_MAX,
+               e_json_util_encode_iso8601 (value));
+}
+
+/**
+ * e_gdata_query_get_due_max:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the due max property, as a Unix
+ * date/time. When not set, returns -1. The optional @out_exists
+ * can be used to see whether the property is set.
+ *
+ * Returns: the set value, or -1
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_query_get_due_max (EGDataQuery *self,
+                          gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, -1);
+
+       return e_gdata_query_get_datetime_property (self, PARAM_DUE_MAX, out_exists);
+}
+
+/**
+ * e_gdata_query_set_due_min:
+ * @self: an #EGDataQuery
+ * @value: a value to set, as a Unix date/time
+ *
+ * Sets lower bound for a task's due date, as a Unix date/time, to filter by.
+ * The default is not to filter by due date.
+ *
+ * This can be used for Task object query only.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_due_min (EGDataQuery *self,
+                          gint64 value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_DUE_MIN,
+               e_json_util_encode_iso8601 (value));
+}
+
+/**
+ * e_gdata_query_get_due_min:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the due min property, as a Unix
+ * date/time. When not set, returns -1. The optional @out_exists
+ * can be used to see whether the property is set.
+ *
+ * Returns: the set value, or -1
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_query_get_due_min (EGDataQuery *self,
+                          gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, -1);
+
+       return e_gdata_query_get_datetime_property (self, PARAM_DUE_MIN, out_exists);
+}
+
+/**
+ * e_gdata_query_set_show_completed:
+ * @self: an #EGDataQuery
+ * @value: a value to set
+ *
+ * Sets a flag indicating whether completed tasks are returned in the result.
+ * The default is True. Note that show hidden should also be True to show
+ * tasks completed in first party clients, such as the web UI and Google's
+ * mobile apps.
+ *
+ * This can be used for Task object query only.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_show_completed (EGDataQuery *self,
+                                 gboolean value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_SHOW_COMPLETED,
+               g_strdup (value ? VALUE_TRUE : VALUE_FALSE));
+}
+
+/**
+ * e_gdata_query_get_show_completed:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the show completed property. When not set,
+ * returns %FALSE. The optional @out_exists can be used to see whether
+ * the property is set.
+ *
+ * Returns: the set value, or %FALSE
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_query_get_show_completed (EGDataQuery *self,
+                                 gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, FALSE);
+
+       return e_gdata_query_get_boolean_property (self, PARAM_SHOW_COMPLETED, out_exists);
+}
+
+/**
+ * e_gdata_query_set_show_deleted:
+ * @self: an #EGDataQuery
+ * @value: a value to set
+ *
+ * Sets a flag indicating whether deleted tasks are returned in the result.
+ * The default is False.
+ *
+ * This can be used for Task object query only.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_show_deleted (EGDataQuery *self,
+                               gboolean value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_SHOW_DELETED,
+               g_strdup (value ? VALUE_TRUE : VALUE_FALSE));
+}
+
+/**
+ * e_gdata_query_get_show_deleted:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the show deleted property. When not set,
+ * returns %FALSE. The optional @out_exists can be used to see whether
+ * the property is set.
+ *
+ * Returns: the set value, or %FALSE
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_query_get_show_deleted (EGDataQuery *self,
+                               gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, FALSE);
+
+       return e_gdata_query_get_boolean_property (self, PARAM_SHOW_DELETED, out_exists);
+}
+
+/**
+ * e_gdata_query_set_show_hidden:
+ * @self: an #EGDataQuery
+ * @value: a value to set
+ *
+ * Sets a flag indicating whether hidden tasks are returned in the result.
+ * The default is False.
+ *
+ * This can be used for Task object query only.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_show_hidden (EGDataQuery *self,
+                              gboolean value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_SHOW_HIDDEN,
+               g_strdup (value ? VALUE_TRUE : VALUE_FALSE));
+}
+
+/**
+ * e_gdata_query_get_show_hidden:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the show hidden property. When not set,
+ * returns %FALSE. The optional @out_exists can be used to see whether
+ * the property is set.
+ *
+ * Returns: the set value, or %FALSE
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_query_get_show_hidden (EGDataQuery *self,
+                              gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, FALSE);
+
+       return e_gdata_query_get_boolean_property (self, PARAM_SHOW_HIDDEN, out_exists);
+}
+
+/**
+ * e_gdata_query_set_updated_min:
+ * @self: an #EGDataQuery
+ * @value: a value to set, as a Unix date/time
+ *
+ * Sets lower bound for a task's last modification time, as a Unix date/time,
+ * to filter by. The default is not to filter by the last modification time.
+ *
+ * This can be used for Task object query only.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_query_set_updated_min (EGDataQuery *self,
+                              gint64 value)
+{
+       g_return_if_fail (self != NULL);
+
+       g_hash_table_insert ((GHashTable *) self, (gpointer) PARAM_UPDATED_MIN,
+               e_json_util_encode_iso8601 (value));
+}
+
+/**
+ * e_gdata_query_get_updated_min:
+ * @self: an #EDataQuery
+ * @out_exists: (out) (optional): an out argument, where can
+ *    be set whether the property exists, or %NULL
+ *
+ * Gets current value of the updated min property, as a Unix
+ * date/time. When not set, returns -1. The optional @out_exists
+ * can be used to see whether the property is set.
+ *
+ * Returns: the set value, or -1
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_query_get_updated_min (EGDataQuery *self,
+                              gboolean *out_exists)
+{
+       g_return_val_if_fail (self != NULL, -1);
+
+       return e_gdata_query_get_datetime_property (self, PARAM_UPDATED_MIN, out_exists);
+}
diff --git a/src/libedataserver/e-gdata-query.h b/src/libedataserver/e-gdata-query.h
new file mode 100644
index 000000000..b1fcbd73d
--- /dev/null
+++ b/src/libedataserver/e-gdata-query.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION)
+#error "Only <libedataserver/libedataserver.h> should be included directly."
+#endif
+
+#ifndef E_GDATA_QUERY_H
+#define E_GDATA_QUERY_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * EGDataQuery:
+ *
+ * Since: 3.46
+ **/
+typedef struct _EGDataQuery EGDataQuery;
+
+#define E_TYPE_GDATA_QUERY (e_gdata_query_get_type ())
+
+GType          e_gdata_query_get_type          (void) G_GNUC_CONST;
+EGDataQuery *  e_gdata_query_new               (void);
+EGDataQuery *  e_gdata_query_ref               (EGDataQuery *self);
+void           e_gdata_query_unref             (EGDataQuery *self);
+gchar *                e_gdata_query_to_string         (EGDataQuery *self);
+void           e_gdata_query_set_max_results   (EGDataQuery *self,
+                                                gint value);
+gint           e_gdata_query_get_max_results   (EGDataQuery *self,
+                                                gboolean *out_exists);
+void           e_gdata_query_set_completed_max (EGDataQuery *self,
+                                                gint64 value);
+gint64         e_gdata_query_get_completed_max (EGDataQuery *self,
+                                                gboolean *out_exists);
+void           e_gdata_query_set_completed_min (EGDataQuery *self,
+                                                gint64 value);
+gint64         e_gdata_query_get_completed_min (EGDataQuery *self,
+                                                gboolean *out_exists);
+void           e_gdata_query_set_due_max       (EGDataQuery *self,
+                                                gint64 value);
+gint64         e_gdata_query_get_due_max       (EGDataQuery *self,
+                                                gboolean *out_exists);
+void           e_gdata_query_set_due_min       (EGDataQuery *self,
+                                                gint64 value);
+gint64         e_gdata_query_get_due_min       (EGDataQuery *self,
+                                                gboolean *out_exists);
+void           e_gdata_query_set_show_completed(EGDataQuery *self,
+                                                gboolean value);
+gboolean       e_gdata_query_get_show_completed(EGDataQuery *self,
+                                                gboolean *out_exists);
+void           e_gdata_query_set_show_deleted  (EGDataQuery *self,
+                                                gboolean value);
+gboolean       e_gdata_query_get_show_deleted  (EGDataQuery *self,
+                                                gboolean *out_exists);
+void           e_gdata_query_set_show_hidden   (EGDataQuery *self,
+                                                gboolean value);
+gboolean       e_gdata_query_get_show_hidden   (EGDataQuery *self,
+                                                gboolean *out_exists);
+void           e_gdata_query_set_updated_min   (EGDataQuery *self,
+                                                gint64 value);
+gint64         e_gdata_query_get_updated_min   (EGDataQuery *self,
+                                                gboolean *out_exists);
+
+G_END_DECLS
+
+#endif /* E_GDATA_SESSION_H */
diff --git a/src/libedataserver/e-gdata-session.c b/src/libedataserver/e-gdata-session.c
new file mode 100644
index 000000000..8aae76df5
--- /dev/null
+++ b/src/libedataserver/e-gdata-session.c
@@ -0,0 +1,1846 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+/**
+ * SECTION: e-gdata-session
+ * @include: libedataserver/libedataserver.h
+ * @short_description: A GData (Google Data) session
+ *
+ * The #EGDataSession is a class to work with Google's GData API.
+ **/
+
+#include "evolution-data-server-config.h"
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel/camel.h"
+
+#include "e-flag.h"
+#include "e-json-utils.h"
+#include "e-gdata-query.h"
+#include "e-soup-session.h"
+
+#include "e-gdata-session.h"
+
+#define LOCK(x) g_rec_mutex_lock (&(x->priv->property_lock))
+#define UNLOCK(x) g_rec_mutex_unlock (&(x->priv->property_lock))
+
+#define E_GDATA_RETRY_IO_ERROR_SECONDS 3
+
+#define TASKS_API_URL          "https://tasks.googleapis.com";
+#define TASKLISTS_TOP_URL      TASKS_API_URL "/tasks/v1/users/@me/lists"
+#define TASKS_TOP_URL          TASKS_API_URL "/tasks/v1/lists"
+
+struct _EGDataSessionPrivate {
+       GRecMutex property_lock;
+       gint64 backoff_for_usec;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (EGDataSession, e_gdata_session, E_TYPE_SOUP_SESSION)
+
+static void
+e_gdata_session_finalize (GObject *object)
+{
+       EGDataSession *self = E_GDATA_SESSION (object);
+
+       g_rec_mutex_clear (&self->priv->property_lock);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_gdata_session_parent_class)->finalize (object);
+}
+
+static void
+e_gdata_session_class_init (EGDataSessionClass *klass)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = e_gdata_session_finalize;
+}
+
+static void
+e_gdata_session_init (EGDataSession *self)
+{
+       self->priv = e_gdata_session_get_instance_private (self);
+
+       g_rec_mutex_init (&self->priv->property_lock);
+
+       e_soup_session_setup_logging (E_SOUP_SESSION (self), g_getenv ("GDATA_DEBUG"));
+}
+
+/**
+ * e_gdata_session_new:
+ * @source: an #ESource
+ *
+ * Creates a new #EGDataSession associated with the given @source.
+ *
+ * Returns: (transfer full): a new #EGDataSession; free it with g_object_unref(),
+ *    when no longer needed.
+ *
+ * Since: 3.46
+ **/
+EGDataSession *
+e_gdata_session_new (ESource *source)
+{
+       g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+       return g_object_new (E_TYPE_GDATA_SESSION,
+               "source", source,
+               NULL);
+}
+
+static void
+e_gdata_session_request_cancelled_cb (GCancellable *cancellable,
+                                     gpointer user_data)
+{
+       EFlag *flag = user_data;
+
+       g_return_if_fail (flag != NULL);
+
+       e_flag_set (flag);
+}
+
+/* (transfer full) (nullable): Free the *out_node with json_node_unref(), if not NULL;
+   It can return 'success', even when the *out_node is NULL. */
+static gboolean
+e_gdata_session_json_node_from_message (SoupMessage *message,
+                                       GByteArray *bytes,
+                                       JsonNode **out_node,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       const gchar *content_type;
+       gboolean success = TRUE;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
+       g_return_val_if_fail (out_node != NULL, FALSE);
+
+       *out_node = NULL;
+
+       content_type = soup_message_get_response_headers (message) ?
+               soup_message_headers_get_content_type (soup_message_get_response_headers (message), NULL) : 
NULL;
+
+       if (content_type && g_ascii_strcasecmp (content_type, "application/json") == 0) {
+               JsonParser *json_parser;
+
+               json_parser = json_parser_new_immutable ();
+
+               if (bytes) {
+                       success = json_parser_load_from_data (json_parser, (const gchar *) bytes->data, 
bytes->len, error);
+               } else {
+                       /* This should not happen, it's for safety check only, thus the string is not 
localized */
+                       success = FALSE;
+                       g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "No JSON data 
found");
+               }
+
+               if (success)
+                       *out_node = json_parser_steal_root (json_parser);
+
+               g_object_unref (json_parser);
+       }
+
+       if (!success && *out_node)
+               g_clear_pointer (out_node, json_node_unref);
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
+}
+
+static gboolean
+e_gdata_session_send_request_sync (EGDataSession *self,
+                                  SoupMessage *message,
+                                  JsonNode **out_node,
+                                  GByteArray **out_bytes,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       gint need_retry_seconds = 5;
+       gboolean did_io_error_retry = FALSE;
+       gboolean success = FALSE, need_retry = TRUE;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
+       g_return_val_if_fail (out_node == NULL || out_bytes == NULL, FALSE);
+
+       while (need_retry && !g_cancellable_is_cancelled (cancellable)) {
+               gboolean is_io_error = FALSE;
+               GByteArray *bytes;
+               GError *local_error = NULL;
+
+               need_retry = FALSE;
+
+               LOCK (self);
+
+               if (self->priv->backoff_for_usec) {
+                       EFlag *flag;
+                       gint64 wait_ms;
+                       gulong handler_id = 0;
+
+                       wait_ms = self->priv->backoff_for_usec / G_TIME_SPAN_MILLISECOND;
+
+                       UNLOCK (self);
+
+                       flag = e_flag_new ();
+
+                       if (cancellable) {
+                               handler_id = g_cancellable_connect (cancellable, G_CALLBACK 
(e_gdata_session_request_cancelled_cb),
+                                       flag, NULL);
+                       }
+
+                       while (wait_ms > 0 && !g_cancellable_is_cancelled (cancellable)) {
+                               gint64 now = g_get_monotonic_time ();
+                               gint left_minutes, left_seconds;
+
+                               left_minutes = wait_ms / 60000;
+                               left_seconds = (wait_ms / 1000) % 60;
+
+                               if (left_minutes > 0) {
+                                       camel_operation_push_message (cancellable,
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "Google server is busy, waiting to retry (%d:%02d 
minute)",
+                                                       "Google server is busy, waiting to retry (%d:%02d 
minutes)", left_minutes),
+                                               left_minutes, left_seconds);
+                               } else {
+                                       camel_operation_push_message (cancellable,
+                                               g_dngettext (GETTEXT_PACKAGE,
+                                                       "Google server is busy, waiting to retry (%d second)",
+                                                       "Google server is busy, waiting to retry (%d 
seconds)", left_seconds),
+                                               left_seconds);
+                               }
+
+                               e_flag_wait_until (flag, now + (G_TIME_SPAN_MILLISECOND * (wait_ms > 1000 ? 
1000 : wait_ms)));
+                               e_flag_clear (flag);
+
+                               now = g_get_monotonic_time () - now;
+                               now = now / G_TIME_SPAN_MILLISECOND;
+
+                               if (now >= wait_ms)
+                                       wait_ms = 0;
+                               wait_ms -= now;
+
+                               camel_operation_pop_message (cancellable);
+                       }
+
+                       if (handler_id)
+                               g_cancellable_disconnect (cancellable, handler_id);
+
+                       e_flag_free (flag);
+
+                       LOCK (self);
+
+                       self->priv->backoff_for_usec = 0;
+               }
+
+               if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+                       UNLOCK (self);
+                       return FALSE;
+               }
+
+               bytes = e_soup_session_send_message_simple_sync (E_SOUP_SESSION (self), message, cancellable, 
&local_error);
+
+               success = bytes != NULL;
+               is_io_error = !did_io_error_retry && g_error_matches (local_error, G_IO_ERROR, 
G_IO_ERROR_PARTIAL_INPUT);
+
+               if (local_error) {
+                       g_propagate_error (error, local_error);
+                       local_error = NULL;
+               }
+
+               if (is_io_error ||
+                   /* Throttling */
+                   soup_message_get_status (message) == 429 ||
+                   /* https://docs.microsoft.com/en-us/graph/best-practices-concept#handling-expected-errors 
*/
+                   soup_message_get_status (message) == SOUP_STATUS_SERVICE_UNAVAILABLE) {
+                       did_io_error_retry = did_io_error_retry || is_io_error;
+                       need_retry = TRUE;
+               }
+
+               if (need_retry) {
+                       const gchar *retry_after_str;
+                       gint64 retry_after;
+
+                       retry_after_str = soup_message_get_response_headers (message) ?
+                               soup_message_headers_get_one (soup_message_get_response_headers (message), 
"Retry-After") : NULL;
+
+                       if (retry_after_str && *retry_after_str)
+                               retry_after = g_ascii_strtoll (retry_after_str, NULL, 10);
+                       else if (is_io_error)
+                               retry_after = E_GDATA_RETRY_IO_ERROR_SECONDS;
+                       else
+                               retry_after = 0;
+
+                       if (retry_after > 0)
+                               need_retry_seconds = retry_after;
+                       else if (need_retry_seconds < 120)
+                               need_retry_seconds *= 2;
+
+                       if (self->priv->backoff_for_usec < need_retry_seconds * G_USEC_PER_SEC)
+                               self->priv->backoff_for_usec = need_retry_seconds * G_USEC_PER_SEC;
+
+                       if (soup_message_get_status (message) == SOUP_STATUS_SERVICE_UNAVAILABLE)
+                               soup_session_abort (SOUP_SESSION (self));
+
+                       success = FALSE;
+               } else if (success && out_bytes && SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status 
(message))) {
+                       *out_bytes = bytes;
+                       bytes = NULL;
+               } else if (success && out_node) {
+                       JsonNode *node = NULL;
+
+                       success = e_gdata_session_json_node_from_message (message, bytes, &node, cancellable, 
error);
+
+                       if (success) {
+                               *out_node = node;
+                               node = NULL;
+                       } else if (error && !*error && !SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status 
(message))) {
+                               g_set_error_literal (error, E_SOUP_SESSION_ERROR, soup_message_get_status 
(message),
+                                       soup_message_get_reason_phrase (message) ? 
soup_message_get_reason_phrase (message) :
+                                       soup_status_get_phrase (soup_message_get_status (message)));
+                       }
+
+                       g_clear_pointer (&node, json_node_unref);
+               }
+
+               g_clear_pointer (&bytes, g_byte_array_unref);
+
+               UNLOCK (self);
+
+               if (need_retry) {
+                       success = FALSE;
+                       g_clear_error (error);
+               }
+       }
+
+       return success;
+}
+
+static void
+e_gdata_session_set_json_body (SoupMessage *message,
+                              JsonBuilder *builder)
+{
+       JsonGenerator *generator;
+       JsonNode *node;
+       gchar *data;
+       gsize data_length = 0;
+
+       g_return_if_fail (SOUP_IS_MESSAGE (message));
+       g_return_if_fail (builder != NULL);
+
+       node = json_builder_get_root (builder);
+
+       generator = json_generator_new ();
+       json_generator_set_root (generator, node);
+
+       data = json_generator_to_data (generator, &data_length);
+
+       soup_message_headers_set_content_type (soup_message_get_request_headers (message), 
"application/json", NULL);
+
+       if (data)
+               e_soup_session_util_set_message_request_body_from_data (message, FALSE, "application/json", 
data, data_length, g_free);
+
+       g_object_unref (generator);
+       json_node_unref (node);
+}
+
+static SoupMessage *
+e_gdata_session_new_message (EGDataSession *self,
+                            const gchar *method,
+                            const gchar *base_url,
+                            const gchar *append_path,
+                            gboolean base_url_has_params,
+                            EGDataQuery *query,
+                            JsonBuilder *body,
+                            GError **error,
+                            ...) G_GNUC_NULL_TERMINATED;
+
+/* base URI followed by param name, GType of the value and the value, terminated by NULL */
+static SoupMessage *
+e_gdata_session_new_message (EGDataSession *self,
+                            const gchar *method,
+                            const gchar *base_url,
+                            const gchar *append_path,
+                            gboolean base_url_has_params,
+                            EGDataQuery *query,
+                            JsonBuilder *body,
+                            GError **error,
+                            ...)
+{
+       GString *uri = NULL;
+       GUri *guri;
+       SoupMessage *message = NULL;
+       va_list ap;
+       const gchar *name;
+       gchar separator;
+
+       g_return_val_if_fail (base_url != NULL, NULL);
+
+       va_start (ap, error);
+       name = va_arg (ap, gchar *);
+       separator = base_url_has_params ? '&' : '?';
+       while (name) {
+               gint type = va_arg (ap, gint);
+
+               if (!uri) {
+                       uri = g_string_new (base_url);
+
+                       if (append_path) {
+                               g_string_append_c (uri, '/');
+                               g_string_append (uri, append_path);
+                       }
+               }
+
+               switch (type) {
+                       case G_TYPE_INT:
+                       case G_TYPE_BOOLEAN: {
+                               gint val = va_arg (ap, gint);
+                               g_string_append_printf (uri, "%c%s=%d", separator, name, val);
+                               break;
+                       }
+                       case G_TYPE_FLOAT:
+                       case G_TYPE_DOUBLE: {
+                               gdouble val = va_arg (ap, double);
+                               g_string_append_printf (uri, "%c%s=%f", separator, name, val);
+                               break;
+                       }
+                       case G_TYPE_STRING: {
+                               gchar *val = va_arg (ap, gchar *);
+                               gchar *escaped = g_uri_escape_string (val, NULL, FALSE);
+                               g_string_append_printf (uri, "%c%s=%s", separator, name, escaped);
+                               g_free (escaped);
+                               break;
+                       }
+                       case G_TYPE_POINTER: {
+                               gpointer val = va_arg (ap, gpointer);
+                               g_string_append_printf (uri, "%c%s=%p", separator, name, val);
+                               break;
+                       }
+                       default:
+                               g_warning ("%s: Unexpected param type %s", G_STRFUNC, g_type_name (type));
+                               g_string_free (uri, TRUE);
+                               va_end (ap);
+                               return NULL;
+               }
+
+               if (separator == '?')
+                       separator = '&';
+
+               name = va_arg (ap, gchar *);
+       }
+       va_end (ap);
+
+       if (!uri && append_path) {
+               uri = g_string_new (base_url);
+               g_string_append_c (uri, '/');
+               g_string_append (uri, append_path);
+       }
+
+       if (query) {
+               gchar *str;
+
+               str = e_gdata_query_to_string (query);
+
+               if (str && *str) {
+                       if (!uri)
+                               uri = g_string_new (base_url);
+                       g_string_append_c (uri, separator);
+                       g_string_append (uri, str);
+               }
+
+               g_free (str);
+       }
+
+       guri = g_uri_parse (uri ? uri->str : base_url, G_URI_FLAGS_PARSE_RELAXED | G_URI_FLAGS_ENCODED, 
error);
+       if (guri) {
+               message = e_soup_session_new_message_from_uri (E_SOUP_SESSION (self), method, guri, error);
+
+               if (message && body)
+                       e_gdata_session_set_json_body (message, body);
+
+               g_uri_unref (guri);
+       }
+
+       if (uri)
+               g_string_free (uri, TRUE);
+
+       return message;
+}
+
+static gboolean
+e_gdata_session_handle_resource_items (EGDataSession *self,
+                                      const gchar *method,
+                                      const gchar *base_url,
+                                      gboolean base_url_has_params,
+                                      EGDataQuery *query,
+                                      EGDataObjectCallback cb,
+                                      gpointer user_data,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       gchar *next_page_token = NULL;
+       gboolean success = TRUE, done = FALSE;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (cb != NULL, FALSE);
+
+       while (success && !done) {
+               SoupMessage *message;
+               JsonNode *node = NULL;
+
+               done = TRUE;
+
+               message = e_gdata_session_new_message (self, method, base_url, NULL, base_url_has_params, 
query, NULL, error,
+                       next_page_token ? "pageToken" : NULL, G_TYPE_STRING, next_page_token,
+                       NULL);
+
+               if (!message) {
+                       success = FALSE;
+                       break;
+               }
+
+               success = e_gdata_session_send_request_sync (self, message, &node, NULL, cancellable, error);
+
+               if (success && node) {
+                       JsonObject *object;
+
+                       object = json_node_get_object (node);
+                       if (object) {
+                               const gchar *str = e_json_get_string_member (object, "nextPageToken", NULL);
+                               JsonArray *array;
+
+                               if (str) {
+                                       g_free (next_page_token);
+                                       next_page_token = g_strdup (str);
+                                       done = FALSE;
+                               }
+
+                               array = e_json_get_array_member (object, "items");
+
+                               if (array) {
+                                       guint ii, len;
+
+                                       len = json_array_get_length (array);
+
+                                       for (ii = 0; ii < len && !g_cancellable_is_cancelled (cancellable); 
ii++) {
+                                               JsonNode *elem = json_array_get_element (array, ii);
+
+                                               if (JSON_NODE_HOLDS_OBJECT (elem)) {
+                                                       JsonObject *elem_object = json_node_get_object (elem);
+
+                                                       if (elem_object && !cb (self, elem_object, 
user_data)) {
+                                                               done = TRUE;
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                               } else {
+                                       done = TRUE;
+                               }
+
+                               success = !g_cancellable_set_error_if_cancelled (cancellable, error);
+                       } else {
+                               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, _("No JSON 
object returned by the server"));
+                               success = FALSE;
+                       }
+               }
+
+               g_clear_pointer (&node, json_node_unref);
+               g_clear_object (&message);
+       }
+
+       g_free (next_page_token);
+
+       return success;
+}
+
+static gboolean
+e_gdata_object_is_kind (JsonObject *object,
+                       const gchar *kind)
+{
+       return object != NULL && kind != NULL &&
+               g_strcmp0 (e_json_get_string_member (object, "kind", NULL), kind) == 0;
+}
+
+/**
+ * e_gdata_tasklist_get_id:
+ * @tasklist: a GData TaskList
+ *
+ * Returns TaskList::id property.
+ *
+ * Returns: (transfer none) (nullable): TaskList::id property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_tasklist_get_id (JsonObject *tasklist)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (tasklist, "tasks#taskList"), NULL);
+
+       return e_json_get_string_member (tasklist, "id", NULL);
+}
+
+/**
+ * e_gdata_tasklist_add_id:
+ * @builder: a #JsonBuilder with a started object member
+ * @value: a TaskList::id property value
+ *
+ * Adds a TaskList::id property @value into the @builder, which
+ * should have started an object member.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_tasklist_add_id (JsonBuilder *builder,
+                        const gchar *value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (value != NULL);
+
+       e_json_add_string_member (builder, "id", value);
+}
+
+/**
+ * e_gdata_tasklist_get_etag:
+ * @tasklist: a GData TaskList
+ *
+ * Returns TaskList::etag property.
+ *
+ * Returns: (transfer none) (nullable): TaskList::etag property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_tasklist_get_etag (JsonObject *tasklist)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (tasklist, "tasks#taskList"), NULL);
+
+       return e_json_get_string_member (tasklist, "etag", NULL);
+}
+
+/**
+ * e_gdata_tasklist_get_title:
+ * @tasklist: a GData TaskList
+ *
+ * Returns TaskList::title property.
+ *
+ * Returns: (transfer none) (nullable): TaskList::title property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_tasklist_get_title (JsonObject *tasklist)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (tasklist, "tasks#taskList"), NULL);
+
+       return e_json_get_string_member (tasklist, "title", NULL);
+}
+
+/**
+ * e_gdata_tasklist_add_title:
+ * @builder: a #JsonBuilder with a started object member
+ * @value: a TaskList::title property value
+ *
+ * Adds a TaskList::title property @value into the @builder, which
+ * should have started an object member.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_tasklist_add_title (JsonBuilder *builder,
+                           const gchar *value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (value != NULL);
+
+       e_json_add_string_member (builder, "title", value);
+}
+
+/**
+ * e_gdata_tasklist_get_self_link:
+ * @tasklist: a GData TaskList
+ *
+ * Returns TaskList::selfLink property.
+ *
+ * Returns: (transfer none) (nullable): TaskList::selfLink property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_tasklist_get_self_link (JsonObject *tasklist)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (tasklist, "tasks#taskList"), NULL);
+
+       return e_json_get_string_member (tasklist, "selfLink", NULL);
+}
+
+/**
+ * e_gdata_tasklist_get_updated:
+ * @tasklist: a GData TaskList
+ *
+ * Returns TaskList::updated property, as Unix time.
+ *
+ * Returns: TaskList::updated property or 0, when not found
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_tasklist_get_updated (JsonObject *tasklist)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (tasklist, "tasks#taskList"), 0);
+
+       return e_json_get_iso8601_member (tasklist, "updated", 0);
+}
+
+/**
+ * e_gdata_session_tasklists_delete_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Deletes a task list @tasklist_id.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasklists_delete_sync (EGDataSession *self,
+                                      const gchar *tasklist_id,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+
+       message = e_gdata_session_new_message (self, SOUP_METHOD_DELETE, TASKLISTS_TOP_URL, tasklist_id, 
FALSE, NULL, NULL, error, NULL);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, NULL, NULL, cancellable, error);
+
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasklists::delete");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasklists_get_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @out_tasklist: (out) (transfer full): tasklist object
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a task list @tasklist_id and returns it as a #JsonObject,
+ * which should be freed with json_object_unref(), when no longer needed.
+ *
+ * There can be used e_gdata_tasklist_get_id(), e_gdata_tasklist_get_etag(),
+ * e_gdata_tasklist_get_title(), e_gdata_tasklist_get_self_link(),
+ * e_gdata_tasklist_get_updated() to read the properties of the task list.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasklists_get_sync (EGDataSession *self,
+                                   const gchar *tasklist_id,
+                                   JsonObject **out_tasklist,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       SoupMessage *message;
+       JsonNode *node = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+       g_return_val_if_fail (out_tasklist != NULL, FALSE);
+
+       *out_tasklist = NULL;
+
+       message = e_gdata_session_new_message (self, SOUP_METHOD_GET, TASKLISTS_TOP_URL, tasklist_id, FALSE, 
NULL, NULL, error, NULL);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, &node, NULL, cancellable, error);
+
+       if (success && node) {
+               JsonObject *object;
+
+               object = json_node_get_object (node);
+               if (object)
+                       *out_tasklist = json_object_ref (object);
+       }
+
+       g_clear_object (&message);
+       g_clear_pointer (&node, json_node_unref);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasklists::get");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasklists_insert_sync:
+ * @self: an #EGDataSession
+ * @title: title to set
+ * @out_inserted_tasklist: (out) (transfer full): the created task list
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new task list, titled @title. The @out_inserted_tasklist should
+ * be freed with json_object_unref(), when no longer needed.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasklists_insert_sync (EGDataSession *self,
+                                      const gchar *title,
+                                      JsonObject **out_inserted_tasklist,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       SoupMessage *message;
+       JsonBuilder *builder;
+       JsonNode *node = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (title != NULL, FALSE);
+       g_return_val_if_fail (out_inserted_tasklist != NULL, FALSE);
+
+       *out_inserted_tasklist = NULL;
+
+       builder = json_builder_new_immutable ();
+
+       e_json_begin_object_member (builder, NULL);
+       e_json_add_string_member (builder, "title", title);
+       e_json_end_object_member (builder);
+
+       message = e_gdata_session_new_message (self, SOUP_METHOD_POST, TASKLISTS_TOP_URL, NULL, FALSE, NULL, 
builder, error, NULL);
+
+       g_clear_object (&builder);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, &node, NULL, cancellable, error);
+
+       if (success && node) {
+               JsonObject *object;
+
+               object = json_node_get_object (node);
+               if (object)
+                       *out_inserted_tasklist = json_object_ref (object);
+       }
+
+       g_clear_pointer (&node, json_node_unref);
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasklists::insert");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasklists_list_sync:
+ * @self: an #EGDataSession
+ * @query: (nullable): an #EGDataQuery to limit returned task lists, or %NULL
+ * @cb: (scope call): an #EGDataObjectCallback to call for each found task list
+ * @user_data: (closure cb): user data passed to the @cb
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Lists all configured task lists for the user, calling the @cb for each of them.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasklists_list_sync (EGDataSession *self,
+                                    EGDataQuery *query,
+                                    EGDataObjectCallback cb,
+                                    gpointer user_data,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (cb != NULL, FALSE);
+
+       success = e_gdata_session_handle_resource_items (self, SOUP_METHOD_GET, TASKLISTS_TOP_URL, FALSE,
+               query, cb, user_data, cancellable, error);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasklists::list");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasklists_patch_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @tasklist_properties: task list properties to change
+ * @out_patched_tasklist: (out) (optional) (transfer full): where to store patched task list, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Changes properties of a task list @tasklist_id.
+ *
+ * If not %NULL, free the @out_patched_tasklist with json_object_unref(),
+ * when no longer needed.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasklists_patch_sync (EGDataSession *self,
+                                     const gchar *tasklist_id,
+                                     JsonBuilder *tasklist_properties,
+                                     JsonObject **out_patched_tasklist,
+                                     GCancellable *cancellable,
+                                     GError **error)
+{
+       SoupMessage *message;
+       JsonNode *node = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+       g_return_val_if_fail (tasklist_properties != NULL, FALSE);
+
+       message = e_gdata_session_new_message (self, "PATCH", TASKLISTS_TOP_URL, tasklist_id, FALSE, NULL, 
tasklist_properties, error, NULL);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, out_patched_tasklist ? &node : NULL, 
NULL, cancellable, error);
+
+       if (success && node && out_patched_tasklist) {
+               JsonObject *object;
+
+               object = json_node_get_object (node);
+               if (object)
+                       *out_patched_tasklist = json_object_ref (object);
+       }
+
+       g_clear_pointer (&node, json_node_unref);
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasklists::patch");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasklists_update_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @tasklist: task list object to update the task list with
+ * @out_updated_tasklist: (out) (optional) (transfer full): where to store updated task list, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Updates a task list @tasklist_id with values from the @tasklist.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasklists_update_sync (EGDataSession *self,
+                                      const gchar *tasklist_id,
+                                      JsonBuilder *tasklist,
+                                      JsonObject **out_updated_tasklist,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       SoupMessage *message;
+       JsonNode *node = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+       g_return_val_if_fail (tasklist != NULL, FALSE);
+
+       message = e_gdata_session_new_message (self, SOUP_METHOD_PUT, TASKLISTS_TOP_URL, tasklist_id, FALSE, 
NULL, tasklist, error, NULL);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, out_updated_tasklist ? &node : NULL, 
NULL, cancellable, error);
+
+       if (success && node && out_updated_tasklist) {
+               JsonObject *object;
+
+               object = json_node_get_object (node);
+               if (object)
+                       *out_updated_tasklist = json_object_ref (object);
+       }
+
+       g_clear_pointer (&node, json_node_unref);
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasklists::update");
+
+       return success;
+}
+
+/**
+ * e_gdata_task_get_id:
+ * @task: a GData Task
+ *
+ * Returns Task::id property.
+ *
+ * Returns: (transfer none) (nullable): Task::id property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_task_get_id (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), NULL);
+
+       return e_json_get_string_member (task, "id", NULL);
+}
+
+/**
+ * e_gdata_task_add_id:
+ * @builder: a #JsonBuilder with a started object member
+ * @value: a Task::id property value
+ *
+ * Adds a Task::id property @value into the @builder, which
+ * should have started an object member.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_task_add_id (JsonBuilder *builder,
+                    const gchar *value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (value != NULL);
+
+       e_json_add_string_member (builder, "id", value);
+}
+
+/**
+ * e_gdata_task_get_etag:
+ * @task: a GData Task
+ *
+ * Returns Task::etag property.
+ *
+ * Returns: (transfer none) (nullable): Task::etag property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_task_get_etag (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), NULL);
+
+       return e_json_get_string_member (task, "etag", NULL);
+}
+
+/**
+ * e_gdata_task_get_title:
+ * @task: a GData Task
+ *
+ * Returns Task::title property.
+ *
+ * Returns: (transfer none) (nullable): Task::title property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_task_get_title (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), NULL);
+
+       return e_json_get_string_member (task, "title", NULL);
+}
+
+/**
+ * e_gdata_task_add_title:
+ * @builder: a #JsonBuilder with a started object member
+ * @value: a Task::title property value
+ *
+ * Adds a Task::title property @value into the @builder, which
+ * should have started an object member.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_task_add_title (JsonBuilder *builder,
+                       const gchar *value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (value != NULL);
+
+       e_json_add_string_member (builder, "title", value);
+}
+
+/**
+ * e_gdata_task_get_updated:
+ * @task: a GData Task
+ *
+ * Returns Task::updated property, as Unix time.
+ *
+ * Returns: Task::updated property or 0, when not found
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_task_get_updated (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), 0);
+
+       return e_json_get_iso8601_member (task, "updated", 0);
+}
+
+/**
+ * e_gdata_task_get_self_link:
+ * @task: a GData TaskList
+ *
+ * Returns Task::selfLink property.
+ *
+ * Returns: (transfer none) (nullable): Task::selfLink property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_task_get_self_link (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), NULL);
+
+       return e_json_get_string_member (task, "selfLink", NULL);
+}
+
+/**
+ * e_gdata_task_get_parent:
+ * @task: a GData Task
+ *
+ * Returns Task::parent property.
+ *
+ * Returns: (transfer none) (nullable): Task::parent property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_task_get_parent (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), NULL);
+
+       return e_json_get_string_member (task, "parent", NULL);
+}
+
+/**
+ * e_gdata_task_get_position:
+ * @task: a GData Task
+ *
+ * Returns Task::position property.
+ *
+ * Returns: (transfer none) (nullable): Task::position property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_task_get_position (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), NULL);
+
+       return e_json_get_string_member (task, "position", NULL);
+}
+
+/**
+ * e_gdata_task_get_notes:
+ * @task: a GData Task
+ *
+ * Returns Task::notes property.
+ *
+ * Returns: (transfer none) (nullable): Task::notes property or %NULL, when not found
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_gdata_task_get_notes (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), NULL);
+
+       return e_json_get_string_member (task, "notes", NULL);
+}
+
+/**
+ * e_gdata_task_add_notes:
+ * @builder: a #JsonBuilder with a started object member
+ * @value: (nullable): a Task::notes property value
+ *
+ * Adds a Task::notes property @value into the @builder, which
+ * should have started an object member.
+ *
+ * When the @value is %NULL, then adds a NULL-object, to indicate removal
+ * of the property.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_task_add_notes (JsonBuilder *builder,
+                       const gchar *value)
+{
+       g_return_if_fail (builder != NULL);
+
+       if (value)
+               e_json_add_string_member (builder, "notes", value);
+       else
+               e_json_add_null_member (builder, "notes");
+}
+
+/**
+ * e_gdata_task_get_status:
+ * @task: a GData Task
+ *
+ * Returns Task::status property.
+ *
+ * Returns: Task::status property as #EGDataTaskStatus or %E_GDATA_TASK_STATUS_UNKNOWN,
+ *    when not found or has set an unknown value.
+ *
+ * Since: 3.46
+ **/
+EGDataTaskStatus
+e_gdata_task_get_status (JsonObject *task)
+{
+       const gchar *str;
+
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), E_GDATA_TASK_STATUS_UNKNOWN);
+
+       str = e_json_get_string_member (task, "status", NULL);
+
+       if (g_strcmp0 (str, "needsAction") == 0)
+               return E_GDATA_TASK_STATUS_NEEDS_ACTION;
+
+       if (g_strcmp0 (str, "completed") == 0)
+               return E_GDATA_TASK_STATUS_COMPLETED;
+
+       return E_GDATA_TASK_STATUS_UNKNOWN;
+}
+
+/**
+ * e_gdata_task_add_status:
+ * @builder: a #JsonBuilder with a started object member
+ * @value: a Task::status property value
+ *
+ * Adds a Task::status property @value into the @builder, which
+ * should have started an object member.
+ *
+ * When the @value is %E_GDATA_TASK_STATUS_UNKNOWN, then adds a NULL-object,
+ * to indicate removal of the property.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_task_add_status (JsonBuilder *builder,
+                        EGDataTaskStatus value)
+{
+       g_return_if_fail (builder != NULL);
+
+       switch (value) {
+       case E_GDATA_TASK_STATUS_UNKNOWN:
+               e_json_add_null_member (builder, "status");
+               break;
+       case E_GDATA_TASK_STATUS_NEEDS_ACTION:
+               e_json_add_string_member (builder, "status", "needsAction");
+               break;
+       case E_GDATA_TASK_STATUS_COMPLETED:
+               e_json_add_string_member (builder, "status", "completed");
+               break;
+       }
+}
+
+/**
+ * e_gdata_task_get_due:
+ * @task: a GData Task
+ *
+ * Returns Task::due property, as Unix time.
+ *
+ * Returns: Task::due property or 0, when not found
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_task_get_due (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), 0);
+
+       return e_json_get_iso8601_member (task, "due", 0);
+}
+
+/**
+ * e_gdata_task_add_due:
+ * @builder: a #JsonBuilder with a started object member
+ * @value: a Task::due property value, as Unix time
+ *
+ * Adds a Task::due property @value into the @builder, which
+ * should have started an object member.
+ *
+ * When the @value is 0, then adds a NULL-object, to indicate
+ * removal of the property.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_task_add_due (JsonBuilder *builder,
+                     gint64 value)
+{
+       g_return_if_fail (builder != NULL);
+
+       if (value != 0)
+               e_json_add_iso8601_member (builder, "due", value);
+       else
+               e_json_add_null_member (builder, "due");
+}
+
+/**
+ * e_gdata_task_get_completed:
+ * @task: a GData Task
+ *
+ * Returns Task::completed property, as Unix time.
+ *
+ * Returns: Task::completed property or 0, when not found
+ *
+ * Since: 3.46
+ **/
+gint64
+e_gdata_task_get_completed (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), 0);
+
+       return e_json_get_iso8601_member (task, "completed", 0);
+}
+
+/**
+ * e_gdata_task_add_completed:
+ * @builder: a #JsonBuilder with a started object member
+ * @value: a Task::completed property value, as Unix time
+ *
+ * Adds a Task:completed property @value into the @builder, which
+ * should have started an object member.
+ *
+ * When the @value is 0, then adds a NULL-object, to indicate
+ * removal of the property.
+ *
+ * Since: 3.46
+ **/
+void
+e_gdata_task_add_completed (JsonBuilder *builder,
+                           gint64 value)
+{
+       g_return_if_fail (builder != NULL);
+
+       if (value != 0)
+               e_json_add_iso8601_member (builder, "completed", value);
+       else
+               e_json_add_null_member (builder, "completed");
+}
+
+/**
+ * e_gdata_task_get_deleted:
+ * @task: a GData Task
+ *
+ * Returns Task::deleted property, as Unix time.
+ *
+ * Returns: Task::deleted property or %FALSE, when not found
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_task_get_deleted (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), FALSE);
+
+       return e_json_get_boolean_member (task, "deleted", FALSE);
+}
+
+/**
+ * e_gdata_task_get_hidden:
+ * @task: a GData Task
+ *
+ * Returns Task::hidden property, as Unix time.
+ *
+ * Returns: Task::hidden property or %FALSE, when not found
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_task_get_hidden (JsonObject *task)
+{
+       g_return_val_if_fail (e_gdata_object_is_kind (task, "tasks#task"), FALSE);
+
+       return e_json_get_boolean_member (task, "hidden", FALSE);
+}
+
+/**
+ * e_gdata_session_tasks_clear_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Clears all completed tasks from the task list @tasklist_id. The affected tasks
+ * will be marked as 'hidden' and no longer be returned by default when retrieving
+ * all tasks for a task list.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasks_clear_sync (EGDataSession *self,
+                                 const gchar *tasklist_id,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *path;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+
+       path = g_strconcat (tasklist_id, "/clear", NULL);
+       message = e_gdata_session_new_message (self, SOUP_METHOD_POST, TASKS_TOP_URL, path, FALSE, NULL, 
NULL, error, NULL);
+       g_free (path);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, NULL, NULL, cancellable, error);
+
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasks::clear");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasks_delete_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @task_id: id of a task
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Deletes a task @task_id from a task list @tasklist_id.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasks_delete_sync (EGDataSession *self,
+                                  const gchar *tasklist_id,
+                                  const gchar *task_id,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *path;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+
+       path = g_strconcat (tasklist_id, "/tasks/", task_id, NULL);
+       message = e_gdata_session_new_message (self, SOUP_METHOD_DELETE, TASKS_TOP_URL, path, FALSE, NULL, 
NULL, error, NULL);
+       g_free (path);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, NULL, NULL, cancellable, error);
+
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasks::delete");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasks_get_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @task_id: id of a task
+ * @out_task: (out) (transfer full): task object
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a task @task_id from a task list @tasklist_id and returns it as a #JsonObject,
+ * which should be freed with json_object_unref(), when no longer needed.
+ *
+ * There can be used e_gdata_task_get_id(), e_gdata_task_get_etag(),
+ * e_gdata_task_get_title() and other e_gdata_task_... functions
+ * to read the properties of the task.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasks_get_sync (EGDataSession *self,
+                               const gchar *tasklist_id,
+                               const gchar *task_id,
+                               JsonObject **out_task,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+       SoupMessage *message;
+       JsonNode *node = NULL;
+       gboolean success;
+       gchar *path;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (out_task != NULL, FALSE);
+
+       *out_task = NULL;
+
+       path = g_strconcat (tasklist_id, "/tasks/", task_id, NULL);
+       message = e_gdata_session_new_message (self, SOUP_METHOD_GET, TASKS_TOP_URL, path, FALSE, NULL, NULL, 
error, NULL);
+       g_free (path);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, &node, NULL, cancellable, error);
+
+       if (success && node) {
+               JsonObject *object;
+
+               object = json_node_get_object (node);
+               if (object)
+                       *out_task = json_object_ref (object);
+       }
+
+       g_clear_object (&message);
+       g_clear_pointer (&node, json_node_unref);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasks::get");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasks_insert_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @task: a #JsonBuilder with the task object
+ * @parent_task_id: (nullable): parent task identifier, or %NULL to create at the top-level
+ * @previous_task_id: (nullable): previous sibling task identifier, or %NULL to create at the first position 
among its siblings
+ * @out_inserted_task: (out) (transfer full): the created task
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new task @task in the task list @tasklist_id. The @out_inserted_task should
+ * be freed with json_object_unref(), when no longer needed.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasks_insert_sync (EGDataSession *self,
+                                  const gchar *tasklist_id,
+                                  JsonBuilder *task,
+                                  const gchar *parent_task_id,
+                                  const gchar *previous_task_id,
+                                  JsonObject **out_inserted_task,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       SoupMessage *message;
+       JsonNode *node = NULL;
+       gboolean success;
+       gchar *path;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+       g_return_val_if_fail (task != NULL, FALSE);
+       g_return_val_if_fail (out_inserted_task != NULL, FALSE);
+
+       *out_inserted_task = NULL;
+
+       path = g_strconcat (tasklist_id, "/tasks", NULL);
+       message = e_gdata_session_new_message (self, SOUP_METHOD_POST, TASKS_TOP_URL, path, FALSE, NULL, 
task, error,
+               parent_task_id ? "parent" : previous_task_id ? "previous" : NULL,
+               G_TYPE_STRING,
+               parent_task_id ? parent_task_id : previous_task_id ? previous_task_id : NULL,
+               parent_task_id && previous_task_id ? "previous" : NULL,
+               G_TYPE_STRING,
+               previous_task_id,
+               NULL);
+       g_free (path);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, &node, NULL, cancellable, error);
+
+       if (success && node) {
+               JsonObject *object;
+
+               object = json_node_get_object (node);
+               if (object)
+                       *out_inserted_task = json_object_ref (object);
+       }
+
+       g_clear_object (&message);
+       g_clear_pointer (&node, json_node_unref);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasks::insert");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasks_list_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @query: (nullable): an #EGDataQuery to limit returned tasks, or %NULL
+ * @cb: (scope call): an #EGDataObjectCallback to call for each found task
+ * @user_data: (closure cb): user data passed to the @cb
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Lists all tasks in the task list @tasklist_id, calling the @cb for each of them.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasks_list_sync (EGDataSession *self,
+                                const gchar *tasklist_id,
+                                EGDataQuery *query,
+                                EGDataObjectCallback cb,
+                                gpointer user_data,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       gboolean success;
+       gchar *base_url;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (cb != NULL, FALSE);
+
+       base_url = g_strconcat (TASKS_TOP_URL, "/", tasklist_id, "/tasks", NULL);
+       success = e_gdata_session_handle_resource_items (self, SOUP_METHOD_GET, base_url, FALSE,
+               query, cb, user_data, cancellable, error);
+       g_free (base_url);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasks::list");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasks_move_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @task_id: id of a task
+ * @parent_task_id: (nullable): parent task identifier, or %NULL to move at the top-level
+ * @previous_task_id: (nullable): previous sibling task identifier, or %NULL to move at the first position 
among its siblings
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Moves the specified task @task_id to another position in the task
+ * list @tasklist_id. This can include putting it as a child task under
+ * a new parent and/or move it to a different position among its sibling tasks.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasks_move_sync (EGDataSession *self,
+                                const gchar *tasklist_id,
+                                const gchar *task_id,
+                                const gchar *parent_task_id,
+                                const gchar *previous_task_id,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *path;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+
+       path = g_strconcat (tasklist_id, "/tasks/", task_id, "/move", NULL);
+       message = e_gdata_session_new_message (self, SOUP_METHOD_POST, TASKS_TOP_URL, path, FALSE, NULL, 
NULL, error,
+               parent_task_id ? "parent" : previous_task_id ? "previous" : NULL,
+               G_TYPE_STRING,
+               parent_task_id ? parent_task_id : previous_task_id ? previous_task_id : NULL,
+               parent_task_id && previous_task_id ? "previous" : NULL,
+               G_TYPE_STRING,
+               previous_task_id,
+               NULL);
+       g_free (path);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, NULL, NULL, cancellable, error);
+
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasks::move");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasks_patch_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @task_id: id of a task
+ * @task_properties: task properties to change
+ * @out_patched_task: (out) (optional) (transfer full): where to set patches task, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Changes properties @task_properties of a task @task_id in the task list @tasklist_id.
+ *
+ * If not %NULL, free the @out_patched_task with json_object_unref(),
+ * when no longer needed.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasks_patch_sync (EGDataSession *self,
+                                 const gchar *tasklist_id,
+                                 const gchar *task_id,
+                                 JsonBuilder *task_properties,
+                                 JsonObject **out_patched_task,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       SoupMessage *message;
+       JsonNode *node = NULL;
+       gboolean success;
+       gchar *path;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (task_properties != NULL, FALSE);
+
+       path = g_strconcat (tasklist_id, "/tasks/", task_id, NULL);
+       message = e_gdata_session_new_message (self, "PATCH", TASKS_TOP_URL, path, FALSE, NULL, 
task_properties, error, NULL);
+       g_free (path);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, out_patched_task ? &node : NULL, NULL, 
cancellable, error);
+
+       if (success && node && out_patched_task) {
+               JsonObject *object;
+
+               object = json_node_get_object (node);
+               if (object)
+                       *out_patched_task = json_object_ref (object);
+       }
+
+       g_clear_pointer (&node, json_node_unref);
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasks::patch");
+
+       return success;
+}
+
+/**
+ * e_gdata_session_tasks_update_sync:
+ * @self: an #EGDataSession
+ * @tasklist_id: id of a task list
+ * @task_id: id of a task
+ * @task: task object to update the task with
+ * @out_updated_task: (out) (optional) (transfer full): where to store updated task, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Updates a task @task_id in a task list @tasklist_id to the values from the @task.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_gdata_session_tasks_update_sync (EGDataSession *self,
+                                  const gchar *tasklist_id,
+                                  const gchar *task_id,
+                                  JsonBuilder *task,
+                                  JsonObject **out_updated_task,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       SoupMessage *message;
+       JsonNode *node = NULL;
+       gboolean success;
+       gchar *path;
+
+       g_return_val_if_fail (E_IS_GDATA_SESSION (self), FALSE);
+       g_return_val_if_fail (tasklist_id != NULL, FALSE);
+       g_return_val_if_fail (task_id != NULL, FALSE);
+       g_return_val_if_fail (task != NULL, FALSE);
+
+       path = g_strconcat (tasklist_id, "/tasks/", task_id, NULL);
+       message = e_gdata_session_new_message (self, SOUP_METHOD_PUT, TASKS_TOP_URL, path, FALSE, NULL, task, 
error, NULL);
+       g_free (path);
+
+       if (!message)
+               return FALSE;
+
+       success = e_gdata_session_send_request_sync (self, message, out_updated_task ? &node : NULL, NULL, 
cancellable, error);
+
+       if (success && node && out_updated_task) {
+               JsonObject *object;
+
+               object = json_node_get_object (node);
+               if (object)
+                       *out_updated_task = json_object_ref (object);
+       }
+
+       g_clear_pointer (&node, json_node_unref);
+       g_clear_object (&message);
+
+       g_prefix_error (error, _("Failed to call %s: "), "tasks::update");
+
+       return success;
+}
diff --git a/src/libedataserver/e-gdata-session.h b/src/libedataserver/e-gdata-session.h
new file mode 100644
index 000000000..d58dde42b
--- /dev/null
+++ b/src/libedataserver/e-gdata-session.h
@@ -0,0 +1,229 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION)
+#error "Only <libedataserver/libedataserver.h> should be included directly."
+#endif
+
+#ifndef E_GDATA_SESSION_H
+#define E_GDATA_SESSION_H
+
+#ifndef __GI_SCANNER__
+
+#include <json-glib/json-glib.h>
+#include <libedataserver/e-gdata-query.h>
+#include <libedataserver/e-soup-session.h>
+
+/* Standard GObject macros */
+#define E_TYPE_GDATA_SESSION \
+       (e_gdata_session_get_type ())
+#define E_GDATA_SESSION(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_GDATA_SESSION, EGDataSession))
+#define E_GDATA_SESSION_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_GDATA_SESSION, EGDataSessionClass))
+#define E_IS_GDATA_SESSION(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_GDATA_SESSION))
+#define E_IS_GDATA_SESSION_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_GDATA_SESSION))
+#define E_GDATA_SESSION_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_GDATA_SESSION, EGDataSessionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EGDataSession EGDataSession;
+typedef struct _EGDataSessionClass EGDataSessionClass;
+typedef struct _EGDataSessionPrivate EGDataSessionPrivate;
+
+/**
+ * EGDataObjectCallback:
+ * @gdata: an #EGDataSession
+ * @object: a #JSonObject with received content
+ * @user_data: user data passed to the call
+ *
+ * Callback used to traverse response from the server, which is
+ * an array.
+ *
+ * Returns: whether the traverse can continue
+ *
+ * Since: 3.46
+ **/
+typedef gboolean (*EGDataObjectCallback) (EGDataSession *gdata,
+                                         JsonObject *object,
+                                         gpointer user_data);
+
+/**
+ * EGDataSession:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.46
+ **/
+struct _EGDataSession {
+       /*< private >*/
+       ESoupSession parent;
+       EGDataSessionPrivate *priv;
+};
+
+struct _EGDataSessionClass {
+       ESoupSessionClass parent_class;
+
+       /* Padding for future expansion */
+       gpointer reserved[10];
+};
+
+GType          e_gdata_session_get_type                (void) G_GNUC_CONST;
+
+EGDataSession *        e_gdata_session_new                     (ESource *source);
+
+const gchar *  e_gdata_tasklist_get_id                 (JsonObject *tasklist);
+void           e_gdata_tasklist_add_id                 (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_gdata_tasklist_get_etag               (JsonObject *tasklist);
+const gchar *  e_gdata_tasklist_get_title              (JsonObject *tasklist);
+void           e_gdata_tasklist_add_title              (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_gdata_tasklist_get_self_link          (JsonObject *tasklist);
+gint64         e_gdata_tasklist_get_updated            (JsonObject *tasklist);
+
+gboolean       e_gdata_session_tasklists_delete_sync   (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasklists_get_sync      (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        JsonObject **out_tasklist,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasklists_insert_sync   (EGDataSession *self,
+                                                        const gchar *title,
+                                                        JsonObject **out_inserted_tasklist,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasklists_list_sync     (EGDataSession *self,
+                                                        EGDataQuery *query,
+                                                        EGDataObjectCallback cb,
+                                                        gpointer user_data,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasklists_patch_sync    (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        JsonBuilder *tasklist_properties,
+                                                        JsonObject **out_patched_tasklist,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasklists_update_sync   (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        JsonBuilder *tasklist,
+                                                        JsonObject **out_updated_tasklist,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+
+/**
+ * EGDataTaskStatus:
+ * @E_GDATA_TASK_STATUS_UNKNOWN: unknown status
+ * @E_GDATA_TASK_STATUS_NEEDS_ACTION: the task needs action
+ * @E_GDATA_TASK_STATUS_COMPLETED: the task is completed
+ *
+ * Holds status of a task.
+ *
+ * Since: 3.46
+ **/
+typedef enum {
+       E_GDATA_TASK_STATUS_UNKNOWN,
+       E_GDATA_TASK_STATUS_NEEDS_ACTION,
+       E_GDATA_TASK_STATUS_COMPLETED
+} EGDataTaskStatus;
+
+const gchar *  e_gdata_task_get_id                     (JsonObject *task);
+void           e_gdata_task_add_id                     (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_gdata_task_get_etag                   (JsonObject *task);
+const gchar *  e_gdata_task_get_title                  (JsonObject *task);
+void           e_gdata_task_add_title                  (JsonBuilder *builder,
+                                                        const gchar *value);
+gint64         e_gdata_task_get_updated                (JsonObject *task);
+const gchar *  e_gdata_task_get_self_link              (JsonObject *task);
+const gchar *  e_gdata_task_get_parent                 (JsonObject *task);
+const gchar *  e_gdata_task_get_position               (JsonObject *task);
+const gchar *  e_gdata_task_get_notes                  (JsonObject *task);
+void           e_gdata_task_add_notes                  (JsonBuilder *builder,
+                                                        const gchar *value);
+EGDataTaskStatus
+               e_gdata_task_get_status                 (JsonObject *task);
+void           e_gdata_task_add_status                 (JsonBuilder *builder,
+                                                        EGDataTaskStatus value);
+gint64         e_gdata_task_get_due                    (JsonObject *task);
+void           e_gdata_task_add_due                    (JsonBuilder *builder,
+                                                        gint64 value);
+gint64         e_gdata_task_get_completed              (JsonObject *task);
+void           e_gdata_task_add_completed              (JsonBuilder *builder,
+                                                        gint64 value);
+gboolean       e_gdata_task_get_deleted                (JsonObject *task);
+gboolean       e_gdata_task_get_hidden                 (JsonObject *task);
+
+gboolean       e_gdata_session_tasks_clear_sync        (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasks_delete_sync       (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        const gchar *task_id,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasks_get_sync          (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        const gchar *task_id,
+                                                        JsonObject **out_task,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasks_insert_sync       (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        JsonBuilder *task,
+                                                        const gchar *parent_task_id,
+                                                        const gchar *previous_task_id,
+                                                        JsonObject **out_inserted_task,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasks_list_sync         (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        EGDataQuery *query,
+                                                        EGDataObjectCallback cb,
+                                                        gpointer user_data,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasks_move_sync         (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        const gchar *task_id,
+                                                        const gchar *parent_task_id,
+                                                        const gchar *previous_task_id,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasks_patch_sync        (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        const gchar *task_id,
+                                                        JsonBuilder *task_properties,
+                                                        JsonObject **out_patched_task,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+gboolean       e_gdata_session_tasks_update_sync       (EGDataSession *self,
+                                                        const gchar *tasklist_id,
+                                                        const gchar *task_id,
+                                                        JsonBuilder *task,
+                                                        JsonObject **out_updated_task,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+
+#endif /* __GI_SCANNER__ */
+
+G_END_DECLS
+
+#endif /* E_GDATA_SESSION_H */
diff --git a/src/libedataserver/e-json-utils.c b/src/libedataserver/e-json-utils.c
new file mode 100644
index 000000000..fea6ec982
--- /dev/null
+++ b/src/libedataserver/e-json-utils.c
@@ -0,0 +1,775 @@
+/* -*- 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
+ */
+
+/**
+ * SECTION: e-json-utils
+ * @include: libedataserver/libedataserver.h
+ * @short_description: A set of JSON utility functions
+ **/
+
+#include "evolution-data-server-config.h"
+
+#include <json-glib/json-glib.h>
+
+#include "e-json-utils.h"
+
+/**
+ * e_json_get_array_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ *
+ * Returns the @member_name of the @object as a #JsonArray,
+ * checking the type is as expected. Asking for an array
+ * when the member holds a different type is considered
+ * a programmer error.
+ *
+ * Returns: (transfer none) (nullable): the @member_name of the @object
+ *   as a #JsonArray, or %NULL when the member cannot be found or when
+ *   it is a JSON-NULL object.
+ *
+ * Since: 3.46
+ **/
+JsonArray *
+e_json_get_array_member (JsonObject *object,
+                        const gchar *member_name)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, NULL);
+       g_return_val_if_fail (member_name != NULL, NULL);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node || JSON_NODE_HOLDS_NULL (node))
+               return NULL;
+
+       g_return_val_if_fail (JSON_NODE_HOLDS_ARRAY (node), NULL);
+
+       return json_node_get_array (node);
+}
+
+/**
+ * e_json_begin_array_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (nullable): name of the array
+ *
+ * Begins a new array object, optionally named @member_name.
+ * The @member_name can be %NULL or an empty string,
+ * in which case the new array has no name (unless the @builder
+ * has already set the member name).
+ *
+ * End the array object with e_json_end_array_member().
+ *
+ * Since: 3.46
+ **/
+void
+e_json_begin_array_member (JsonBuilder *builder,
+                          const gchar *member_name)
+{
+       g_return_if_fail (builder != NULL);
+
+       if (member_name && *member_name)
+               json_builder_set_member_name (builder, member_name);
+
+       json_builder_begin_array (builder);
+}
+
+/**
+ * e_json_end_array_member: (skip)
+ * @builder: a #JsonBuilder
+ *
+ * Ends the array object begun with e_json_begin_array_member().
+ *
+ * Since: 3.46
+ **/
+void
+e_json_end_array_member (JsonBuilder *builder)
+{
+       g_return_if_fail (builder != NULL);
+
+       json_builder_end_array (builder);
+}
+
+/**
+ * e_json_get_boolean_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ * @default_value: the default value to return
+ *
+ * Returns the @member_name of the @object as a gboolean,
+ * checking the type is as expected. Asking for a gboolean
+ * when the member holds a different type is considered
+ * a programmer error.
+ *
+ * Returns: the @member_name of the @object as a gboolean, or the @default_value
+ *   when the member cannot be found or when it is a JSON-NULL object.
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_json_get_boolean_member (JsonObject *object,
+                          const gchar *member_name,
+                          gboolean default_value)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, default_value);
+       g_return_val_if_fail (member_name != NULL, default_value);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node || JSON_NODE_HOLDS_NULL (node))
+               return default_value;
+
+       g_return_val_if_fail (JSON_NODE_HOLDS_VALUE (node), default_value);
+
+       return json_node_get_boolean (node);
+}
+
+/**
+ * e_json_add_boolean_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ * @value: value to set
+ *
+ * Adds a boolean member named @member_name, holding the @value.
+ * The @member_name cannot be %NULL nor an empty string.
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_boolean_member (JsonBuilder *builder,
+                          const gchar *member_name,
+                          gboolean value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       json_builder_set_member_name (builder, member_name);
+       json_builder_add_boolean_value (builder, value);
+}
+
+/**
+ * e_json_get_double_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ * @default_value: the default value to return
+ *
+ * Returns the @member_name of the @object as a gdouble,
+ * checking the type is as expected. Asking for a gdouble
+ * when the member holds a different type is considered
+ * a programmer error.
+ *
+ * Returns: the @member_name of the @object as a gdouble, or the @default_value
+ *   when the member cannot be found or when it is a JSON-NULL object.
+ *
+ * Since: 3.46
+ **/
+gdouble
+e_json_get_double_member (JsonObject *object,
+                         const gchar *member_name,
+                         gdouble default_value)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, default_value);
+       g_return_val_if_fail (member_name != NULL, default_value);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node || JSON_NODE_HOLDS_NULL (node))
+               return default_value;
+
+       g_return_val_if_fail (JSON_NODE_HOLDS_VALUE (node), default_value);
+
+       return json_node_get_double (node);
+}
+
+/**
+ * e_json_add_double_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ * @value: value to set
+ *
+ * Adds a double member named @member_name, holding the @value.
+ * The @member_name cannot be %NULL nor an empty string.
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_double_member (JsonBuilder *builder,
+                         const gchar *member_name,
+                         gdouble value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       json_builder_set_member_name (builder, member_name);
+       json_builder_add_double_value (builder, value);
+}
+
+/**
+ * e_json_get_int_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ * @default_value: the default value to return
+ *
+ * Returns the @member_name of the @object as a gint64,
+ * checking the type is as expected. Asking for a gint64
+ * when the member holds a different type is considered
+ * a programmer error.
+ *
+ * Returns: the @member_name of the @object as a gint64, or the @default_value
+ *   when the member cannot be found or when it is a JSON-NULL object.
+ *
+ * Since: 3.46
+ **/
+gint64
+e_json_get_int_member (JsonObject *object,
+                      const gchar *member_name,
+                      gint64 default_value)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, default_value);
+       g_return_val_if_fail (member_name != NULL, default_value);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node || JSON_NODE_HOLDS_NULL (node))
+               return default_value;
+
+       g_return_val_if_fail (JSON_NODE_HOLDS_VALUE (node), default_value);
+
+       return json_node_get_int (node);
+}
+
+/**
+ * e_json_add_int_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ * @value: value to set
+ *
+ * Adds an integer member named @member_name, holding the @value.
+ * The @member_name cannot be %NULL nor an empty string.
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_int_member (JsonBuilder *builder,
+                      const gchar *member_name,
+                      gint64 value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       json_builder_set_member_name (builder, member_name);
+       json_builder_add_int_value (builder, value);
+}
+
+/**
+ * e_json_get_null_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ * @default_value: the default value to return
+ *
+ * Returns whether the @member_name of the @object is a JSON-NULL object,
+ * or the @default_value, when the @member_name does not exist.
+ *
+ * Returns: whether the @member_name of the @object is a JSON-NULL object,
+ *   or the @default_value when the member cannot be found.
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_json_get_null_member (JsonObject *object,
+                       const gchar *member_name,
+                       gboolean default_value)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, default_value);
+       g_return_val_if_fail (member_name != NULL, default_value);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node)
+               return default_value;
+
+       g_return_val_if_fail (JSON_NODE_HOLDS_NULL (node), default_value);
+
+       return json_node_is_null (node);
+}
+
+/**
+ * e_json_add_null_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ *
+ * Adds a JSON-NULL member named @member_name.
+ * The @member_name cannot be %NULL nor an empty string.
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_null_member (JsonBuilder *builder,
+                       const gchar *member_name)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       json_builder_set_member_name (builder, member_name);
+       json_builder_add_null_value (builder);
+}
+
+/**
+ * e_json_get_object_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ *
+ * Returns the @member_name of the @object as a #JsonObject,
+ * checking the type is as expected. Asking for an object
+ * when the member holds a different type is considered
+ * a programmer error.
+ *
+ * Returns: (transfer none) (nullable): the @member_name of the @object
+ *   as a #JsonObject, or %NULL when the member cannot be found or when
+ *   it is a JSON-NULL object.
+ *
+ * Since: 3.46
+ **/
+JsonObject *
+e_json_get_object_member (JsonObject *object,
+                         const gchar *member_name)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, NULL);
+       g_return_val_if_fail (member_name != NULL, NULL);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node || JSON_NODE_HOLDS_NULL (node))
+               return NULL;
+
+       g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (node), NULL);
+
+       return json_node_get_object (node);
+}
+
+/**
+ * e_json_begin_object_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (nullable): name of the object
+ *
+ * Begins a new object, optionally named @member_name.
+ * The @member_name can be %NULL or an empty string,
+ * in which case the new object has no name (unless the @builder
+ * has already set the member name).
+ *
+ * End the object with e_json_end_object_member().
+ *
+ * Since: 3.46
+ **/
+void
+e_json_begin_object_member (JsonBuilder *builder,
+                           const gchar *member_name)
+{
+       g_return_if_fail (builder != NULL);
+
+       if (member_name && *member_name)
+               json_builder_set_member_name (builder, member_name);
+
+       json_builder_begin_object (builder);
+}
+
+/**
+ * e_json_end_object_member: (skip)
+ * @builder: a #JsonBuilder
+ *
+ * Ends the object begun with e_json_begin_object_member().
+ *
+ * Since: 3.46
+ **/
+void
+e_json_end_object_member (JsonBuilder *builder)
+{
+       g_return_if_fail (builder != NULL);
+
+       json_builder_end_object (builder);
+}
+
+/**
+ * e_json_get_string_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ * @default_value: (nullable): the default value to return
+ *
+ * Returns the @member_name of the @object as a string,
+ * checking the type is as expected. Asking for a string
+ * when the member holds a different type is considered
+ * a programmer error.
+ *
+ * Returns: (transfer none) (nullable): the @member_name of the @object as string,
+ *   or the @default_value when the member cannot be found or when it is a JSON-NULL object.
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_json_get_string_member (JsonObject *object,
+                         const gchar *member_name,
+                         const gchar *default_value)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, default_value);
+       g_return_val_if_fail (member_name != NULL, default_value);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node || JSON_NODE_HOLDS_NULL (node))
+               return default_value;
+
+       g_return_val_if_fail (JSON_NODE_HOLDS_VALUE (node), default_value);
+
+       return json_node_get_string (node);
+}
+
+/**
+ * e_json_add_string_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ * @value: (nullable): value to set
+ *
+ * Adds a string member named @member_name, holding the @value.
+ * The @member_name cannot be %NULL nor an empty string.
+ * When the @value is %NULL, an empty string is set instead.
+ *
+ * See e_json_add_nonempty_string_member(), e_json_add_nonempty_or_null_string_member().
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_string_member (JsonBuilder *builder,
+                         const gchar *member_name,
+                         const gchar *value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       json_builder_set_member_name (builder, member_name);
+       json_builder_add_string_value (builder, value ? value : "");
+}
+
+/**
+ * e_json_add_nonempty_string_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ * @value: (nullable): value to set
+ *
+ * Adds a string member named @member_name, holding the @value,
+ * but only if the @value is a non-empty string.
+ * The @member_name cannot be %NULL nor an empty string.
+ *
+ * See e_json_add_string_member(), e_json_add_nonempty_or_null_string_member().
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_nonempty_string_member (JsonBuilder *builder,
+                                  const gchar *member_name,
+                                  const gchar *value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       if (value && *value)
+               e_json_add_string_member (builder, member_name, value);
+}
+
+/**
+ * e_json_add_nonempty_or_null_string_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ * @value: (nullable): value to set
+ *
+ * Adds a string member named @member_name, holding the @value,
+ * if the @value is a non-empty string, otherwise adds a JSON-NULL
+ * object.
+ *
+ * The @member_name cannot be %NULL nor an empty string.
+ *
+ * See e_json_add_string_member(), e_json_add_nonempty_string_member().
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_nonempty_or_null_string_member (JsonBuilder *builder,
+                                          const gchar *member_name,
+                                          const gchar *value)
+{
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       if (value && *value)
+               e_json_add_string_member (builder, member_name, value);
+       else
+               e_json_add_null_member (builder, member_name);
+}
+
+/**
+ * e_json_get_date_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ * @default_value: the default value to return
+ *
+ * Returns the @member_name date (encoded as YYYY-MM-DD) of the @object as
+ * a gint64 Unix time. The member itself is expected to be of type string.
+ *
+ * Returns: the @member_name date (encoded as YYYY-MM-DD) of the @object as
+ *   a gint64 Unix time, or the @default_value when the member cannot
+ *   be found or when it is a JSON-NULL object.
+ *
+ * Since: 3.46
+ **/
+gint64
+e_json_get_date_member (JsonObject *object,
+                       const gchar *member_name,
+                       gint64 default_value)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, default_value);
+       g_return_val_if_fail (member_name != NULL, default_value);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node || JSON_NODE_HOLDS_NULL (node) || !JSON_NODE_HOLDS_VALUE (node))
+               return default_value;
+
+       return e_json_util_decode_date (json_node_get_string (node), default_value);
+}
+
+/**
+ * e_json_add_date_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ * @value: value to set, as Unix time
+ *
+ * Adds a date (encoded as YYYY-MM-DD) member named @member_name, holding
+ * the date as Unix time in the @value, stored as string.
+ * The @member_name cannot be %NULL nor an empty string.
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_date_member (JsonBuilder *builder,
+                       const gchar *member_name,
+                       gint64 value)
+{
+       gchar *str;
+
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       str = e_json_util_encode_date (value);
+       g_return_if_fail (str != NULL);
+
+       e_json_add_string_member (builder, member_name, str);
+
+       g_free (str);
+}
+
+/**
+ * e_json_get_iso8601_member: (skip)
+ * @object: a #JsonObject object
+ * @member_name: name of the member
+ * @default_value: the default value to return
+ *
+ * Returns the @member_name ISO 8601 date/time of the @object as
+ * a gint64 Unix time. The member itself is expected to be of type string.
+ *
+ * Returns: the @member_name ISO 8601 date/time of the @object as
+ *   a gint64 Unix time, or the @default_value when the member cannot
+ *   be found or when it is a JSON-NULL object.
+ *
+ * Since: 3.46
+ **/
+gint64
+e_json_get_iso8601_member (JsonObject *object,
+                          const gchar *member_name,
+                          gint64 default_value)
+{
+       JsonNode *node;
+
+       g_return_val_if_fail (object != NULL, default_value);
+       g_return_val_if_fail (member_name != NULL, default_value);
+
+       node = json_object_get_member (object, member_name);
+
+       if (!node || JSON_NODE_HOLDS_NULL (node) || !JSON_NODE_HOLDS_VALUE (node))
+               return default_value;
+
+       return e_json_util_decode_iso8601 (json_node_get_string (node), default_value);
+}
+
+/**
+ * e_json_add_iso8601_member: (skip)
+ * @builder: a #JsonBuilder
+ * @member_name: (not nullable): member name
+ * @value: value to set, as Unix time
+ *
+ * Adds a date/time (encoded as ISO 8601) member named @member_name, holding
+ * the Unix time in the @value, stored as string.
+ * The @member_name cannot be %NULL nor an empty string.
+ *
+ * Since: 3.46
+ **/
+void
+e_json_add_iso8601_member (JsonBuilder *builder,
+                          const gchar *member_name,
+                          gint64 value)
+{
+       gchar *str;
+
+       g_return_if_fail (builder != NULL);
+       g_return_if_fail (member_name && *member_name);
+
+       str = e_json_util_encode_iso8601 (value);
+       g_return_if_fail (str != NULL);
+
+       e_json_add_string_member (builder, member_name, str);
+
+       g_free (str);
+}
+
+/**
+ * e_json_util_decode_date: (skip)
+ * @str_date: (nullable): date as string
+ * @default_value: the default value to return
+ *
+ * Returns the @str_date (encoded as YYYY-MM-DD) as a gint64 Unix time.
+ *
+ * Returns: the @str_date (encoded as YYYY-MM-DD) as a gint64 Unix time,
+ *   or the @default_value when the @str_date cannot be decoded.
+ *
+ * Since: 3.46
+ **/
+gint64
+e_json_util_decode_date (const gchar *str_date,
+                        gint64 default_value)
+{
+       GDateTime *dt;
+       gchar *tmp;
+       gint64 res;
+
+       if (!str_date || !*str_date)
+               return default_value;
+
+       tmp = g_strconcat (str_date, "T00:00:00Z", NULL);
+       dt = g_date_time_new_from_iso8601 (tmp, NULL);
+       g_free (tmp);
+
+       if (dt) {
+               res = g_date_time_to_unix (dt);
+               g_date_time_unref (dt);
+       } else {
+               res = default_value;
+       }
+
+       return res;
+}
+
+/**
+ * e_json_util_encode_date: (skip)
+ * @value: date to encode, as Unix time
+ *
+ * Encodes (as YYYY-MM-DD) the date @value Unix time  to string.
+ *
+ * Returns: (transfer full): Unix time @value encoded as date
+ *
+ * Since: 3.46
+ **/
+gchar *
+e_json_util_encode_date (gint64 value)
+{
+       GDateTime *dt;
+       gchar *str;
+
+       dt = g_date_time_new_from_unix_utc (value);
+
+       g_return_val_if_fail (dt != NULL, NULL);
+
+       str = g_strdup_printf ("%04d-%02d-%02d",
+               g_date_time_get_year (dt),
+               g_date_time_get_month (dt),
+               g_date_time_get_day_of_month (dt));
+
+       g_date_time_unref (dt);
+
+       return str;
+}
+
+/**
+ * e_json_decode_iso8601: (skip)
+ * @str_datetime: (nullable): an ISO 8601 encoded date/time
+ * @default_value: the default value to return
+ *
+ * Returns the @str_datetime ISO 8601 date/time as a gint64 Unix time.
+ *
+ * Returns: the @str_datetime ISO 8601 date/time as a gint64 Unix time,
+ *   or the @default_value when the @str_datetime cannot be decoded.
+ *
+ * Since: 3.46
+ **/
+gint64
+e_json_util_decode_iso8601 (const gchar *str_datetime,
+                           gint64 default_value)
+{
+       GDateTime *dt;
+       gint64 res;
+
+       if (!str_datetime || !*str_datetime)
+               return default_value;
+
+       dt = g_date_time_new_from_iso8601 (str_datetime, NULL);
+
+       if (dt) {
+               res = g_date_time_to_unix (dt);
+               g_date_time_unref (dt);
+       } else {
+               res = default_value;
+       }
+
+       return res;
+}
+
+/**
+ * e_json_util_encode_iso8601: (skip)
+ * @value: date/time to encode, as Unix time
+ *
+ * Encodes (as ISO 8601) the date/time @value Unix time  to string.
+ *
+ * Returns: (transfer full): Unix time @value encoded as date/time
+ *
+ * Since: 3.46
+ **/
+gchar *
+e_json_util_encode_iso8601 (gint64 value)
+{
+       GDateTime *dt;
+       gchar *str;
+
+       dt = g_date_time_new_from_unix_utc (value);
+
+       g_return_val_if_fail (dt != NULL, NULL);
+
+       str = g_date_time_format_iso8601 (dt);
+
+       g_date_time_unref (dt);
+
+       return str;
+}
diff --git a/src/libedataserver/e-json-utils.h b/src/libedataserver/e-json-utils.h
new file mode 100644
index 000000000..b9919ed8b
--- /dev/null
+++ b/src/libedataserver/e-json-utils.h
@@ -0,0 +1,90 @@
+/* -*- 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
+ */
+
+#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION)
+#error "Only <libedataserver/libedataserver.h> should be included directly."
+#endif
+
+#ifndef E_JSON_UTILS_H
+#define E_JSON_UTILS_H
+
+#ifndef __GI_SCANNER__
+
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+JsonArray *    e_json_get_array_member                 (JsonObject *object,
+                                                        const gchar *member_name);
+void           e_json_begin_array_member               (JsonBuilder *builder,
+                                                        const gchar *member_name);
+void           e_json_end_array_member                 (JsonBuilder *builder);
+gboolean       e_json_get_boolean_member               (JsonObject *object,
+                                                        const gchar *member_name,
+                                                        gboolean default_value);
+void           e_json_add_boolean_member               (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        gboolean value);
+gdouble                e_json_get_double_member                (JsonObject *object,
+                                                        const gchar *member_name,
+                                                        gdouble default_value);
+void           e_json_add_double_member                (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        gdouble value);
+gint64         e_json_get_int_member                   (JsonObject *object,
+                                                        const gchar *member_name,
+                                                        gint64 default_value);
+void           e_json_add_int_member                   (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        gint64 value);
+gboolean       e_json_get_null_member                  (JsonObject *object,
+                                                        const gchar *member_name,
+                                                        gboolean default_value);
+void           e_json_add_null_member                  (JsonBuilder *builder,
+                                                        const gchar *member_name);
+JsonObject *   e_json_get_object_member                (JsonObject *object,
+                                                        const gchar *member_name);
+void           e_json_begin_object_member              (JsonBuilder *builder,
+                                                        const gchar *member_name);
+void           e_json_end_object_member                (JsonBuilder *builder);
+const gchar *  e_json_get_string_member                (JsonObject *object,
+                                                        const gchar *member_name,
+                                                        const gchar *default_value);
+void           e_json_add_string_member                (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        const gchar *value);
+void           e_json_add_nonempty_string_member       (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        const gchar *value);
+void           e_json_add_nonempty_or_null_string_member
+                                                       (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        const gchar *value);
+gint64         e_json_get_date_member                  (JsonObject *object,
+                                                        const gchar *member_name,
+                                                        gint64 default_value);
+void           e_json_add_date_member                  (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        gint64 value);
+gint64         e_json_get_iso8601_member               (JsonObject *object,
+                                                        const gchar *member_name,
+                                                        gint64 default_value);
+void           e_json_add_iso8601_member               (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        gint64 value);
+
+gint64         e_json_util_decode_date                 (const gchar *str_date,
+                                                        gint64 default_value);
+gchar *                e_json_util_encode_date                 (gint64 value);
+gint64         e_json_util_decode_iso8601              (const gchar *str_datetime,
+                                                        gint64 default_value);
+gchar *                e_json_util_encode_iso8601              (gint64 value);
+
+#endif /* __GI_SCANNER__ */
+
+G_END_DECLS
+
+#endif /* E_JSON_UTILS_H */
diff --git a/src/libedataserver/libedataserver.h b/src/libedataserver/libedataserver.h
index 0a4d52f4f..2051d38a5 100644
--- a/src/libedataserver/libedataserver.h
+++ b/src/libedataserver/libedataserver.h
@@ -32,7 +32,10 @@
 #include <libedataserver/e-flag.h>
 #include <libedataserver/e-free-form-exp.h>
 #include <libedataserver/e-gdata-oauth2-authorizer.h>
+#include <libedataserver/e-gdata-query.h>
+#include <libedataserver/e-gdata-session.h>
 #include <libedataserver/e-iterator.h>
+#include <libedataserver/e-json-utils.h>
 #include <libedataserver/e-list-iterator.h>
 #include <libedataserver/e-list.h>
 #include <libedataserver/e-memory.h>
diff --git a/src/libedataserver/libedataserver.pc.in b/src/libedataserver/libedataserver.pc.in
index 911cd319d..33bede929 100644
--- a/src/libedataserver/libedataserver.pc.in
+++ b/src/libedataserver/libedataserver.pc.in
@@ -12,7 +12,7 @@ credentialmoduledir=@credentialmoduledir@
 Name: libedataserver
 Description: Utility library for Evolution Data Server
 Version: @PROJECT_VERSION@
-Requires: gio-2.0 gmodule-2.0 libsecret-1 libxml-2.0 libsoup-3.0
+Requires: gio-2.0 gmodule-2.0 libsecret-1 libxml-2.0 libsoup-3.0 json-glib-1.0
 Requires.private: camel-@API_VERSION@
 Libs: -L${libdir} -ledataserver-@API_VERSION@
 Cflags: -I${privincludedir}


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