[evolution-data-server] Add functions to manipulate Google Task API
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server] Add functions to manipulate Google Task API
- Date: Thu, 7 Jul 2022 16:22:12 +0000 (UTC)
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]