summaryrefslogtreecommitdiff
path: root/src/plugins/eds
diff options
context:
space:
mode:
authorMatthew Fennell <matthew@fennell.dev>2025-12-27 12:40:20 +0000
committerMatthew Fennell <matthew@fennell.dev>2025-12-27 12:40:20 +0000
commit5d8e439bc597159e3c9f0a8b65c0ae869dead3a8 (patch)
treeed28aefed8add0da1c55c08fdf80b23c4346e0dc /src/plugins/eds
Import Upstream version 43.0upstream/latest
Diffstat (limited to 'src/plugins/eds')
-rw-r--r--src/plugins/eds/e-source-endeavour.c128
-rw-r--r--src/plugins/eds/e-source-endeavour.h35
-rw-r--r--src/plugins/eds/eds-plugin.c30
-rw-r--r--src/plugins/eds/eds.gresource.xml6
-rw-r--r--src/plugins/eds/eds.plugin14
-rw-r--r--src/plugins/eds/gtd-eds-autoptr.h27
-rw-r--r--src/plugins/eds/gtd-eds.h32
-rw-r--r--src/plugins/eds/gtd-plugin-eds.c323
-rw-r--r--src/plugins/eds/gtd-plugin-eds.h28
-rw-r--r--src/plugins/eds/gtd-provider-eds.c1157
-rw-r--r--src/plugins/eds/gtd-provider-eds.h61
-rw-r--r--src/plugins/eds/gtd-provider-goa.c262
-rw-r--r--src/plugins/eds/gtd-provider-goa.h43
-rw-r--r--src/plugins/eds/gtd-provider-local.c150
-rw-r--r--src/plugins/eds/gtd-provider-local.h37
-rw-r--r--src/plugins/eds/gtd-task-eds.c650
-rw-r--r--src/plugins/eds/gtd-task-eds.h46
-rw-r--r--src/plugins/eds/gtd-task-list-eds.c875
-rw-r--r--src/plugins/eds/gtd-task-list-eds.h53
-rw-r--r--src/plugins/eds/meson.build27
20 files changed, 3984 insertions, 0 deletions
diff --git a/src/plugins/eds/e-source-endeavour.c b/src/plugins/eds/e-source-endeavour.c
new file mode 100644
index 0000000..3ca8846
--- /dev/null
+++ b/src/plugins/eds/e-source-endeavour.c
@@ -0,0 +1,128 @@
+/* gtd-task-list-eds.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "e-source-endeavour.h"
+
+struct _ESourceEndeavour
+{
+ ESourceExtension parent;
+
+ guint api_version;
+};
+
+G_DEFINE_TYPE (ESourceEndeavour, e_source_endeavour, E_TYPE_SOURCE_EXTENSION)
+
+enum
+{
+ PROP_0,
+ PROP_API_VERSION,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS] = { NULL, };
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+e_source_endeavour_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ESourceEndeavour *self = E_SOURCE_ENDEAVOUR (object);
+
+ switch (prop_id)
+ {
+ case PROP_API_VERSION:
+ g_value_set_uint (value, self->api_version);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+e_source_endeavour_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ESourceEndeavour *self = E_SOURCE_ENDEAVOUR (object);
+
+ switch (prop_id)
+ {
+ case PROP_API_VERSION:
+ self->api_version = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+e_source_endeavour_class_init (ESourceEndeavourClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ESourceExtensionClass *extension_class = E_SOURCE_EXTENSION_CLASS (klass);
+
+ object_class->get_property = e_source_endeavour_get_property;
+ object_class->set_property = e_source_endeavour_set_property;
+
+ extension_class->name = E_SOURCE_EXTENSION_ENDEAVOUR;
+
+ properties[PROP_API_VERSION] = g_param_spec_uint ("api-version",
+ "API Version",
+ "API Version",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE | E_SOURCE_PARAM_SETTING | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+e_source_endeavour_init (ESourceEndeavour *self)
+{
+ self->api_version = 0;
+}
+
+guint
+e_source_endeavour_get_api_version (ESourceEndeavour *self)
+{
+ g_return_val_if_fail (E_IS_SOURCE_ENDEAVOUR (self), 0);
+
+ return self->api_version;
+}
+
+void
+e_source_endeavour_set_api_version (ESourceEndeavour *self,
+ guint api_version)
+{
+ g_return_if_fail (E_IS_SOURCE_ENDEAVOUR (self));
+
+ e_source_extension_property_lock (E_SOURCE_EXTENSION (self));
+ self->api_version = api_version;
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_API_VERSION]);
+}
diff --git a/src/plugins/eds/e-source-endeavour.h b/src/plugins/eds/e-source-endeavour.h
new file mode 100644
index 0000000..ca70c18
--- /dev/null
+++ b/src/plugins/eds/e-source-endeavour.h
@@ -0,0 +1,35 @@
+/* gtd-task-list-eds.h
+ *
+ * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "gtd-eds.h"
+
+G_BEGIN_DECLS
+
+#define E_SOURCE_EXTENSION_ENDEAVOUR "Endeavour"
+
+#define E_TYPE_SOURCE_ENDEAVOUR (e_source_endeavour_get_type())
+G_DECLARE_FINAL_TYPE (ESourceEndeavour, e_source_endeavour, E, SOURCE_ENDEAVOUR, ESourceExtension)
+
+guint e_source_endeavour_get_api_version (ESourceEndeavour *self);
+
+void e_source_endeavour_set_api_version (ESourceEndeavour *self,
+ guint api_version);
+
+G_END_DECLS
diff --git a/src/plugins/eds/eds-plugin.c b/src/plugins/eds/eds-plugin.c
new file mode 100644
index 0000000..df172d0
--- /dev/null
+++ b/src/plugins/eds/eds-plugin.c
@@ -0,0 +1,30 @@
+/* eds-plugin.c
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "endeavour.h"
+#include "gtd-plugin-eds.h"
+
+G_MODULE_EXPORT void
+gtd_plugin_eds_register_types (PeasObjectModule *module)
+{
+ peas_object_module_register_extension_type (module,
+ GTD_TYPE_ACTIVATABLE,
+ GTD_TYPE_PLUGIN_EDS);
+}
diff --git a/src/plugins/eds/eds.gresource.xml b/src/plugins/eds/eds.gresource.xml
new file mode 100644
index 0000000..4b578f9
--- /dev/null
+++ b/src/plugins/eds/eds.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/todo/plugins/eds">
+ <file>eds.plugin</file>
+ </gresource>
+</gresources>
diff --git a/src/plugins/eds/eds.plugin b/src/plugins/eds/eds.plugin
new file mode 100644
index 0000000..b7f28cf
--- /dev/null
+++ b/src/plugins/eds/eds.plugin
@@ -0,0 +1,14 @@
+[Plugin]
+Name = Core
+Module = eds
+Description = Evolution-data-server plugin for Endeavour
+Version = @VERSION@
+Authors = Georges Basile Stavracas Neto <gbsneto@gnome.org>
+Copyright = Copyleft © The Endeavour maintainers
+Website = https://wiki.gnome.org/Apps/Todo
+Builtin = true
+Hidden = true
+License = GPL
+Loader = C
+Embedded = gtd_plugin_eds_register_types
+Depends =
diff --git a/src/plugins/eds/gtd-eds-autoptr.h b/src/plugins/eds/gtd-eds-autoptr.h
new file mode 100644
index 0000000..99c6129
--- /dev/null
+++ b/src/plugins/eds/gtd-eds-autoptr.h
@@ -0,0 +1,27 @@
+/* gtd-eds-autoptr.h
+ *
+ * Copyright 2018-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "gtd-eds.h"
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (ECalComponent, g_object_unref);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (ECalComponentId, e_cal_component_id_free);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (ECalClient, g_object_unref);
diff --git a/src/plugins/eds/gtd-eds.h b/src/plugins/eds/gtd-eds.h
new file mode 100644
index 0000000..a48827c
--- /dev/null
+++ b/src/plugins/eds/gtd-eds.h
@@ -0,0 +1,32 @@
+/* gtd-eds.h
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib.h>
+
+#define HANDLE_LIBICAL_MEMORY
+#define EDS_DISABLE_DEPRECATED
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+#include <libecal/libecal.h>
+#include <libedataserver/libedataserver.h>
+
+G_GNUC_END_IGNORE_DEPRECATIONS
diff --git a/src/plugins/eds/gtd-plugin-eds.c b/src/plugins/eds/gtd-plugin-eds.c
new file mode 100644
index 0000000..6200916
--- /dev/null
+++ b/src/plugins/eds/gtd-plugin-eds.c
@@ -0,0 +1,323 @@
+/* gtd-plugin-eds.c
+ *
+ * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "GtdPluginEds"
+
+#include "gtd-plugin-eds.h"
+#include "gtd-provider-goa.h"
+#include "gtd-provider-local.h"
+
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+/**
+ * The #GtdPluginEds is a class that loads all the
+ * essential providers of Endeavour.
+ *
+ * It basically loads #ESourceRegistry which provides
+ * #GtdProviderLocal. Immediately after that, it loads
+ * #GoaClient which provides one #GtdProviderGoa per
+ * supported account.
+ *
+ * The currently supported Online Accounts are Google,
+ * ownCloud and Microsoft Exchange ones.
+ */
+
+struct _GtdPluginEds
+{
+ GObject parent;
+
+ ESourceRegistry *registry;
+
+ /* Providers */
+ GList *providers;
+};
+
+enum
+{
+ PROP_0,
+ PROP_PREFERENCES_PANEL,
+ LAST_PROP
+};
+
+const gchar *supported_accounts[] = {
+ "exchange",
+ "google",
+ "owncloud",
+ NULL
+};
+
+static void gtd_activatable_iface_init (GtdActivatableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtdPluginEds, gtd_plugin_eds, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTD_TYPE_ACTIVATABLE, gtd_activatable_iface_init))
+
+/*
+ * GtdActivatable interface implementation
+ */
+static void
+gtd_plugin_eds_activate (GtdActivatable *activatable)
+{
+ ;
+}
+
+static void
+gtd_plugin_eds_deactivate (GtdActivatable *activatable)
+{
+ ;
+}
+
+static GtkWidget*
+gtd_plugin_eds_get_preferences_panel (GtdActivatable *activatable)
+{
+ return NULL;
+}
+
+static void
+gtd_activatable_iface_init (GtdActivatableInterface *iface)
+{
+ iface->activate = gtd_plugin_eds_activate;
+ iface->deactivate = gtd_plugin_eds_deactivate;
+ iface->get_preferences_panel = gtd_plugin_eds_get_preferences_panel;
+}
+
+
+/*
+ * Init
+ */
+
+static void
+gtd_plugin_eds_goa_account_removed_cb (GoaClient *client,
+ GoaObject *object,
+ GtdPluginEds *self)
+{
+ GtdManager *manager;
+ GoaAccount *account;
+ GList *l;
+
+ account = goa_object_peek_account (object);
+ manager = gtd_manager_get_default ();
+
+ if (!g_strv_contains (supported_accounts, goa_account_get_provider_type (account)))
+ return;
+
+ for (l = self->providers; l != NULL; l = l->next)
+ {
+ if (!GTD_IS_PROVIDER_GOA (l->data))
+ continue;
+
+ if (account == gtd_provider_goa_get_account (l->data))
+ {
+ GtdProviderGoa *provider = GTD_PROVIDER_GOA (l->data);
+
+ self->providers = g_list_remove (self->providers, l->data);
+ gtd_manager_add_provider (manager, GTD_PROVIDER (provider));
+ break;
+ }
+ }
+}
+
+static void
+gtd_plugin_eds_goa_account_added_cb (GoaClient *client,
+ GoaObject *object,
+ GtdPluginEds *self)
+{
+ GtdManager *manager;
+ GoaAccount *account;
+
+ account = goa_object_get_account (object);
+ manager = gtd_manager_get_default ();
+
+ if (g_strv_contains (supported_accounts, goa_account_get_provider_type (account)))
+ {
+ GtdProviderGoa *provider;
+
+ provider = gtd_provider_goa_new (self->registry, account);
+
+ self->providers = g_list_append (self->providers, provider);
+ gtd_manager_add_provider (manager, GTD_PROVIDER (provider));
+ }
+}
+
+static void
+gtd_plugin_eds_goa_client_finish_cb (GObject *client,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+ GtdPluginEds *self;
+ GtdManager *manager;
+ GoaClient *goa_client;
+ GList *accounts;
+ GList *l;
+
+ self = GTD_PLUGIN_EDS (user_data);
+ goa_client = goa_client_new_finish (result, &error);
+ manager = gtd_manager_get_default ();
+
+ if (error)
+ {
+ g_warning ("%s: %s: %s",
+ G_STRFUNC,
+ "Error loading GNOME Online Accounts",
+ error->message);
+
+ gtd_manager_emit_error_message (gtd_manager_get_default (),
+ _("Error loading GNOME Online Accounts"),
+ error->message,
+ NULL,
+ NULL);
+ g_clear_error (&error);
+ }
+
+ /* Load each supported GoaAccount into a GtdProviderGoa */
+ accounts = goa_client_get_accounts (goa_client);
+
+ for (l = accounts; l != NULL; l = l->next)
+ {
+ GtdProviderGoa *provider;
+ GoaAccount *account;
+ GoaObject *object;
+
+ object = l->data;
+ account = goa_object_get_account (object);
+
+ if (!g_strv_contains (supported_accounts, goa_account_get_provider_type (account)))
+ {
+ g_object_unref (account);
+ continue;
+ }
+
+ g_debug ("Creating new provider for account '%s'", goa_account_get_identity (account));
+
+ /* Create the new GOA provider */
+ provider = gtd_provider_goa_new (self->registry, account);
+
+ self->providers = g_list_append (self->providers, provider);
+ gtd_manager_add_provider (manager, GTD_PROVIDER (provider));
+
+ g_object_unref (account);
+ }
+
+ /* Connect GoaClient signals */
+ g_signal_connect (goa_client,
+ "account-added",
+ G_CALLBACK (gtd_plugin_eds_goa_account_added_cb),
+ user_data);
+
+ g_signal_connect (goa_client,
+ "account-removed",
+ G_CALLBACK (gtd_plugin_eds_goa_account_removed_cb),
+ user_data);
+
+ g_list_free_full (accounts, g_object_unref);
+}
+
+
+
+static void
+gtd_plugin_eds_source_registry_finish_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtdPluginEds *self = GTD_PLUGIN_EDS (user_data);
+ GtdProviderLocal *provider;
+ ESourceRegistry *registry;
+ GtdManager *manager;
+ GError *error = NULL;
+
+ manager = gtd_manager_get_default ();
+ registry = e_source_registry_new_finish (result, &error);
+ self->registry = registry;
+
+ /* Abort on error */
+ if (error)
+ {
+ g_warning ("%s: %s",
+ "Error loading Evolution-Data-Server backend",
+ error->message);
+
+ g_clear_error (&error);
+ return;
+ }
+
+ /* Load the local provider */
+ provider = gtd_provider_local_new (registry);
+
+ self->providers = g_list_append (self->providers, provider);
+ gtd_manager_add_provider (manager, GTD_PROVIDER (provider));
+
+ /* We only start loading Goa accounts after
+ * ESourceRegistry is get, since it'd be way
+ * too hard to synchronize these two asynchronous
+ * calls.
+ */
+ goa_client_new (NULL,
+ (GAsyncReadyCallback) gtd_plugin_eds_goa_client_finish_cb,
+ self);
+}
+
+static void
+gtd_plugin_eds_finalize (GObject *object)
+{
+ GtdPluginEds *self = (GtdPluginEds *)object;
+
+ g_list_free_full (self->providers, g_object_unref);
+ self->providers = NULL;
+
+ G_OBJECT_CLASS (gtd_plugin_eds_parent_class)->finalize (object);
+}
+
+static void
+gtd_plugin_eds_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ case PROP_PREFERENCES_PANEL:
+ g_value_set_object (value, NULL);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_plugin_eds_class_init (GtdPluginEdsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_plugin_eds_finalize;
+ object_class->get_property = gtd_plugin_eds_get_property;
+
+ g_object_class_override_property (object_class,
+ PROP_PREFERENCES_PANEL,
+ "preferences-panel");
+}
+
+static void
+gtd_plugin_eds_init (GtdPluginEds *self)
+{
+ /* load the source registry */
+ e_source_registry_new (NULL,
+ (GAsyncReadyCallback) gtd_plugin_eds_source_registry_finish_cb,
+ self);
+}
diff --git a/src/plugins/eds/gtd-plugin-eds.h b/src/plugins/eds/gtd-plugin-eds.h
new file mode 100644
index 0000000..6eea2c4
--- /dev/null
+++ b/src/plugins/eds/gtd-plugin-eds.h
@@ -0,0 +1,28 @@
+/* gtd-eds-plugin.h
+ *
+ * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "endeavour.h"
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_PLUGIN_EDS (gtd_plugin_eds_get_type())
+G_DECLARE_FINAL_TYPE (GtdPluginEds, gtd_plugin_eds, GTD, PLUGIN_EDS, PeasExtensionBase)
+
+G_END_DECLS
diff --git a/src/plugins/eds/gtd-provider-eds.c b/src/plugins/eds/gtd-provider-eds.c
new file mode 100644
index 0000000..d46d70e
--- /dev/null
+++ b/src/plugins/eds/gtd-provider-eds.c
@@ -0,0 +1,1157 @@
+/* gtd-provider-eds.c
+ *
+ * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "GtdProviderEds"
+
+#include "gtd-debug.h"
+#include "gtd-eds-autoptr.h"
+#include "gtd-provider-eds.h"
+#include "gtd-task-eds.h"
+#include "gtd-task-list-eds.h"
+
+#include <glib/gi18n.h>
+
+/**
+ * #GtdProviderEds is the base class of #GtdProviderLocal
+ * and #GtdProviderGoa. It provides the common functionality
+ * shared between these two providers.
+ *
+ * The subclasses basically have to implement GtdProviderEds->should_load_source
+ * which decides whether a given #ESource should be loaded (and added to the
+ * sources list) or not. #GtdProviderLocal for example would filter out
+ * sources whose backend is not "local".
+ */
+
+typedef struct
+{
+ GtdTaskList *list;
+ GDateTime *due_date;
+ gchar *title;
+ ESource *source;
+
+ /* Update Task */
+ ECalComponent *component;
+ GtdTask *task;
+} AsyncData;
+
+typedef struct
+{
+ GHashTable *task_lists;
+
+ ESourceRegistry *source_registry;
+
+ GCancellable *cancellable;
+
+ gint lazy_load_id;
+} GtdProviderEdsPrivate;
+
+
+static void gtd_provider_iface_init (GtdProviderInterface *iface);
+
+
+G_DEFINE_TYPE_WITH_CODE (GtdProviderEds, gtd_provider_eds, GTD_TYPE_OBJECT,
+ G_ADD_PRIVATE (GtdProviderEds)
+ G_IMPLEMENT_INTERFACE (GTD_TYPE_PROVIDER, gtd_provider_iface_init))
+
+
+enum
+{
+ PROP_0,
+ PROP_ENABLED,
+ PROP_DESCRIPTION,
+ PROP_ICON,
+ PROP_ID,
+ PROP_NAME,
+ PROP_PROVIDER_TYPE,
+ PROP_REGISTRY,
+ N_PROPS
+};
+
+
+/*
+ * Auxiliary methods
+ */
+
+static void
+async_data_free (gpointer data)
+{
+ AsyncData *async_data = data;
+
+ g_clear_pointer (&async_data->due_date, g_date_time_unref);
+ g_clear_pointer (&async_data->title, g_free);
+ g_clear_object (&async_data->source);
+ g_clear_object (&async_data->list);
+ g_clear_object (&async_data->task);
+ g_clear_object (&async_data->component);
+ g_free (async_data);
+}
+
+static void
+set_default_list (GtdProviderEds *self,
+ GtdTaskList *list)
+{
+ GtdProviderEdsPrivate *priv;
+ GtdManager *manager;
+ ESource *source;
+
+ priv = gtd_provider_eds_get_instance_private (self);
+ source = gtd_task_list_eds_get_source (GTD_TASK_LIST_EDS (list));
+ manager = gtd_manager_get_default ();
+
+ e_source_registry_set_default_task_list (priv->source_registry, source);
+
+ if (gtd_manager_get_default_provider (manager) != (GtdProvider*) self)
+ gtd_manager_set_default_provider (manager, GTD_PROVIDER (self));
+}
+
+static void
+ensure_offline_sync (GtdProviderEds *self,
+ ESource *source)
+{
+ GtdProviderEdsPrivate *priv = gtd_provider_eds_get_instance_private (self);
+ ESourceOffline *extension;
+
+ extension = e_source_get_extension (source, E_SOURCE_EXTENSION_OFFLINE);
+ e_source_offline_set_stay_synchronized (extension, TRUE);
+
+ e_source_registry_commit_source (priv->source_registry, source, NULL, NULL, NULL);
+}
+
+
+/*
+ * Callbacks
+ */
+
+static void
+on_task_list_eds_loaded_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+ GtdProviderEdsPrivate *priv;
+ GtdProviderEds *self;
+ GtdTaskListEds *list;
+ ESource *source;
+
+ self = GTD_PROVIDER_EDS (user_data);
+ priv = gtd_provider_eds_get_instance_private (self);
+ list = gtd_task_list_eds_new_finish (result, &error);
+
+ if (error)
+ {
+ g_warning ("Error creating task list: %s", error->message);
+ return;
+ }
+
+ source = gtd_task_list_eds_get_source (list);
+
+ g_hash_table_insert (priv->task_lists, e_source_dup_uid (source), g_object_ref (list));
+ g_object_set_data (G_OBJECT (source), "task-list", list);
+
+ g_debug ("Task list '%s' successfully connected", e_source_get_display_name (source));
+}
+
+static void
+on_client_connected_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+ GtdProviderEdsPrivate *priv;
+ GtdProviderEds *self;
+ ECalClient *client;
+ ESource *source;
+
+ self = GTD_PROVIDER_EDS (user_data);
+ priv = gtd_provider_eds_get_instance_private (self);
+ source = e_client_get_source (E_CLIENT (source_object));
+ client = E_CAL_CLIENT (e_cal_client_connect_finish (result, &error));
+
+ if (error)
+ {
+ g_warning ("Failed to connect to task list '%s': %s", e_source_get_uid (source), error->message);
+
+ gtd_manager_emit_error_message (gtd_manager_get_default (),
+ _("Failed to connect to task list"),
+ error->message,
+ NULL,
+ NULL);
+ gtd_object_pop_loading (GTD_OBJECT (self));
+ return;
+ }
+
+ ensure_offline_sync (self, source);
+
+ /* creates a new task list */
+ gtd_task_list_eds_new (GTD_PROVIDER (self),
+ source,
+ client,
+ on_task_list_eds_loaded_cb,
+ priv->cancellable,
+ self);
+}
+
+static void
+on_source_added_cb (GtdProviderEds *provider,
+ ESource *source)
+{
+ /* Don't load the source if it's not a tasklist */
+ if (!e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST) ||
+ !GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->should_load_source (provider, source))
+ {
+ GTD_TRACE_MSG ("Ignoring source %s (%s)",
+ e_source_get_display_name (source),
+ e_source_get_uid (source));
+ return;
+ }
+
+ /*
+ * The pop_loading() is actually emited by GtdTaskListEds, after the
+ * ECalClientView sends the :complete signal.
+ */
+ gtd_object_push_loading (GTD_OBJECT (provider));
+ gtd_object_push_loading (GTD_OBJECT (gtd_manager_get_default ()));
+
+ e_cal_client_connect (source,
+ E_CAL_CLIENT_SOURCE_TYPE_TASKS,
+ 15, /* seconds to wait */
+ NULL,
+ on_client_connected_cb,
+ provider);
+}
+
+static void
+on_source_removed_cb (GtdProviderEds *provider,
+ ESource *source)
+{
+ GtdProviderEdsPrivate *priv;
+ GtdTaskList *list;
+
+ GTD_ENTRY;
+
+ priv = gtd_provider_eds_get_instance_private (provider);
+ list = g_object_get_data (G_OBJECT (source), "task-list");
+
+ if (!g_hash_table_remove (priv->task_lists, gtd_object_get_uid (GTD_OBJECT (list))))
+ GTD_RETURN ();
+
+ /*
+ * Since all subclasses will have this signal given that they
+ * are all GtdProvider implementations, it's not that bad
+ * to let it stay here.
+ */
+ g_signal_emit_by_name (provider, "list-removed", list);
+
+ GTD_EXIT;
+}
+
+static void
+on_source_refreshed_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtdProviderEdsPrivate *priv = gtd_provider_eds_get_instance_private (user_data);
+ g_autoptr (GError) error = NULL;
+
+ GTD_ENTRY;
+
+ e_source_registry_refresh_backend_finish (priv->source_registry, result, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Error refreshing source: %s", error->message);
+ GTD_RETURN ();
+ }
+
+ GTD_EXIT;
+}
+
+static void
+create_task_in_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr (ECalComponent) component = NULL;
+ g_autoptr (GError) error = NULL;
+ g_autofree gchar *new_uid = NULL;
+ ECalComponentText *new_summary;
+ GtdTaskListEds *tasklist;
+ ECalClient *client;
+ AsyncData *data;
+ GtdTask *new_task;
+
+ GTD_ENTRY;
+
+ data = task_data;
+ tasklist = GTD_TASK_LIST_EDS (data->list);
+ client = gtd_task_list_eds_get_client (tasklist);
+
+ /* Create the new task */
+ component = e_cal_component_new ();
+ e_cal_component_set_new_vtype (component, E_CAL_COMPONENT_TODO);
+
+ new_summary = e_cal_component_text_new (data->title, NULL);
+ e_cal_component_set_summary (component, new_summary);
+
+ if (data->due_date)
+ {
+ ECalComponentDateTime *comp_dt;
+ ICalTime *idt;
+
+ idt = i_cal_time_new_null_time ();
+ i_cal_time_set_date (idt,
+ g_date_time_get_year (data->due_date),
+ g_date_time_get_month (data->due_date),
+ g_date_time_get_day_of_month (data->due_date));
+ i_cal_time_set_time (idt,
+ g_date_time_get_hour (data->due_date),
+ g_date_time_get_minute (data->due_date),
+ g_date_time_get_seconds (data->due_date));
+ i_cal_time_set_is_date (idt,
+ i_cal_time_get_hour (idt) == 0 &&
+ i_cal_time_get_minute (idt) == 0 &&
+ i_cal_time_get_second (idt) == 0);
+
+ comp_dt = e_cal_component_datetime_new_take (idt, g_strdup ("UTC"));
+ e_cal_component_set_due (component, comp_dt);
+ e_cal_component_commit_sequence (component);
+
+ e_cal_component_datetime_free (comp_dt);
+ }
+
+ e_cal_client_create_object_sync (client,
+ e_cal_component_get_icalcomponent (component),
+ E_CAL_OPERATION_FLAG_NONE,
+ &new_uid,
+ cancellable,
+ &error);
+
+ e_cal_component_text_free (new_summary);
+
+ if (error)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ new_task = gtd_task_eds_new (component);
+ gtd_task_set_position (new_task, g_list_model_get_n_items (G_LIST_MODEL (tasklist)));
+
+ /*
+ * In the case the task UID changes because of creation proccess,
+ * reapply it to the task.
+ */
+ if (new_uid)
+ gtd_object_set_uid (GTD_OBJECT (new_task), new_uid);
+
+ /* Effectively apply the updated component */
+ gtd_task_eds_apply (GTD_TASK_EDS (new_task));
+
+ g_task_return_pointer (task, g_object_ref (new_task), g_object_unref);
+
+ GTD_EXIT;
+}
+
+static void
+update_task_in_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr (GError) error = NULL;
+ GtdTaskListEds *tasklist;
+ ECalClient *client;
+ AsyncData *data;
+
+ GTD_ENTRY;
+
+ data = task_data;
+ tasklist = GTD_TASK_LIST_EDS (gtd_task_get_list (data->task));
+ client = gtd_task_list_eds_get_client (tasklist);
+
+ e_cal_client_modify_object_sync (client,
+ e_cal_component_get_icalcomponent (data->component),
+ E_CAL_OBJ_MOD_THIS,
+ E_CAL_OPERATION_FLAG_NONE,
+ cancellable,
+ &error);
+
+
+ if (error)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ GTD_RETURN ();
+ }
+
+ g_task_return_boolean (task, TRUE);
+
+ GTD_EXIT;
+}
+
+static void
+remove_task_in_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr (ECalComponentId) id = NULL;
+ g_autoptr (GError) error = NULL;
+ GtdTaskListEds *tasklist;
+ ECalClient *client;
+ AsyncData *data;
+
+ GTD_ENTRY;
+
+ data = task_data;
+ tasklist = GTD_TASK_LIST_EDS (gtd_task_get_list (data->task));
+ client = gtd_task_list_eds_get_client (tasklist);
+ id = e_cal_component_get_id (data->component);
+
+ e_cal_client_remove_object_sync (client,
+ e_cal_component_id_get_uid (id),
+ e_cal_component_id_get_rid (id),
+ E_CAL_OBJ_MOD_THIS,
+ E_CAL_OPERATION_FLAG_NONE,
+ cancellable,
+ &error);
+
+
+ if (error)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ GTD_RETURN ();
+ }
+
+ g_task_return_boolean (task, TRUE);
+
+ GTD_EXIT;
+}
+
+static void
+create_or_update_task_list_in_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GtdProviderEdsPrivate *priv;
+ g_autoptr (GError) error = NULL;
+ GtdProviderEds *self;
+ AsyncData *data;
+
+ GTD_ENTRY;
+
+ data = task_data;
+ self = GTD_PROVIDER_EDS (source_object);
+ priv = gtd_provider_eds_get_instance_private (self);
+
+ e_source_registry_commit_source_sync (priv->source_registry,
+ data->source,
+ cancellable,
+ &error);
+
+ if (error)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ GTD_RETURN ();
+ }
+
+ g_task_return_boolean (task, TRUE);
+
+ GTD_EXIT;
+}
+
+
+static void
+remove_task_list_in_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr (GError) error = NULL;
+ AsyncData *data;
+
+ GTD_ENTRY;
+
+ data = task_data;
+
+ e_source_remove_sync (data->source, cancellable, &error);
+
+ if (error)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ GTD_RETURN ();
+ }
+
+ g_task_return_boolean (task, TRUE);
+
+ GTD_EXIT;
+}
+
+
+/*
+ * GtdProvider iface
+ */
+
+static const gchar*
+gtd_provider_eds_get_id (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER_EDS (provider), NULL);
+
+ return GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->get_id (GTD_PROVIDER_EDS (provider));
+}
+
+static const gchar*
+gtd_provider_eds_get_name (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER_EDS (provider), NULL);
+
+ return GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->get_name (GTD_PROVIDER_EDS (provider));
+}
+
+static const gchar*
+gtd_provider_eds_get_provider_type (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER_EDS (provider), NULL);
+
+ return GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->get_provider_type (GTD_PROVIDER_EDS (provider));
+}
+
+static const gchar*
+gtd_provider_eds_get_description (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER_EDS (provider), NULL);
+
+ return GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->get_description (GTD_PROVIDER_EDS (provider));
+}
+
+
+static gboolean
+gtd_provider_eds_get_enabled (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER_EDS (provider), FALSE);
+
+ return GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->get_enabled (GTD_PROVIDER_EDS (provider));
+}
+
+static void
+gtd_provider_eds_refresh (GtdProvider *provider)
+{
+ g_autoptr (GHashTable) collections = NULL;
+ GtdProviderEdsPrivate *priv;
+ GtdProviderEds *self;
+ GHashTableIter iter;
+ GtdTaskListEds *list;
+
+ GTD_ENTRY;
+
+ g_return_if_fail (GTD_IS_PROVIDER_EDS (provider));
+
+ self = GTD_PROVIDER_EDS (provider);
+ priv = gtd_provider_eds_get_instance_private (self);
+ collections = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ g_hash_table_iter_init (&iter, priv->task_lists);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &list))
+ {
+ g_autoptr (ESource) collection = NULL;
+ ESource *source;
+
+ source = gtd_task_list_eds_get_source (list);
+ collection = e_source_registry_find_extension (priv->source_registry,
+ source,
+ E_SOURCE_EXTENSION_COLLECTION);
+
+ if (!collection || g_hash_table_contains (collections, collection))
+ continue;
+
+ GTD_TRACE_MSG ("Refreshing collection %s", e_source_get_uid (collection));
+
+ e_source_registry_refresh_backend (priv->source_registry,
+ e_source_get_uid (collection),
+ priv->cancellable,
+ on_source_refreshed_cb,
+ g_object_ref (self));
+
+ g_hash_table_add (collections, collection);
+ }
+
+ GTD_EXIT;
+}
+
+static GIcon*
+gtd_provider_eds_get_icon (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER_EDS (provider), NULL);
+
+ return GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->get_icon (GTD_PROVIDER_EDS (provider));
+}
+
+static void
+gtd_provider_eds_create_task (GtdProvider *provider,
+ GtdTaskList *list,
+ const gchar *title,
+ GDateTime *due_date,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = NULL;
+ GtdProviderEds *self;
+ AsyncData *data;
+
+ g_return_if_fail (GTD_IS_TASK_LIST_EDS (list));
+
+ GTD_ENTRY;
+
+ self = GTD_PROVIDER_EDS (provider);
+
+ data = g_new0 (AsyncData, 1);
+ data->list = g_object_ref (list);
+ data->title = g_strdup (title);
+ data->due_date = due_date ? g_date_time_ref (due_date) : NULL;
+
+ gtd_object_push_loading (GTD_OBJECT (self));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gtd_provider_eds_create_task);
+ g_task_set_task_data (task, data, async_data_free);
+ g_task_run_in_thread (task, create_task_in_thread_cb);
+
+ GTD_EXIT;
+}
+
+static GtdTask*
+gtd_provider_eds_create_task_finish (GtdProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_autoptr (GtdTask) new_task = NULL;
+ GtdProviderEds *self;
+ GtdTaskList *list;
+ AsyncData *data;
+
+ GTD_ENTRY;
+
+ self = GTD_PROVIDER_EDS (provider);
+ data = g_task_get_task_data (G_TASK (result));
+ list = data->list;
+
+ gtd_object_pop_loading (GTD_OBJECT (self));
+
+ new_task = g_task_propagate_pointer (G_TASK (result), error);
+
+ if (new_task)
+ {
+ gtd_task_set_list (new_task, list);
+ gtd_task_list_add_task (list, new_task);
+ set_default_list (self, list);
+ }
+
+ GTD_RETURN (new_task);
+}
+
+static void
+gtd_provider_eds_update_task (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) gtask = NULL;
+ ECalComponent *component;
+ AsyncData *data;
+
+ GTD_ENTRY;
+
+ g_return_if_fail (GTD_IS_TASK (task));
+ g_return_if_fail (GTD_IS_TASK_LIST_EDS (gtd_task_get_list (task)));
+
+ component = gtd_task_eds_get_component (GTD_TASK_EDS (task));
+
+ e_cal_component_commit_sequence (component);
+
+ /* The task is not ready until we finish the operation */
+ gtd_object_push_loading (GTD_OBJECT (task));
+ gtd_object_push_loading (GTD_OBJECT (provider));
+
+ data = g_new0 (AsyncData, 1);
+ data->task = g_object_ref (task);
+ data->component = e_cal_component_clone (component);
+
+ gtask = g_task_new (provider, cancellable, callback, user_data);
+ g_task_set_source_tag (gtask, gtd_provider_eds_update_task);
+ g_task_set_task_data (gtask, data, async_data_free);
+ g_task_run_in_thread (gtask, update_task_in_thread_cb);
+
+ GTD_EXIT;
+}
+
+static gboolean
+gtd_provider_eds_update_task_finish (GtdProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ GtdProviderEds *self;
+ AsyncData *data;
+ GtdTask *task;
+
+ GTD_ENTRY;
+
+ self = GTD_PROVIDER_EDS (provider);
+ data = g_task_get_task_data (G_TASK (result));
+ task = data->task;
+
+ gtd_object_pop_loading (GTD_OBJECT (self));
+ gtd_object_pop_loading (GTD_OBJECT (task));
+
+ if (!g_task_propagate_boolean (G_TASK (result), error))
+ {
+ gtd_task_eds_revert (GTD_TASK_EDS (task));
+ GTD_RETURN (FALSE);
+ }
+
+ gtd_task_eds_apply (GTD_TASK_EDS (task));
+ gtd_task_list_update_task (gtd_task_get_list (task), task);
+
+ GTD_RETURN (TRUE);
+}
+
+static void
+gtd_provider_eds_remove_task (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) gtask = NULL;
+ ECalComponent *component;
+ AsyncData *data;
+
+ GTD_ENTRY;
+
+ g_return_if_fail (GTD_IS_TASK (task));
+ g_return_if_fail (GTD_IS_TASK_LIST_EDS (gtd_task_get_list (task)));
+
+ component = gtd_task_eds_get_component (GTD_TASK_EDS (task));
+
+ gtd_object_push_loading (GTD_OBJECT (provider));
+
+ data = g_new0 (AsyncData, 1);
+ data->task = g_object_ref (task);
+ data->component = e_cal_component_clone (component);
+
+ gtask = g_task_new (provider, cancellable, callback, user_data);
+ g_task_set_source_tag (gtask, gtd_provider_eds_remove_task);
+ g_task_set_task_data (gtask, data, async_data_free);
+ g_task_run_in_thread (gtask, remove_task_in_thread_cb);
+
+ GTD_EXIT;
+}
+
+static gboolean
+gtd_provider_eds_remove_task_finish (GtdProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ GTD_ENTRY;
+
+ gtd_object_pop_loading (GTD_OBJECT (provider));
+
+ GTD_RETURN (g_task_propagate_boolean (G_TASK (result), error));
+}
+
+static void
+gtd_provider_eds_create_task_list (GtdProvider *provider,
+ const gchar *name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = NULL;
+ GtdProviderEds *self;
+ AsyncData *data;
+ ESource *source;
+
+ GTD_ENTRY;
+
+ self = GTD_PROVIDER_EDS (provider);
+ source = NULL;
+
+ /* Create an ESource */
+ if (!GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->create_source) {
+ g_debug ("Can't create task list: not supported by %s", G_OBJECT_TYPE_NAME (provider));
+ return;
+ }
+
+ source = GTD_PROVIDER_EDS_CLASS (G_OBJECT_GET_CLASS (provider))->create_source (self);
+ if (!source) {
+ g_debug ("Can't create task list: create_source() returned NULL");
+ return;
+ }
+
+ /* EDS properties */
+ e_source_set_display_name (source, name);
+
+ data = g_new0 (AsyncData, 1);
+ data->title = g_strdup (name);
+ data->source = g_object_ref (source);
+
+ gtd_object_push_loading (GTD_OBJECT (provider));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gtd_provider_eds_create_task_list);
+ g_task_set_task_data (task, data, async_data_free);
+ g_task_run_in_thread (task, create_or_update_task_list_in_thread_cb);
+
+ GTD_EXIT;
+}
+
+static gboolean
+gtd_provider_eds_create_task_list_finish (GtdProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ GtdProviderEds *self;
+
+ GTD_ENTRY;
+
+ self = GTD_PROVIDER_EDS (provider);
+ gtd_object_pop_loading (GTD_OBJECT (self));
+
+ GTD_RETURN (g_task_propagate_boolean (G_TASK (result), error));
+}
+
+static void
+gtd_provider_eds_update_task_list (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = NULL;
+ AsyncData *data;
+ ESource *source;
+
+ GTD_ENTRY;
+
+ g_assert (GTD_IS_TASK_LIST_EDS (list));
+ g_assert (gtd_task_list_eds_get_source (GTD_TASK_LIST_EDS (list)) != NULL);
+
+ source = gtd_task_list_eds_get_source (GTD_TASK_LIST_EDS (list));
+
+ gtd_object_push_loading (GTD_OBJECT (provider));
+ gtd_object_push_loading (GTD_OBJECT (list));
+
+ data = g_new0 (AsyncData, 1);
+ data->list = g_object_ref (list);
+ data->source = g_object_ref (source);
+
+ task = g_task_new (provider, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gtd_provider_eds_update_task_list);
+ g_task_set_task_data (task, data, async_data_free);
+ g_task_run_in_thread (task, create_or_update_task_list_in_thread_cb);
+
+ GTD_EXIT;
+}
+
+static gboolean
+gtd_provider_eds_update_task_list_finish (GtdProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ GtdProviderEds *self;
+ AsyncData *data;
+
+ GTD_ENTRY;
+
+ self = GTD_PROVIDER_EDS (provider);
+ data = g_task_get_task_data (G_TASK (result));
+
+ gtd_object_pop_loading (GTD_OBJECT (data->list));
+ gtd_object_pop_loading (GTD_OBJECT (self));
+
+ g_signal_emit_by_name (self, "list-changed", data->list);
+
+ GTD_RETURN (g_task_propagate_boolean (G_TASK (result), error));
+}
+
+static void
+gtd_provider_eds_remove_task_list (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) gtask = NULL;
+ AsyncData *data;
+ ESource *source;
+
+ GTD_ENTRY;
+
+ g_assert (GTD_IS_TASK_LIST_EDS (list));
+ g_assert (gtd_task_list_eds_get_source (GTD_TASK_LIST_EDS (list)) != NULL);
+
+ source = gtd_task_list_eds_get_source (GTD_TASK_LIST_EDS (list));
+
+ gtd_object_push_loading (GTD_OBJECT (provider));
+
+ data = g_new0 (AsyncData, 1);
+ data->source = g_object_ref (source);
+
+ gtask = g_task_new (provider, cancellable, callback, user_data);
+ g_task_set_source_tag (gtask, gtd_provider_eds_remove_task_list);
+ g_task_set_task_data (gtask, data, async_data_free);
+ g_task_run_in_thread (gtask, remove_task_list_in_thread_cb);
+
+ GTD_EXIT;
+}
+
+static gboolean
+gtd_provider_eds_remove_task_list_finish (GtdProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ GTD_ENTRY;
+
+ gtd_object_pop_loading (GTD_OBJECT (provider));
+
+ GTD_RETURN (g_task_propagate_boolean (G_TASK (result), error));
+}
+
+static GList*
+gtd_provider_eds_get_task_lists (GtdProvider *provider)
+{
+ GtdProviderEdsPrivate *priv = gtd_provider_eds_get_instance_private (GTD_PROVIDER_EDS (provider));
+
+ return g_hash_table_get_values (priv->task_lists);
+}
+
+static GtdTaskList*
+gtd_provider_eds_get_inbox (GtdProvider *provider)
+{
+ GtdProviderEdsPrivate *priv = gtd_provider_eds_get_instance_private (GTD_PROVIDER_EDS (provider));
+
+ return g_hash_table_lookup (priv->task_lists, GTD_PROVIDER_EDS_INBOX_ID);
+}
+
+static void
+gtd_provider_iface_init (GtdProviderInterface *iface)
+{
+ iface->get_id = gtd_provider_eds_get_id;
+ iface->get_name = gtd_provider_eds_get_name;
+ iface->get_provider_type = gtd_provider_eds_get_provider_type;
+ iface->get_description = gtd_provider_eds_get_description;
+ iface->get_enabled = gtd_provider_eds_get_enabled;
+ iface->refresh = gtd_provider_eds_refresh;
+ iface->get_icon = gtd_provider_eds_get_icon;
+ iface->create_task = gtd_provider_eds_create_task;
+ iface->create_task_finish = gtd_provider_eds_create_task_finish;
+ iface->update_task = gtd_provider_eds_update_task;
+ iface->update_task_finish = gtd_provider_eds_update_task_finish;
+ iface->remove_task = gtd_provider_eds_remove_task;
+ iface->remove_task_finish = gtd_provider_eds_remove_task_finish;
+ iface->create_task_list = gtd_provider_eds_create_task_list;
+ iface->create_task_list_finish = gtd_provider_eds_create_task_list_finish;
+ iface->update_task_list = gtd_provider_eds_update_task_list;
+ iface->update_task_list_finish = gtd_provider_eds_update_task_list_finish;
+ iface->remove_task_list = gtd_provider_eds_remove_task_list;
+ iface->remove_task_list_finish = gtd_provider_eds_remove_task_list_finish;
+ iface->get_task_lists = gtd_provider_eds_get_task_lists;
+ iface->get_inbox = gtd_provider_eds_get_inbox;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_provider_eds_finalize (GObject *object)
+{
+ GtdProviderEds *self = (GtdProviderEds *)object;
+ GtdProviderEdsPrivate *priv = gtd_provider_eds_get_instance_private (self);
+
+ g_cancellable_cancel (priv->cancellable);
+
+ g_clear_object (&priv->cancellable);
+ g_clear_object (&priv->source_registry);
+ g_clear_pointer (&priv->task_lists, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (gtd_provider_eds_parent_class)->finalize (object);
+}
+
+static void
+gtd_provider_eds_constructed (GObject *object)
+{
+ GtdProviderEdsPrivate *priv;
+ GtdProviderEds *self;
+ g_autoptr (GError) error = NULL;
+ GList *sources;
+ GList *l;
+
+ self = GTD_PROVIDER_EDS (object);
+ priv = gtd_provider_eds_get_instance_private (self);
+
+ if (error)
+ {
+ g_warning ("%s: %s", "Error loading task manager", error->message);
+ return;
+ }
+
+ /* Load task list sources */
+ sources = e_source_registry_list_sources (priv->source_registry, E_SOURCE_EXTENSION_TASK_LIST);
+
+ for (l = sources; l != NULL; l = l->next)
+ on_source_added_cb (self, l->data);
+
+ g_list_free_full (sources, g_object_unref);
+
+ /* listen to the signals, so new sources don't slip by */
+ g_signal_connect_swapped (priv->source_registry,
+ "source-added",
+ G_CALLBACK (on_source_added_cb),
+ self);
+
+ g_signal_connect_swapped (priv->source_registry,
+ "source-removed",
+ G_CALLBACK (on_source_removed_cb),
+ self);
+}
+
+static void
+gtd_provider_eds_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdProvider *provider = GTD_PROVIDER (object);
+ GtdProviderEdsPrivate *priv = gtd_provider_eds_get_instance_private (GTD_PROVIDER_EDS (object));
+
+
+ switch (prop_id)
+ {
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, gtd_provider_eds_get_description (provider));
+ break;
+
+ case PROP_ENABLED:
+ g_value_set_boolean (value, gtd_provider_eds_get_enabled (provider));
+ break;
+
+ case PROP_ICON:
+ g_value_set_object (value, gtd_provider_eds_get_icon (provider));
+ break;
+
+ case PROP_ID:
+ g_value_set_string (value, gtd_provider_eds_get_id (provider));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, gtd_provider_eds_get_name (provider));
+ break;
+
+ case PROP_PROVIDER_TYPE:
+ g_value_set_string (value, gtd_provider_eds_get_provider_type (provider));
+ break;
+
+ case PROP_REGISTRY:
+ g_value_set_object (value, priv->source_registry);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_provider_eds_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdProviderEds *self = GTD_PROVIDER_EDS (object);
+ GtdProviderEdsPrivate *priv = gtd_provider_eds_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_REGISTRY:
+ if (g_set_object (&priv->source_registry, g_value_get_object (value)))
+ g_object_notify (object, "registry");
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_provider_eds_class_init (GtdProviderEdsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_provider_eds_finalize;
+ object_class->constructed = gtd_provider_eds_constructed;
+ object_class->get_property = gtd_provider_eds_get_property;
+ object_class->set_property = gtd_provider_eds_set_property;
+
+ g_object_class_override_property (object_class, PROP_DESCRIPTION, "description");
+ g_object_class_override_property (object_class, PROP_ENABLED, "enabled");
+ g_object_class_override_property (object_class, PROP_ICON, "icon");
+ g_object_class_override_property (object_class, PROP_ID, "id");
+ g_object_class_override_property (object_class, PROP_NAME, "name");
+ g_object_class_override_property (object_class, PROP_PROVIDER_TYPE, "provider-type");
+
+ g_object_class_install_property (object_class,
+ PROP_REGISTRY,
+ g_param_spec_object ("registry",
+ "Source registry",
+ "The EDS source registry object",
+ E_TYPE_SOURCE_REGISTRY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gtd_provider_eds_init (GtdProviderEds *self)
+{
+ GtdProviderEdsPrivate *priv = gtd_provider_eds_get_instance_private (self);
+
+ priv->cancellable = g_cancellable_new ();
+ priv->task_lists = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+GtdProviderEds*
+gtd_provider_eds_new (ESourceRegistry *registry)
+{
+ return g_object_new (GTD_TYPE_PROVIDER_EDS,
+ "registry", registry,
+ NULL);
+}
+
+ESourceRegistry*
+gtd_provider_eds_get_registry (GtdProviderEds *provider)
+{
+ GtdProviderEdsPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_PROVIDER_EDS (provider), NULL);
+
+ priv = gtd_provider_eds_get_instance_private (provider);
+
+ return priv->source_registry;
+}
diff --git a/src/plugins/eds/gtd-provider-eds.h b/src/plugins/eds/gtd-provider-eds.h
new file mode 100644
index 0000000..5ffe4ff
--- /dev/null
+++ b/src/plugins/eds/gtd-provider-eds.h
@@ -0,0 +1,61 @@
+/* gtd-provider-eds.h
+ *
+ * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "endeavour.h"
+
+#include "gtd-eds.h"
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define GTD_PROVIDER_EDS_INBOX_ID "system-task-list"
+
+#define GTD_TYPE_PROVIDER_EDS (gtd_provider_eds_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GtdProviderEds, gtd_provider_eds, GTD, PROVIDER_EDS, GtdObject)
+
+struct _GtdProviderEdsClass
+{
+ GtdObjectClass parent;
+
+ const gchar* (*get_id) (GtdProviderEds *self);
+
+ const gchar* (*get_name) (GtdProviderEds *self);
+
+ const gchar* (*get_provider_type) (GtdProviderEds *self);
+
+ const gchar* (*get_description) (GtdProviderEds *self);
+
+ gboolean (*get_enabled) (GtdProviderEds *self);
+
+ GIcon* (*get_icon) (GtdProviderEds *self);
+
+ ESource* (*create_source) (GtdProviderEds *self);
+
+ gboolean (*should_load_source) (GtdProviderEds *provider,
+ ESource *source);
+};
+
+GtdProviderEds* gtd_provider_eds_new (ESourceRegistry *registry);
+
+ESourceRegistry* gtd_provider_eds_get_registry (GtdProviderEds *local);
+
+G_END_DECLS
diff --git a/src/plugins/eds/gtd-provider-goa.c b/src/plugins/eds/gtd-provider-goa.c
new file mode 100644
index 0000000..05cacd0
--- /dev/null
+++ b/src/plugins/eds/gtd-provider-goa.c
@@ -0,0 +1,262 @@
+/* gtd-provider-goa.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "GtdProviderGoa"
+
+#include "gtd-eds-autoptr.h"
+#include "gtd-provider-eds.h"
+#include "gtd-provider-goa.h"
+
+#include <glib/gi18n.h>
+
+struct _GtdProviderGoa
+{
+ GtdProviderEds parent;
+
+ GoaAccount *account;
+ GIcon *icon;
+
+ gchar *id;
+};
+
+G_DEFINE_TYPE (GtdProviderGoa, gtd_provider_goa, GTD_TYPE_PROVIDER_EDS)
+
+enum
+{
+ PROP_0,
+ PROP_ACCOUNT,
+ N_PROPS
+};
+
+
+/*
+ * GtdProviderEds overrides
+ */
+
+static const gchar*
+gtd_provider_goa_get_id (GtdProviderEds *provider)
+{
+ GtdProviderGoa *self = GTD_PROVIDER_GOA (provider);
+
+ return self->id;
+}
+
+static const gchar*
+gtd_provider_goa_get_name (GtdProviderEds *provider)
+{
+ GtdProviderGoa *self = GTD_PROVIDER_GOA (provider);
+
+ return goa_account_get_provider_name (self->account);
+}
+
+static const gchar*
+gtd_provider_goa_get_provider_type (GtdProviderEds *provider)
+{
+ GtdProviderGoa *self = GTD_PROVIDER_GOA (provider);
+
+ return goa_account_get_provider_type (self->account);
+}
+
+static const gchar*
+gtd_provider_goa_get_description (GtdProviderEds *provider)
+{
+ GtdProviderGoa *self = GTD_PROVIDER_GOA (provider);
+
+ return goa_account_get_identity (self->account);
+}
+
+static gboolean
+gtd_provider_goa_get_enabled (GtdProviderEds *provider)
+{
+ GtdProviderGoa *self = GTD_PROVIDER_GOA (provider);
+
+ return !goa_account_get_calendar_disabled (self->account);
+}
+
+static GIcon*
+gtd_provider_goa_get_icon (GtdProviderEds *provider)
+{
+ GtdProviderGoa *self = GTD_PROVIDER_GOA (provider);
+
+ return self->icon;
+}
+
+static void
+gtd_provider_goa_set_account (GtdProviderGoa *provider,
+ GoaAccount *account)
+{
+ g_autofree gchar *icon_name = NULL;
+
+ if (provider->account == account)
+ return;
+
+ g_set_object (&provider->account, account);
+ g_object_notify (G_OBJECT (provider), "account");
+
+ g_debug ("Setting up Online Account: %s (%s)",
+ goa_account_get_identity (account),
+ goa_account_get_id (account));
+
+ /* Update icon */
+ icon_name = g_strdup_printf ("goa-account-%s", goa_account_get_provider_type (provider->account));
+ g_set_object (&provider->icon, g_themed_icon_new (icon_name));
+ g_object_notify (G_OBJECT (provider), "icon");
+
+ /* Provider id */
+ provider->id = g_strdup_printf ("%s@%s",
+ goa_account_get_provider_type (provider->account),
+ goa_account_get_id (provider->account));
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_provider_goa_finalize (GObject *object)
+{
+ GtdProviderGoa *self = (GtdProviderGoa *)object;
+
+ g_clear_pointer (&self->id, g_free);
+
+ g_clear_object (&self->account);
+ g_clear_object (&self->icon);
+
+ G_OBJECT_CLASS (gtd_provider_goa_parent_class)->finalize (object);
+}
+
+static void
+gtd_provider_goa_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdProviderGoa *self = GTD_PROVIDER_GOA (object);
+
+ switch (prop_id)
+ {
+
+ case PROP_ACCOUNT:
+ g_value_set_object (value, self->account);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_provider_goa_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdProviderGoa *self = GTD_PROVIDER_GOA (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACCOUNT:
+ gtd_provider_goa_set_account (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static gboolean
+gtd_provider_goa_should_load_source (GtdProviderEds *provider,
+ ESource *source)
+{
+ g_autoptr (ESource) ancestor = NULL;
+ GtdProviderGoa *self;
+ gboolean retval;
+
+ self = GTD_PROVIDER_GOA (provider);
+ retval = FALSE;
+
+ ancestor = e_source_registry_find_extension (gtd_provider_eds_get_registry (provider),
+ source,
+ E_SOURCE_EXTENSION_GOA);
+
+ /* If we detect that the given source is provided by a GOA account, check the account id */
+ if (ancestor)
+ {
+ ESourceExtension *extension;
+ const gchar *ancestor_id;
+ const gchar *account_id;
+
+ extension = e_source_get_extension (ancestor, E_SOURCE_EXTENSION_GOA);
+ ancestor_id = e_source_goa_get_account_id (E_SOURCE_GOA (extension));
+ account_id = goa_account_get_id (self->account);
+
+ /* When the ancestor's GOA id matches the current account's id, we shall load this list */
+ retval = g_strcmp0 (ancestor_id, account_id) == 0;
+ }
+
+ return retval;
+}
+
+static void
+gtd_provider_goa_class_init (GtdProviderGoaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtdProviderEdsClass *eds_class = GTD_PROVIDER_EDS_CLASS (klass);
+
+ eds_class->get_id = gtd_provider_goa_get_id;
+ eds_class->get_name = gtd_provider_goa_get_name;
+ eds_class->get_provider_type = gtd_provider_goa_get_provider_type;
+ eds_class->get_description = gtd_provider_goa_get_description;
+ eds_class->get_enabled = gtd_provider_goa_get_enabled;
+ eds_class->get_icon = gtd_provider_goa_get_icon;
+ eds_class->should_load_source = gtd_provider_goa_should_load_source;
+
+ object_class->finalize = gtd_provider_goa_finalize;
+ object_class->get_property = gtd_provider_goa_get_property;
+ object_class->set_property = gtd_provider_goa_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ACCOUNT,
+ g_param_spec_object ("account",
+ "Account of the provider",
+ "The Online Account of the provider",
+ GOA_TYPE_ACCOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gtd_provider_goa_init (GtdProviderGoa *self)
+{
+}
+
+GtdProviderGoa*
+gtd_provider_goa_new (ESourceRegistry *registry,
+ GoaAccount *account)
+{
+ return g_object_new (GTD_TYPE_PROVIDER_GOA,
+ "account", account,
+ "registry", registry,
+ NULL);
+}
+
+GoaAccount*
+gtd_provider_goa_get_account (GtdProviderGoa *provider)
+{
+ return provider->account;
+}
diff --git a/src/plugins/eds/gtd-provider-goa.h b/src/plugins/eds/gtd-provider-goa.h
new file mode 100644
index 0000000..42625cd
--- /dev/null
+++ b/src/plugins/eds/gtd-provider-goa.h
@@ -0,0 +1,43 @@
+/* gtd-provider-goa.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTD_PROVIDER_GOA_H
+#define GTD_PROVIDER_GOA_H
+
+#define GOA_API_IS_SUBJECT_TO_CHANGE 1
+
+#include "endeavour.h"
+#include "gtd-provider-eds.h"
+
+#include <glib.h>
+#include <goa/goa.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_PROVIDER_GOA (gtd_provider_goa_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdProviderGoa, gtd_provider_goa, GTD, PROVIDER_GOA, GtdProviderEds)
+
+GtdProviderGoa* gtd_provider_goa_new (ESourceRegistry *registry,
+ GoaAccount *account);
+
+GoaAccount* gtd_provider_goa_get_account (GtdProviderGoa *provider);
+
+G_END_DECLS
+
+#endif /* GTD_PROVIDER_GOA_H */
diff --git a/src/plugins/eds/gtd-provider-local.c b/src/plugins/eds/gtd-provider-local.c
new file mode 100644
index 0000000..c5651ab
--- /dev/null
+++ b/src/plugins/eds/gtd-provider-local.c
@@ -0,0 +1,150 @@
+/* gtd-provider-local.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "GtdProviderLocal"
+
+#include "gtd-provider-local.h"
+#include "gtd-task-list-eds.h"
+
+#include <glib/gi18n.h>
+
+struct _GtdProviderLocal
+{
+ GtdProviderEds parent;
+
+ GIcon *icon;
+ GList *tasklists;
+};
+
+G_DEFINE_TYPE (GtdProviderLocal, gtd_provider_local, GTD_TYPE_PROVIDER_EDS)
+
+
+/*
+ * GtdProviderEds overrides
+ */
+
+static const gchar*
+gtd_provider_local_get_id (GtdProviderEds *provider)
+{
+ return "local";
+}
+
+static const gchar*
+gtd_provider_local_get_name (GtdProviderEds *provider)
+{
+ return _("On This Computer");
+}
+
+static const gchar*
+gtd_provider_local_get_provider_type (GtdProviderEds *provider)
+{
+ return "local";
+}
+
+static const gchar*
+gtd_provider_local_get_description (GtdProviderEds *provider)
+{
+ return _("Local");
+}
+
+static gboolean
+gtd_provider_local_get_enabled (GtdProviderEds *provider)
+{
+ return TRUE;
+}
+
+static GIcon*
+gtd_provider_local_get_icon (GtdProviderEds *provider)
+{
+ GtdProviderLocal *self = GTD_PROVIDER_LOCAL (provider);
+
+ return self->icon;
+}
+
+static ESource*
+gtd_provider_local_create_source (GtdProviderEds *provider)
+{
+ ESourceExtension *extension;
+ ESource *source;
+
+ /* Create the source */
+ source = e_source_new (NULL, NULL, NULL);
+
+ if (!source)
+ return NULL;
+
+ /* Make it a local source */
+ extension = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
+
+ e_source_set_parent (source, "local-stub");
+ e_source_backend_set_backend_name (E_SOURCE_BACKEND (extension), "local");
+
+ return source;
+}
+
+static void
+gtd_provider_local_finalize (GObject *object)
+{
+ GtdProviderLocal *self = (GtdProviderLocal *)object;
+
+ g_clear_object (&self->icon);
+
+ G_OBJECT_CLASS (gtd_provider_local_parent_class)->finalize (object);
+}
+
+static gboolean
+gtd_provider_local_should_load_source (GtdProviderEds *provider,
+ ESource *source)
+{
+ if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
+ return g_strcmp0 (e_source_get_parent (source), "local-stub") == 0;
+
+ return FALSE;
+}
+
+static void
+gtd_provider_local_class_init (GtdProviderLocalClass *klass)
+{
+ GtdProviderEdsClass *eds_class = GTD_PROVIDER_EDS_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ eds_class->get_id = gtd_provider_local_get_id;
+ eds_class->get_name = gtd_provider_local_get_name;
+ eds_class->get_provider_type = gtd_provider_local_get_provider_type;
+ eds_class->get_description = gtd_provider_local_get_description;
+ eds_class->get_enabled = gtd_provider_local_get_enabled;
+ eds_class->get_icon = gtd_provider_local_get_icon;
+ eds_class->create_source = gtd_provider_local_create_source;
+ eds_class->should_load_source = gtd_provider_local_should_load_source;
+
+ object_class->finalize = gtd_provider_local_finalize;
+}
+
+static void
+gtd_provider_local_init (GtdProviderLocal *self)
+{
+ self->icon = G_ICON (g_themed_icon_new_with_default_fallbacks ("computer-symbolic"));
+}
+
+GtdProviderLocal*
+gtd_provider_local_new (ESourceRegistry *registry)
+{
+ return g_object_new (GTD_TYPE_PROVIDER_LOCAL,
+ "registry", registry,
+ NULL);
+}
diff --git a/src/plugins/eds/gtd-provider-local.h b/src/plugins/eds/gtd-provider-local.h
new file mode 100644
index 0000000..90ff8a6
--- /dev/null
+++ b/src/plugins/eds/gtd-provider-local.h
@@ -0,0 +1,37 @@
+/* gtd-provider-local.h
+ *
+ * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTD_PROVIDER_LOCAL_H
+#define GTD_PROVIDER_LOCAL_H
+
+#include "endeavour.h"
+#include "gtd-provider-eds.h"
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_PROVIDER_LOCAL (gtd_provider_local_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdProviderLocal, gtd_provider_local, GTD, PROVIDER_LOCAL, GtdProviderEds)
+
+GtdProviderLocal* gtd_provider_local_new (ESourceRegistry *source_registry);
+
+G_END_DECLS
+
+#endif /* GTD_PROVIDER_LOCAL_H */
diff --git a/src/plugins/eds/gtd-task-eds.c b/src/plugins/eds/gtd-task-eds.c
new file mode 100644
index 0000000..5dc667f
--- /dev/null
+++ b/src/plugins/eds/gtd-task-eds.c
@@ -0,0 +1,650 @@
+/* gtd-task-eds.c
+ *
+ * Copyright (C) 2017-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "GtdTaskEds"
+
+#include "gtd-eds-autoptr.h"
+#include "gtd-task-eds.h"
+
+#define ICAL_X_ENDEAVOUR_POSITION "X-ENDEAVOUR-POSITION"
+
+struct _GtdTaskEds
+{
+ GtdTask parent;
+
+ ECalComponent *component;
+ ECalComponent *new_component;
+
+ gchar *description;
+};
+
+G_DEFINE_TYPE (GtdTaskEds, gtd_task_eds, GTD_TYPE_TASK)
+
+enum
+{
+ PROP_0,
+ PROP_COMPONENT,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+
+/*
+ * Auxiliary methods
+ */
+
+static GDateTime*
+convert_icaltime (const ICalTime *date)
+{
+ GDateTime *dt;
+
+ if (!date)
+ return NULL;
+
+ dt = g_date_time_new_utc (i_cal_time_get_year (date),
+ i_cal_time_get_month (date),
+ i_cal_time_get_day (date),
+ i_cal_time_is_date (date) ? 0 : i_cal_time_get_hour (date),
+ i_cal_time_is_date (date) ? 0 : i_cal_time_get_minute (date),
+ i_cal_time_is_date (date) ? 0 : i_cal_time_get_second (date));
+
+ return dt;
+}
+
+static void
+set_description (GtdTaskEds *self,
+ const gchar *description)
+{
+ ECalComponentText *text;
+ GSList note;
+
+ text = e_cal_component_text_new (description ? description : "", NULL);
+
+ note.data = text;
+ note.next = NULL;
+
+ g_clear_pointer (&self->description, g_free);
+ self->description = g_strdup (description);
+
+ e_cal_component_set_descriptions (self->new_component, (description && *description) ? &note : NULL);
+
+ e_cal_component_text_free (text);
+}
+
+static void
+setup_description (GtdTaskEds *self)
+{
+ g_autofree gchar *desc = NULL;
+ GSList *text_list;
+ GSList *l;
+
+ /* concatenates the multiple descriptions a task may have */
+ text_list = e_cal_component_get_descriptions (self->new_component);
+
+ for (l = text_list; l != NULL; l = l->next)
+ {
+ if (l->data != NULL)
+ {
+ ECalComponentText *text;
+ gchar *carrier;
+
+ text = l->data;
+
+ if (desc)
+ {
+ carrier = g_strconcat (desc,
+ "\n",
+ e_cal_component_text_get_value (text),
+ NULL);
+ g_free (desc);
+ desc = carrier;
+ }
+ else
+ {
+ desc = g_strdup (e_cal_component_text_get_value (text));
+ }
+ }
+ }
+
+ set_description (self, desc);
+
+ g_slist_free_full (text_list, e_cal_component_text_free);
+}
+
+
+/*
+ * GtdObject overrides
+ */
+
+static const gchar*
+gtd_task_eds_get_uid (GtdObject *object)
+{
+ GtdTaskEds *self;
+ const gchar *uid;
+
+ g_return_val_if_fail (GTD_IS_TASK (object), NULL);
+
+ self = GTD_TASK_EDS (object);
+
+ if (self->new_component)
+ uid = e_cal_component_get_uid (self->new_component);
+ else
+ uid = NULL;
+
+ return uid;
+}
+
+static void
+gtd_task_eds_set_uid (GtdObject *object,
+ const gchar *uid)
+{
+ GtdTaskEds *self;
+ const gchar *current_uid;
+
+ g_return_if_fail (GTD_IS_TASK (object));
+
+ self = GTD_TASK_EDS (object);
+
+ if (!self->new_component)
+ return;
+
+ current_uid = e_cal_component_get_uid (self->new_component);
+
+ if (g_strcmp0 (current_uid, uid) != 0)
+ {
+ e_cal_component_set_uid (self->new_component, uid);
+
+ g_object_notify (G_OBJECT (object), "uid");
+ }
+}
+
+
+/*
+ * GtdTask overrides
+ */
+
+static GDateTime*
+gtd_task_eds_get_completion_date (GtdTask *task)
+{
+ ICalTime *idt;
+ GtdTaskEds *self;
+ GDateTime *dt;
+
+ self = GTD_TASK_EDS (task);
+ dt = NULL;
+
+ idt = e_cal_component_get_completed (self->new_component);
+
+ if (idt)
+ dt = convert_icaltime (idt);
+
+ g_clear_object (&idt);
+
+ return dt;
+}
+
+static void
+gtd_task_eds_set_completion_date (GtdTask *task,
+ GDateTime *dt)
+{
+ GtdTaskEds *self;
+ ICalTime *idt;
+
+ self = GTD_TASK_EDS (task);
+
+ idt = i_cal_time_new_null_time ();
+ i_cal_time_set_date (idt,
+ g_date_time_get_year (dt),
+ g_date_time_get_month (dt),
+ g_date_time_get_day_of_month (dt));
+ i_cal_time_set_time (idt,
+ g_date_time_get_hour (dt),
+ g_date_time_get_minute (dt),
+ g_date_time_get_seconds (dt));
+ i_cal_time_set_timezone (idt, i_cal_timezone_get_utc_timezone ());
+
+ /* convert timezone
+ *
+ * FIXME: This does not do anything until we have an ical
+ * timezone associated with the task
+ */
+ i_cal_time_convert_timezone (idt, NULL, i_cal_timezone_get_utc_timezone ());
+
+ e_cal_component_set_completed (self->new_component, idt);
+
+ g_object_unref (idt);
+}
+
+static gboolean
+gtd_task_eds_get_complete (GtdTask *task)
+{
+ ICalPropertyStatus status;
+ GtdTaskEds *self;
+ gboolean completed;
+
+ g_return_val_if_fail (GTD_IS_TASK_EDS (task), FALSE);
+
+ self = GTD_TASK_EDS (task);
+
+ status = e_cal_component_get_status (self->new_component);
+ completed = status == I_CAL_STATUS_COMPLETED;
+
+ return completed;
+}
+
+static void
+gtd_task_eds_set_complete (GtdTask *task,
+ gboolean complete)
+{
+ ICalPropertyStatus status;
+ GtdTaskEds *self;
+ gint percent;
+ g_autoptr (GDateTime) now = NULL;
+
+ self = GTD_TASK_EDS (task);
+ now = complete ? g_date_time_new_now_local () : NULL;
+
+ if (complete)
+ {
+ percent = 100;
+ status = I_CAL_STATUS_COMPLETED;
+ }
+ else
+ {
+ percent = 0;
+ status = I_CAL_STATUS_NEEDSACTION;
+ }
+
+ e_cal_component_set_percent_complete (self->new_component, percent);
+ e_cal_component_set_status (self->new_component, status);
+ gtd_task_eds_set_completion_date (task, now);
+}
+
+static GDateTime*
+gtd_task_eds_get_creation_date (GtdTask *task)
+{
+ ICalTime *idt;
+ GtdTaskEds *self;
+ GDateTime *dt;
+
+ self = GTD_TASK_EDS (task);
+ dt = NULL;
+
+ idt = e_cal_component_get_created (self->new_component);
+
+ if (idt)
+ dt = convert_icaltime (idt);
+
+ g_clear_object (&idt);
+
+ return dt;
+}
+
+static void
+gtd_task_eds_set_creation_date (GtdTask *task,
+ GDateTime *dt)
+{
+ g_assert_not_reached ();
+}
+
+static const gchar*
+gtd_task_eds_get_description (GtdTask *task)
+{
+ GtdTaskEds *self = GTD_TASK_EDS (task);
+
+ return self->description ? self->description : "";
+}
+
+static void
+gtd_task_eds_set_description (GtdTask *task,
+ const gchar *description)
+{
+ set_description (GTD_TASK_EDS (task), description);
+}
+
+static GDateTime*
+gtd_task_eds_get_due_date (GtdTask *task)
+{
+ ECalComponentDateTime *comp_dt;
+ GtdTaskEds *self;
+ GDateTime *date;
+
+ g_return_val_if_fail (GTD_IS_TASK_EDS (task), NULL);
+
+ self = GTD_TASK_EDS (task);
+
+ comp_dt = e_cal_component_get_due (self->new_component);
+ if (!comp_dt)
+ return NULL;
+
+ date = convert_icaltime (e_cal_component_datetime_get_value (comp_dt));
+ e_cal_component_datetime_free (comp_dt);
+
+ return date;
+}
+
+static void
+gtd_task_eds_set_due_date (GtdTask *task,
+ GDateTime *dt)
+{
+ GtdTaskEds *self;
+ GDateTime *current_dt;
+
+ g_assert (GTD_IS_TASK_EDS (task));
+
+ self = GTD_TASK_EDS (task);
+
+ current_dt = gtd_task_get_due_date (task);
+
+ if (dt != current_dt)
+ {
+ ECalComponentDateTime *comp_dt;
+ ICalTime *idt;
+
+ comp_dt = NULL;
+ idt = NULL;
+
+ if (!current_dt ||
+ (current_dt &&
+ dt &&
+ g_date_time_compare (current_dt, dt) != 0))
+ {
+ idt = i_cal_time_new_null_time ();
+
+ g_date_time_ref (dt);
+
+ /* Copy the given dt */
+ i_cal_time_set_date (idt,
+ g_date_time_get_year (dt),
+ g_date_time_get_month (dt),
+ g_date_time_get_day_of_month (dt));
+ i_cal_time_set_time (idt,
+ g_date_time_get_hour (dt),
+ g_date_time_get_minute (dt),
+ g_date_time_get_seconds (dt));
+ i_cal_time_set_is_date (idt,
+ i_cal_time_get_hour (idt) == 0 &&
+ i_cal_time_get_minute (idt) == 0 &&
+ i_cal_time_get_second (idt) == 0);
+
+ comp_dt = e_cal_component_datetime_new_take (idt, g_strdup ("UTC"));
+
+ e_cal_component_set_due (self->new_component, comp_dt);
+
+ e_cal_component_datetime_free (comp_dt);
+
+ g_date_time_unref (dt);
+ }
+ else if (!dt)
+ {
+ e_cal_component_set_due (self->new_component, NULL);
+ }
+ }
+
+ g_clear_pointer (&current_dt, g_date_time_unref);
+}
+
+static gboolean
+gtd_task_eds_get_important (GtdTask *task)
+{
+ GtdTaskEds *self = GTD_TASK_EDS (task);
+
+ return e_cal_component_get_priority (self->new_component) > 0;
+}
+
+static void
+gtd_task_eds_set_important (GtdTask *task,
+ gboolean important)
+{
+ GtdTaskEds *self = GTD_TASK_EDS (task);
+
+ e_cal_component_set_priority (self->new_component, important ? 3 : -1);
+}
+
+static gint64
+gtd_task_eds_get_position (GtdTask *task)
+{
+ g_autofree gchar *value = NULL;
+ ICalComponent *ical_comp;
+ gint64 position = -1;
+ GtdTaskEds *self;
+
+ self = GTD_TASK_EDS (task);
+ ical_comp = e_cal_component_get_icalcomponent (self->new_component);
+
+ value = e_cal_util_component_dup_x_property (ical_comp, ICAL_X_ENDEAVOUR_POSITION);
+ if (value)
+ position = g_ascii_strtoll (value, NULL, 10);
+
+ return position;
+}
+
+void
+gtd_task_eds_set_position (GtdTask *task,
+ gint64 position)
+{
+ g_autofree gchar *value = NULL;
+ ICalComponent *ical_comp;
+ GtdTaskEds *self;
+
+ self = GTD_TASK_EDS (task);
+ if (position != -1)
+ value = g_strdup_printf ("%" G_GINT64_FORMAT, position);
+ ical_comp = e_cal_component_get_icalcomponent (self->new_component);
+
+ e_cal_util_component_set_x_property (ical_comp, ICAL_X_ENDEAVOUR_POSITION, value);
+}
+
+static const gchar*
+gtd_task_eds_get_title (GtdTask *task)
+{
+ GtdTaskEds *self;
+
+ g_return_val_if_fail (GTD_IS_TASK_EDS (task), NULL);
+
+ self = GTD_TASK_EDS (task);
+
+ return i_cal_component_get_summary (e_cal_component_get_icalcomponent (self->new_component));
+}
+
+static void
+gtd_task_eds_set_title (GtdTask *task,
+ const gchar *title)
+{
+ ECalComponentText *new_summary;
+ GtdTaskEds *self;
+
+ g_return_if_fail (GTD_IS_TASK_EDS (task));
+ g_return_if_fail (g_utf8_validate (title, -1, NULL));
+
+ self = GTD_TASK_EDS (task);
+
+ new_summary = e_cal_component_text_new (title, NULL);
+
+ e_cal_component_set_summary (self->new_component, new_summary);
+
+ e_cal_component_text_free (new_summary);
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_task_eds_finalize (GObject *object)
+{
+ GtdTaskEds *self = (GtdTaskEds *)object;
+
+ g_clear_object (&self->component);
+ g_clear_object (&self->new_component);
+
+ G_OBJECT_CLASS (gtd_task_eds_parent_class)->finalize (object);
+}
+
+static void
+gtd_task_eds_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTaskEds *self = GTD_TASK_EDS (object);
+
+ switch (prop_id)
+ {
+ case PROP_COMPONENT:
+ g_value_set_object (value, self->component);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_eds_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTaskEds *self = GTD_TASK_EDS (object);
+
+ switch (prop_id)
+ {
+ case PROP_COMPONENT:
+ gtd_task_eds_set_component (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_eds_class_init (GtdTaskEdsClass *klass)
+{
+ GtdObjectClass *gtd_object_class = GTD_OBJECT_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtdTaskClass *task_class = GTD_TASK_CLASS (klass);
+
+ object_class->finalize = gtd_task_eds_finalize;
+ object_class->get_property = gtd_task_eds_get_property;
+ object_class->set_property = gtd_task_eds_set_property;
+
+ task_class->get_complete = gtd_task_eds_get_complete;
+ task_class->set_complete = gtd_task_eds_set_complete;
+ task_class->get_creation_date = gtd_task_eds_get_creation_date;
+ task_class->set_creation_date = gtd_task_eds_set_creation_date;
+ task_class->get_completion_date = gtd_task_eds_get_completion_date;
+ task_class->set_completion_date = gtd_task_eds_set_completion_date;
+ task_class->get_description = gtd_task_eds_get_description;
+ task_class->set_description = gtd_task_eds_set_description;
+ task_class->get_due_date = gtd_task_eds_get_due_date;
+ task_class->set_due_date = gtd_task_eds_set_due_date;
+ task_class->get_important = gtd_task_eds_get_important;
+ task_class->set_important = gtd_task_eds_set_important;
+ task_class->get_position = gtd_task_eds_get_position;
+ task_class->set_position = gtd_task_eds_set_position;
+ task_class->get_title = gtd_task_eds_get_title;
+ task_class->set_title = gtd_task_eds_set_title;
+
+ gtd_object_class->get_uid = gtd_task_eds_get_uid;
+ gtd_object_class->set_uid = gtd_task_eds_set_uid;
+
+ properties[PROP_COMPONENT] = g_param_spec_object ("component",
+ "Component",
+ "Component",
+ E_TYPE_CAL_COMPONENT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gtd_task_eds_init (GtdTaskEds *self)
+{
+}
+
+GtdTask*
+gtd_task_eds_new (ECalComponent *component)
+{
+ return g_object_new (GTD_TYPE_TASK_EDS,
+ "component", component,
+ NULL);
+}
+
+ECalComponent*
+gtd_task_eds_get_component (GtdTaskEds *self)
+{
+ g_return_val_if_fail (GTD_IS_TASK_EDS (self), NULL);
+
+ return self->new_component;
+}
+
+void
+gtd_task_eds_set_component (GtdTaskEds *self,
+ ECalComponent *component)
+{
+ GObject *object;
+
+ g_return_if_fail (GTD_IS_TASK_EDS (self));
+ g_return_if_fail (E_IS_CAL_COMPONENT (component));
+
+ if (!g_set_object (&self->component, component))
+ return;
+
+ object = G_OBJECT (self);
+
+ g_clear_object (&self->new_component);
+ self->new_component = e_cal_component_clone (component);
+
+ setup_description (self);
+
+ g_object_notify (object, "complete");
+ g_object_notify (object, "creation-date");
+ g_object_notify (object, "description");
+ g_object_notify (object, "due-date");
+ g_object_notify (object, "important");
+ g_object_notify (object, "position");
+ g_object_notify (object, "title");
+ g_object_notify_by_pspec (object, properties[PROP_COMPONENT]);
+}
+
+void
+gtd_task_eds_apply (GtdTaskEds *self)
+{
+ g_return_if_fail (GTD_IS_TASK_EDS (self));
+
+ e_cal_component_commit_sequence (self->new_component);
+
+ /* Make new_component the actual component */
+ gtd_task_eds_set_component (self, self->new_component);
+}
+
+void
+gtd_task_eds_revert (GtdTaskEds *self)
+{
+ g_autoptr (ECalComponent) component = NULL;
+
+ g_return_if_fail (GTD_IS_TASK_EDS (self));
+
+ component = e_cal_component_clone (self->component);
+
+ gtd_task_eds_set_component (self, component);
+}
diff --git a/src/plugins/eds/gtd-task-eds.h b/src/plugins/eds/gtd-task-eds.h
new file mode 100644
index 0000000..078f585
--- /dev/null
+++ b/src/plugins/eds/gtd-task-eds.h
@@ -0,0 +1,46 @@
+/* gtd-task-eds.h
+ *
+ * Copyright (C) 2017-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTD_TASK_EDS_H
+#define GTD_TASK_EDS_H
+
+#include "endeavour.h"
+
+#include "gtd-eds.h"
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_TASK_EDS (gtd_task_eds_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdTaskEds, gtd_task_eds, GTD, TASK_EDS, GtdTask)
+
+GtdTask* gtd_task_eds_new (ECalComponent *component);
+
+ECalComponent* gtd_task_eds_get_component (GtdTaskEds *self);
+
+void gtd_task_eds_set_component (GtdTaskEds *self,
+ ECalComponent *component);
+
+void gtd_task_eds_apply (GtdTaskEds *self);
+
+void gtd_task_eds_revert (GtdTaskEds *self);
+
+G_END_DECLS
+
+#endif /* GTD_TASK_EDS_H */
+
diff --git a/src/plugins/eds/gtd-task-list-eds.c b/src/plugins/eds/gtd-task-list-eds.c
new file mode 100644
index 0000000..19bcdab
--- /dev/null
+++ b/src/plugins/eds/gtd-task-list-eds.c
@@ -0,0 +1,875 @@
+/* gtd-task-list-eds.c
+ *
+ * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "GtdTaskListEds"
+
+#include "e-source-endeavour.h"
+#include "gtd-debug.h"
+#include "gtd-eds-autoptr.h"
+#include "gtd-provider-eds.h"
+#include "gtd-task-eds.h"
+#include "gtd-task-list-eds.h"
+
+#include <glib/gi18n.h>
+
+struct _GtdTaskListEds
+{
+ GtdTaskList parent;
+
+ ECalClient *client;
+ ECalClientView *client_view;
+ ESource *source;
+
+ GCancellable *cancellable;
+};
+
+typedef struct
+{
+ GtdProvider *provider;
+ ESource *source;
+ ECalClient *client;
+} NewTaskListData;
+
+static void on_client_objects_modified_for_migration_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data);
+
+G_DEFINE_TYPE (GtdTaskListEds, gtd_task_list_eds, GTD_TYPE_TASK_LIST)
+
+enum
+{
+ PROP_0,
+ PROP_CLIENT,
+ PROP_SOURCE,
+ N_PROPS
+};
+
+
+/*
+ * Auxiliary methods
+ */
+
+static void
+new_task_list_data_free (gpointer data)
+{
+ NewTaskListData *list_data = data;
+
+ if (!list_data)
+ return;
+
+ g_clear_object (&list_data->provider);
+ g_clear_object (&list_data->source);
+ g_clear_object (&list_data->client);
+ g_free (list_data);
+}
+
+static void
+update_changed_tasks (GtdTaskListEds *self,
+ GHashTable *changed_tasks)
+{
+ g_autoptr (GSList) components = NULL;
+ GHashTableIter iter;
+ GtdProvider *provider;
+ GtdTask *task;
+ guint n_changed_tasks;
+
+ GTD_ENTRY;
+
+ n_changed_tasks = g_hash_table_size (changed_tasks);
+ provider = gtd_task_list_get_provider (GTD_TASK_LIST (self));
+
+ /* Nothing changed, list is ready */
+ if (n_changed_tasks == 0)
+ {
+ gtd_object_pop_loading (GTD_OBJECT (provider));
+ g_signal_emit_by_name (provider, "list-added", self);
+ GTD_RETURN ();
+ }
+
+ GTD_TRACE_MSG ("%u task(s) changed", n_changed_tasks);
+
+ g_hash_table_iter_init (&iter, changed_tasks);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &task, NULL))
+ {
+ ICalComponent *ical_comp;
+ ECalComponent *comp;
+
+ comp = gtd_task_eds_get_component (GTD_TASK_EDS (task));
+ ical_comp = e_cal_component_get_icalcomponent (comp);
+
+ components = g_slist_prepend (components, ical_comp);
+ }
+
+ e_cal_client_modify_objects (self->client,
+ components,
+ E_CAL_OBJ_MOD_THIS,
+ E_CAL_OPERATION_FLAG_NONE,
+ self->cancellable,
+ on_client_objects_modified_for_migration_cb,
+ self);
+
+ GTD_EXIT;
+}
+
+static void
+migrate_to_v1 (GtdTaskListEds *self,
+ GHashTable *changed_tasks)
+{
+ GListModel *model;
+ guint n_tasks;
+ guint i;
+
+ model = G_LIST_MODEL (self);
+ n_tasks = g_list_model_get_n_items (model);
+
+ for (i = 0; i < n_tasks; i++)
+ {
+ g_autoptr (GtdTask) task = NULL;
+
+ task = g_list_model_get_item (model, i);
+
+ /* Don't notify to avoid carpet-bombing GtdTaskList */
+ g_object_freeze_notify (G_OBJECT (task));
+
+ gtd_task_set_position (task, i);
+
+ g_hash_table_add (changed_tasks, task);
+ }
+
+ for (i = 0; i < n_tasks; i++)
+ {
+ g_autoptr (GtdTask) task = NULL;
+
+ task = g_list_model_get_item (model, i);
+ g_object_thaw_notify (G_OBJECT (task));
+ }
+}
+
+struct
+{
+ guint api_version;
+ void (* migrate) (GtdTaskListEds *self,
+ GHashTable *changed_tasks);
+}
+migration_vtable[] =
+{
+ { 0, migrate_to_v1 },
+};
+
+static void
+maybe_migrate_todo_api_version (GtdTaskListEds *self)
+{
+ g_autoptr (GHashTable) changed_tasks = NULL;
+ ESourceEndeavour *endeavour_extension;
+ gboolean api_version_changed;
+ guint api_version;
+ guint i;
+
+ GTD_ENTRY;
+
+ /*
+ * Ensure the type so that it is available for introspection when
+ * calling e_source_get_extension().
+ */
+ g_type_ensure (E_TYPE_SOURCE_ENDEAVOUR);
+
+ api_version_changed = FALSE;
+ endeavour_extension = e_source_get_extension (self->source, E_SOURCE_EXTENSION_ENDEAVOUR);
+ api_version = e_source_endeavour_get_api_version (endeavour_extension);
+ changed_tasks = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ g_debug ("%s: Endeavour API version %u",
+ gtd_task_list_get_name (GTD_TASK_LIST (self)),
+ api_version);
+
+ for (i = 0; i < G_N_ELEMENTS (migration_vtable); i++)
+ {
+ guint new_api_version = i + 1;
+
+ if (api_version > migration_vtable[i].api_version)
+ continue;
+
+ g_debug (" Migrating task list to Endeavour API v%u", new_api_version);
+
+ migration_vtable[i].migrate (self, changed_tasks);
+
+ e_source_endeavour_set_api_version (endeavour_extension, new_api_version);
+ api_version_changed = TRUE;
+ }
+
+ if (api_version_changed)
+ {
+ g_debug ("Saving new API version");
+
+ e_source_write (self->source, NULL, NULL, NULL);
+ }
+
+ update_changed_tasks (self, changed_tasks);
+
+ GTD_EXIT;
+}
+
+
+/*
+ * Callbacks
+ */
+
+static gboolean
+new_task_list_in_idle_cb (gpointer data)
+{
+ g_autoptr (GtdTaskListEds) list_eds = NULL;
+ g_autoptr (GTask) task = data;
+ NewTaskListData *list_data;
+
+ list_data = g_task_get_task_data (task);
+ list_eds = g_object_new (GTD_TYPE_TASK_LIST_EDS,
+ "provider", list_data->provider,
+ "source", list_data->source,
+ "client", list_data->client,
+ NULL);
+
+ g_task_return_pointer (task, g_steal_pointer (&list_eds), NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+on_client_objects_modified_for_migration_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+ GtdProvider *provider;
+ GtdTaskListEds *self;
+
+ GTD_ENTRY;
+
+ self = GTD_TASK_LIST_EDS (user_data);
+ provider = gtd_task_list_get_provider (GTD_TASK_LIST (self));
+
+ e_cal_client_modify_objects_finish (self->client, result, &error);
+
+ if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Error migrating tasks to new API version: %s", error->message);
+
+ gtd_object_pop_loading (GTD_OBJECT (provider));
+ g_signal_emit_by_name (provider, "list-added", self);
+
+ GTD_EXIT;
+}
+
+static void
+on_view_objects_added_cb (ECalClientView *view,
+ const GSList *objects,
+ GtdTaskList *self)
+{
+ g_autoptr (ECalClient) client = NULL;
+ GSList *l;
+
+ GTD_ENTRY;
+
+ client = e_cal_client_view_ref_client (view);
+
+ for (l = (GSList*) objects; l; l = l->next)
+ {
+ g_autoptr (ECalComponent) component = NULL;
+ GtdTask *task;
+ const gchar *uid;
+
+ component = e_cal_component_new_from_icalcomponent (i_cal_component_clone (l->data));
+ uid = e_cal_component_get_uid (component);
+
+ task = gtd_task_list_get_task_by_id (self, uid);
+
+ /* If the task already exists, we must instead update its component */
+ if (task)
+ {
+ gtd_task_eds_set_component (GTD_TASK_EDS (task), component);
+
+ gtd_task_list_update_task (self, task);
+
+ GTD_TRACE_MSG ("Updated task '%s' to tasklist '%s'",
+ gtd_task_get_title (task),
+ gtd_task_list_get_name (self));
+
+ continue;
+ }
+
+ /* Add the new task */
+ task = gtd_task_eds_new (component);
+ gtd_task_set_list (task, self);
+
+ gtd_task_list_add_task (self, task);
+
+ GTD_TRACE_MSG ("Added task '%s' (%s) to tasklist '%s'",
+ gtd_task_get_title (task),
+ gtd_object_get_uid (GTD_OBJECT (task)),
+ gtd_task_list_get_name (self));
+ }
+
+ GTD_EXIT;
+}
+
+static void
+on_view_objects_modified_cb (ECalClientView *view,
+ const GSList *objects,
+ GtdTaskList *self)
+{
+ g_autoptr (ECalClient) client = NULL;
+ GSList *l;
+
+ GTD_ENTRY;
+
+ client = e_cal_client_view_ref_client (view);
+
+ for (l = (GSList*) objects; l; l = l->next)
+ {
+ g_autoptr (ECalComponent) component = NULL;
+ GtdTask *task;
+ const gchar *uid;
+
+ component = e_cal_component_new_from_icalcomponent (i_cal_component_clone (l->data));
+ uid = e_cal_component_get_uid (component);
+
+ task = gtd_task_list_get_task_by_id (self, uid);
+
+ if (!task)
+ continue;
+
+ gtd_task_eds_set_component (GTD_TASK_EDS (task), component);
+
+ GTD_TRACE_MSG ("Updated task '%s' from tasklist '%s'",
+ gtd_task_get_title (GTD_TASK (task)),
+ gtd_task_list_get_name (self));
+ }
+
+ GTD_EXIT;
+}
+
+static void
+on_view_objects_removed_cb (ECalClientView *view,
+ const GSList *uids,
+ GtdTaskList *self)
+{
+ GSList *l;
+
+ GTD_ENTRY;
+
+ for (l = (GSList*) uids; l; l = l->next)
+ {
+ ECalComponentId *id;
+ GtdTask *task;
+
+ id = l->data;
+ task = gtd_task_list_get_task_by_id (self, e_cal_component_id_get_uid (id));
+
+ if (!task)
+ continue;
+
+ gtd_task_list_remove_task (self, task);
+
+ GTD_TRACE_MSG ("Removed task '%s' from tasklist '%s'",
+ gtd_task_get_title (task),
+ gtd_task_list_get_name (self));
+ }
+
+ GTD_EXIT;
+}
+
+static void
+on_view_completed_cb (ECalClientView *view,
+ const GError *error,
+ GtdTaskList *self)
+{
+ gtd_object_pop_loading (GTD_OBJECT (gtd_manager_get_default ()));
+ gtd_object_pop_loading (GTD_OBJECT (self));
+
+ if (error)
+ {
+ g_warning ("Error fetching tasks from list: %s", error->message);
+
+ gtd_manager_emit_error_message (gtd_manager_get_default (),
+ _("Error fetching tasks from list"),
+ error->message,
+ NULL,
+ NULL);
+ return;
+ }
+
+ maybe_migrate_todo_api_version (GTD_TASK_LIST_EDS (self));
+}
+
+static void
+on_client_view_acquired_cb (GObject *client,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+ GtdTaskListEds *self;
+
+ self = GTD_TASK_LIST_EDS (user_data);
+
+ e_cal_client_get_view_finish (E_CAL_CLIENT (client), result, &self->client_view, &error);
+
+ if (error)
+ {
+ g_warning ("Error fetching tasks from list: %s", error->message);
+
+ gtd_manager_emit_error_message (gtd_manager_get_default (),
+ _("Error fetching tasks from list"),
+ error->message,
+ NULL,
+ NULL);
+ return;
+ }
+
+ g_debug ("ECalClientView for tasklist '%s' successfully acquired",
+ gtd_task_list_get_name (GTD_TASK_LIST (self)));
+
+ g_signal_connect (self->client_view, "objects-added", G_CALLBACK (on_view_objects_added_cb), self);
+ g_signal_connect (self->client_view, "objects-removed", G_CALLBACK (on_view_objects_removed_cb), self);
+ g_signal_connect (self->client_view, "objects-modified", G_CALLBACK (on_view_objects_modified_cb), self);
+ g_signal_connect (self->client_view, "complete", G_CALLBACK (on_view_completed_cb), self);
+
+ gtd_object_push_loading (GTD_OBJECT (self));
+
+ e_cal_client_view_start (self->client_view, &error);
+
+ if (error)
+ {
+ g_warning ("Error starting view: %s", error->message);
+
+ gtd_manager_emit_error_message (gtd_manager_get_default (),
+ _("Error fetching tasks from list"),
+ error->message,
+ NULL,
+ NULL);
+ }
+}
+
+static void
+on_source_removable_changed_cb (GtdTaskListEds *list)
+{
+ gtd_task_list_set_is_removable (GTD_TASK_LIST (list),
+ e_source_get_removable (list->source) ||
+ e_source_get_remote_deletable (list->source));
+}
+
+static void
+on_source_selectable_selected_changed_cb (ESourceSelectable *selectable,
+ GParamSpec *pspec,
+ GtdTaskListEds *self)
+{
+ g_debug ("%s (%s): ESourceSelectable:selected changed, notifying...",
+ e_source_get_uid (self->source),
+ e_source_get_display_name (self->source));
+
+ g_object_notify (G_OBJECT (self), "archived");
+}
+
+static void
+on_save_task_list_finished_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtdTaskListEds *list;
+ GError *error;
+
+ list = user_data;
+ error = NULL;
+
+ gtd_object_pop_loading (GTD_OBJECT (list));
+
+ e_source_write_finish (E_SOURCE (source), result, &error);
+
+ if (error)
+ {
+ g_warning ("%s: %s: %s",
+ G_STRFUNC,
+ "Error saving task list",
+ error->message);
+ g_clear_error (&error);
+ }
+}
+
+static void
+save_task_list (GtdTaskListEds *list)
+{
+ if (e_source_get_writable (list->source))
+ {
+ if (!list->cancellable)
+ list->cancellable = g_cancellable_new ();
+
+ gtd_object_push_loading (GTD_OBJECT (list));
+
+ e_source_write (list->source,
+ list->cancellable,
+ on_save_task_list_finished_cb,
+ list);
+ }
+}
+
+static gboolean
+color_to_string (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ GdkRGBA *color;
+ gchar *color_str;
+
+ color = g_value_get_boxed (from_value);
+ color_str = gdk_rgba_to_string (color);
+
+ g_value_set_string (to_value, color_str);
+
+ g_free (color_str);
+
+ return TRUE;
+}
+
+static gboolean
+string_to_color (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ GdkRGBA color;
+
+ if (!gdk_rgba_parse (&color, g_value_get_string (from_value)))
+ gdk_rgba_parse (&color, "#ffffff"); /* calendar default colour */
+
+ g_value_set_boxed (to_value, &color);
+
+ return TRUE;
+}
+
+
+/*
+ * GtdTaskList overrides
+ */
+
+static gboolean
+gtd_task_list_eds_get_archived (GtdTaskList *list)
+{
+ ESourceSelectable *selectable;
+ GtdTaskListEds *self;
+
+ self = GTD_TASK_LIST_EDS (list);
+ selectable = e_source_get_extension (self->source, E_SOURCE_EXTENSION_TASK_LIST);
+
+ return !e_source_selectable_get_selected (selectable);
+}
+
+static void
+gtd_task_list_eds_set_archived (GtdTaskList *list,
+ gboolean archived)
+{
+ ESourceSelectable *selectable;
+ GtdTaskListEds *self;
+
+ GTD_ENTRY;
+
+ self = GTD_TASK_LIST_EDS (list);
+ selectable = e_source_get_extension (self->source, E_SOURCE_EXTENSION_TASK_LIST);
+
+ g_signal_handlers_block_by_func (selectable, on_source_selectable_selected_changed_cb, self);
+
+ e_source_selectable_set_selected (selectable, !archived);
+
+ g_signal_handlers_unblock_by_func (selectable, on_source_selectable_selected_changed_cb, self);
+
+ GTD_EXIT;
+}
+
+
+/*
+ * GtdObject overrides
+ */
+
+static const gchar*
+gtd_task_list_eds_get_uid (GtdObject *object)
+{
+ GtdTaskListEds *self = GTD_TASK_LIST_EDS (object);
+
+ return e_source_get_uid (self->source);
+}
+
+static void
+gtd_task_list_eds_set_uid (GtdObject *object,
+ const gchar *uid)
+{
+ g_assert_not_reached ();
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_task_list_eds_finalize (GObject *object)
+{
+ GtdTaskListEds *self = GTD_TASK_LIST_EDS (object);
+
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->client);
+ g_clear_object (&self->client_view);
+ g_clear_object (&self->source);
+
+ G_OBJECT_CLASS (gtd_task_list_eds_parent_class)->finalize (object);
+}
+
+static void
+gtd_task_list_eds_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTaskListEds *self = GTD_TASK_LIST_EDS (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, self->client);
+ break;
+
+ case PROP_SOURCE:
+ g_value_set_object (value, self->source);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_list_eds_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTaskListEds *self = GTD_TASK_LIST_EDS (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ if (!g_set_object (&self->client, g_value_get_object (value)) || !self->client)
+ return;
+
+ e_cal_client_get_view (self->client,
+ "#t",
+ self->cancellable,
+ on_client_view_acquired_cb,
+ self);
+
+ g_object_notify (object, "client");
+ break;
+
+ case PROP_SOURCE:
+ gtd_task_list_eds_set_source (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_list_eds_class_init (GtdTaskListEdsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtdObjectClass *gtd_object_class = GTD_OBJECT_CLASS (klass);
+ GtdTaskListClass *task_list_class = GTD_TASK_LIST_CLASS (klass);
+
+ task_list_class->get_archived = gtd_task_list_eds_get_archived;
+ task_list_class->set_archived = gtd_task_list_eds_set_archived;
+
+ gtd_object_class->get_uid = gtd_task_list_eds_get_uid;
+ gtd_object_class->set_uid = gtd_task_list_eds_set_uid;
+
+ object_class->finalize = gtd_task_list_eds_finalize;
+ object_class->get_property = gtd_task_list_eds_get_property;
+ object_class->set_property = gtd_task_list_eds_set_property;
+
+
+ /**
+ * GtdTaskListEds::client:
+ *
+ * The #ECalClient of this #GtdTaskListEds
+ */
+ g_object_class_install_property (object_class,
+ PROP_CLIENT,
+ g_param_spec_object ("client",
+ "ECalClient of this list",
+ "The ECalClient of this list",
+ E_TYPE_CAL_CLIENT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ /**
+ * GtdTaskListEds::source:
+ *
+ * The #ESource of this #GtdTaskListEds
+ */
+ g_object_class_install_property (object_class,
+ PROP_SOURCE,
+ g_param_spec_object ("source",
+ "ESource of this list",
+ "The ESource of this list",
+ E_TYPE_SOURCE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+gtd_task_list_eds_init (GtdTaskListEds *self)
+{
+}
+
+void
+gtd_task_list_eds_new (GtdProvider *provider,
+ ESource *source,
+ ECalClient *client,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = NULL;
+ NewTaskListData *data;
+
+ data = g_new (NewTaskListData, 1);
+ data->provider = g_object_ref (provider);
+ data->source = g_object_ref (source);
+ data->client = g_object_ref (client);
+
+ task = g_task_new (NULL, cancellable, callback, user_data);
+ g_task_set_task_data (task, data, new_task_list_data_free);
+ g_task_set_source_tag (task, gtd_task_list_eds_new);
+
+ g_idle_add (new_task_list_in_idle_cb, g_steal_pointer (&task));
+}
+
+GtdTaskListEds*
+gtd_task_list_eds_new_finish (GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_TASK (result), NULL);
+ g_return_val_if_fail (!error || !*error, NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+ESource*
+gtd_task_list_eds_get_source (GtdTaskListEds *list)
+{
+ g_return_val_if_fail (GTD_IS_TASK_LIST_EDS (list), NULL);
+
+ return list->source;
+}
+
+void
+gtd_task_list_eds_set_source (GtdTaskListEds *self,
+ ESource *source)
+{
+ ESourceSelectable *selectable;
+ ESourceRefresh *refresh;
+ GdkRGBA color;
+ gboolean is_inbox;
+
+ g_return_if_fail (GTD_IS_TASK_LIST_EDS (self));
+
+ if (!g_set_object (&self->source, source))
+ return;
+
+ is_inbox = g_str_equal (e_source_get_uid (source), GTD_PROVIDER_EDS_INBOX_ID);
+
+ /* Setup color */
+ selectable = E_SOURCE_SELECTABLE (e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST));
+
+ if (!gdk_rgba_parse (&color, e_source_selectable_get_color (selectable)))
+ gdk_rgba_parse (&color, "#ffffff"); /* calendar default color */
+
+ gtd_task_list_set_color (GTD_TASK_LIST (self), &color);
+
+ g_object_bind_property_full (self,
+ "color",
+ selectable,
+ "color",
+ G_BINDING_BIDIRECTIONAL,
+ color_to_string,
+ string_to_color,
+ self,
+ NULL);
+
+ g_signal_connect_object (selectable,
+ "notify::selected",
+ G_CALLBACK (on_source_selectable_selected_changed_cb),
+ self,
+ 0);
+
+ /* Setup tasklist name */
+ if (is_inbox)
+ gtd_task_list_set_name (GTD_TASK_LIST (self), _("Inbox"));
+ else
+ gtd_task_list_set_name (GTD_TASK_LIST (self), e_source_get_display_name (source));
+
+ g_object_bind_property (source,
+ "display-name",
+ self,
+ "name",
+ G_BINDING_BIDIRECTIONAL);
+
+ /* Save the task list every time something changes */
+ g_signal_connect_swapped (source,
+ "notify",
+ G_CALLBACK (save_task_list),
+ self);
+
+ /* Update ::is-removable property */
+ gtd_task_list_set_is_removable (GTD_TASK_LIST (self),
+ e_source_get_removable (source) ||
+ e_source_get_remote_deletable (source));
+
+ g_signal_connect_swapped (source,
+ "notify::removable",
+ G_CALLBACK (on_source_removable_changed_cb),
+ self);
+
+ g_signal_connect_swapped (source,
+ "notify::remote-deletable",
+ G_CALLBACK (on_source_removable_changed_cb),
+ self);
+
+ /* Refresh timeout */
+ refresh = e_source_get_extension (source, E_SOURCE_EXTENSION_REFRESH);
+ e_source_refresh_set_enabled (refresh, TRUE);
+ e_source_refresh_set_interval_minutes (refresh, 5);
+
+ e_source_write (source, NULL, NULL, NULL);
+
+ g_object_notify (G_OBJECT (self), "source");
+}
+
+ECalClient*
+gtd_task_list_eds_get_client (GtdTaskListEds *self)
+{
+ g_return_val_if_fail (GTD_IS_TASK_LIST_EDS (self), NULL);
+
+ return self->client;
+}
diff --git a/src/plugins/eds/gtd-task-list-eds.h b/src/plugins/eds/gtd-task-list-eds.h
new file mode 100644
index 0000000..5a71295
--- /dev/null
+++ b/src/plugins/eds/gtd-task-list-eds.h
@@ -0,0 +1,53 @@
+/* gtd-task-list-eds.h
+ *
+ * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTD_TASK_LIST_EDS_H
+#define GTD_TASK_LIST_EDS_H
+
+#include "endeavour.h"
+
+#include "gtd-eds.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_TASK_LIST_EDS (gtd_task_list_eds_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdTaskListEds, gtd_task_list_eds, GTD, TASK_LIST_EDS, GtdTaskList)
+
+void gtd_task_list_eds_new (GtdProvider *provider,
+ ESource *source,
+ ECalClient *client,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable,
+ gpointer user_data);
+
+GtdTaskListEds* gtd_task_list_eds_new_finish (GAsyncResult *result,
+ GError **error);
+
+ESource* gtd_task_list_eds_get_source (GtdTaskListEds *list);
+
+void gtd_task_list_eds_set_source (GtdTaskListEds *list,
+ ESource *source);
+
+ECalClient* gtd_task_list_eds_get_client (GtdTaskListEds *self);
+
+G_END_DECLS
+
+#endif /* GTD_TASK_LIST_EDS_H */
diff --git a/src/plugins/eds/meson.build b/src/plugins/eds/meson.build
new file mode 100644
index 0000000..50705a3
--- /dev/null
+++ b/src/plugins/eds/meson.build
@@ -0,0 +1,27 @@
+plugins_ldflags += ['-Wl,--undefined=gtd_plugin_eds_register_types']
+
+################
+# Dependencies #
+################
+
+plugins_deps += [
+ dependency('libecal-2.0', version: '>= 3.33.2'),
+ dependency('libedataserver-1.2', version: '>= 3.32.0'),
+]
+
+plugins_sources += files(
+ 'e-source-endeavour.c',
+ 'gtd-plugin-eds.c',
+ 'gtd-provider-eds.c',
+ 'gtd-provider-goa.c',
+ 'gtd-provider-local.c',
+ 'gtd-task-eds.c',
+ 'gtd-task-list-eds.c',
+ 'eds-plugin.c',
+)
+
+plugins_sources += gnome.compile_resources(
+ 'eds-resources',
+ 'eds.gresource.xml',
+ c_name: 'eds_plugin',
+)