summaryrefslogtreecommitdiff
path: root/src/core
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/core
Import Upstream version 43.0upstream/latest
Diffstat (limited to 'src/core')
-rw-r--r--src/core/gtd-activatable.c129
-rw-r--r--src/core/gtd-activatable.h52
-rw-r--r--src/core/gtd-clock.c292
-rw-r--r--src/core/gtd-clock.h33
-rw-r--r--src/core/gtd-log.c103
-rw-r--r--src/core/gtd-log.h27
-rw-r--r--src/core/gtd-manager-protected.h29
-rw-r--r--src/core/gtd-manager.c933
-rw-r--r--src/core/gtd-manager.h86
-rw-r--r--src/core/gtd-notification.c441
-rw-r--r--src/core/gtd-notification.h68
-rw-r--r--src/core/gtd-object.c313
-rw-r--r--src/core/gtd-object.h54
-rw-r--r--src/core/gtd-plugin-manager.c323
-rw-r--r--src/core/gtd-plugin-manager.h45
-rw-r--r--src/core/gtd-provider.c681
-rw-r--r--src/core/gtd-provider.h208
-rw-r--r--src/core/gtd-task-list.c1161
-rw-r--r--src/core/gtd-task-list.h118
-rw-r--r--src/core/gtd-task.c993
-rw-r--r--src/core/gtd-task.h122
21 files changed, 6211 insertions, 0 deletions
diff --git a/src/core/gtd-activatable.c b/src/core/gtd-activatable.c
new file mode 100644
index 0000000..fcfd60e
--- /dev/null
+++ b/src/core/gtd-activatable.c
@@ -0,0 +1,129 @@
+/* gtd-activatable.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 "GtdActivatable"
+
+#include "gtd-activatable.h"
+#include "gtd-panel.h"
+#include "gtd-provider.h"
+
+/**
+ * SECTION:gtd-activatable
+ * @short_description:entry point for plugins
+ * @title:GtdActivatable
+ * @stability:Unstable
+ *
+ * The #GtdActivatable interface is the interface plugins must
+ * implement in order to be seen by Endeavour.
+ *
+ * When plugins are loaded, the gtd_activatable_activate() vfunc
+ * is called. Use this vfunc to load anything that depends on
+ * Endeavour.
+ *
+ * When plugins are unloaded, the gtd_activatable_deactivate() vfunc
+ * if called. Ideally, the implementation should undo everything that
+ * was done on gtd_activatable_activate().
+ *
+ * A plugin implementation may expose one or more #GtdProvider instances,
+ * which are the data sources of Endeavour. See the 'eds' plugin for
+ * a reference on how to expose one ('local') and multiple (Online Accounts)
+ * providers.
+ *
+ * Plugins may also expose one or more #GtdPanel implementations.
+ *
+ * Optionally, a plugin may expose a preferences panel. See gtd_activatable_get_preferences_panel().
+ */
+
+G_DEFINE_INTERFACE (GtdActivatable, gtd_activatable, G_TYPE_OBJECT)
+
+static void
+gtd_activatable_default_init (GtdActivatableInterface *iface)
+{
+ /**
+ * GtdActivatable::preferences-panel:
+ *
+ * The preferences panel of the plugin, or %NULL.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("preferences-panel",
+ "Preferences panel",
+ "The preferences panel of the plugins",
+ GTK_TYPE_WIDGET,
+ G_PARAM_READABLE));
+}
+
+/**
+ * gtd_activatable_activate:
+ * @activatable: a #GtdActivatable
+ *
+ * Activates the extension. This is the starting point where
+ * the implementation does everything it needs to do. Avoid
+ * doing it earlier than this call.
+ *
+ * This function is called after the extension is loaded and
+ * the signals are connected. If you want to do anything before
+ * that, the _init function should be used instead.
+ */
+void
+gtd_activatable_activate (GtdActivatable *activatable)
+{
+ g_return_if_fail (GTD_IS_ACTIVATABLE (activatable));
+
+ if (GTD_ACTIVATABLE_GET_IFACE (activatable)->activate)
+ GTD_ACTIVATABLE_GET_IFACE (activatable)->activate (activatable);
+}
+
+/**
+ * gtd_activatable_deactivate:
+ * @activatable: a #GtdActivatable
+ *
+ * Deactivates the extension. Here, the extension should remove
+ * all providers and panels it set.
+ *
+ * This function is called before the extension is removed. At
+ * this point, the plugin manager already removed all providers
+ * and widgets this extension exported. If you want to do anything
+ * after the extension is removed, use GObject::finalize instead.
+ */
+void
+gtd_activatable_deactivate (GtdActivatable *activatable)
+{
+ g_return_if_fail (GTD_IS_ACTIVATABLE (activatable));
+
+ if (GTD_ACTIVATABLE_GET_IFACE (activatable)->deactivate)
+ GTD_ACTIVATABLE_GET_IFACE (activatable)->deactivate (activatable);
+}
+
+/**
+ * gtd_activatable_get_preferences_panel:
+ * @activatable: a #GtdActivatable
+ *
+ * Retrieve the preferences panel of @activatable if any.
+ *
+ * Returns: (transfer none)(nullable): a #GtkWidget, or %NULL
+ */
+GtkWidget*
+gtd_activatable_get_preferences_panel (GtdActivatable *activatable)
+{
+ g_return_val_if_fail (GTD_IS_ACTIVATABLE (activatable), NULL);
+
+ if (GTD_ACTIVATABLE_GET_IFACE (activatable)->get_preferences_panel)
+ return GTD_ACTIVATABLE_GET_IFACE (activatable)->get_preferences_panel (activatable);
+
+ return NULL;
+}
diff --git a/src/core/gtd-activatable.h b/src/core/gtd-activatable.h
new file mode 100644
index 0000000..2e26375
--- /dev/null
+++ b/src/core/gtd-activatable.h
@@ -0,0 +1,52 @@
+/* gtd-activatable.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_ACTIVATABLE_H
+#define GTD_ACTIVATABLE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <libpeas/peas.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_ACTIVATABLE (gtd_activatable_get_type ())
+
+G_DECLARE_INTERFACE (GtdActivatable, gtd_activatable, GTD, ACTIVATABLE, GObject)
+
+struct _GtdActivatableInterface
+{
+ GTypeInterface parent;
+
+ void (*activate) (GtdActivatable *activatable);
+
+ void (*deactivate) (GtdActivatable *activatable);
+
+ GtkWidget* (*get_preferences_panel) (GtdActivatable *activatable);
+};
+
+void gtd_activatable_activate (GtdActivatable *activatable);
+
+void gtd_activatable_deactivate (GtdActivatable *activatable);
+
+GtkWidget* gtd_activatable_get_preferences_panel (GtdActivatable *activatable);
+
+G_END_DECLS
+
+#endif /* GTD_ACTIVATABLE_H */
diff --git a/src/core/gtd-clock.c b/src/core/gtd-clock.c
new file mode 100644
index 0000000..68263c6
--- /dev/null
+++ b/src/core/gtd-clock.c
@@ -0,0 +1,292 @@
+/* gtd-clock.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 "GtdClock"
+
+#include "gtd-clock.h"
+#include "gtd-debug.h"
+
+#include <gio/gio.h>
+
+struct _GtdClock
+{
+ GtdObject parent;
+
+ guint timeout_id;
+
+ GDateTime *current;
+
+ GDBusProxy *logind;
+ GCancellable *cancellable;
+};
+
+static gboolean timeout_cb (gpointer user_data);
+
+G_DEFINE_TYPE (GtdClock, gtd_clock, GTD_TYPE_OBJECT)
+
+enum
+{
+ DAY_CHANGED,
+ HOUR_CHANGED,
+ MINUTE_CHANGED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0, };
+
+/*
+ * Auxiliary methods
+ */
+
+static void
+update_current_date (GtdClock *self)
+{
+ g_autoptr (GDateTime) now = NULL;
+ gboolean minute_changed;
+ gboolean hour_changed;
+ gboolean day_changed;
+
+ GTD_ENTRY;
+
+ now = g_date_time_new_now_local ();
+
+ day_changed = g_date_time_get_year (now) != g_date_time_get_year (self->current) ||
+ g_date_time_get_day_of_year (now) != g_date_time_get_day_of_year (self->current);
+ hour_changed = day_changed || g_date_time_get_hour (now) != g_date_time_get_hour (self->current);
+ minute_changed = hour_changed || g_date_time_get_minute (now) != g_date_time_get_minute (self->current);
+
+ if (day_changed)
+ g_signal_emit (self, signals[DAY_CHANGED], 0);
+
+ if (hour_changed)
+ g_signal_emit (self, signals[HOUR_CHANGED], 0);
+
+ if (minute_changed)
+ g_signal_emit (self, signals[MINUTE_CHANGED], 0);
+
+ GTD_TRACE_MSG ("Ticking clock");
+
+ g_clear_pointer (&self->current, g_date_time_unref);
+ self->current = g_date_time_ref (now);
+
+ GTD_EXIT;
+}
+
+static void
+schedule_update (GtdClock *self)
+{
+ g_autoptr (GDateTime) now = NULL;
+ guint seconds_between;
+
+ /* Remove the previous timeout if we came from resume */
+ if (self->timeout_id > 0)
+ {
+ g_source_remove (self->timeout_id);
+ self->timeout_id = 0;
+ }
+
+ now = g_date_time_new_now_local ();
+
+ seconds_between = 60 - g_date_time_get_second (now);
+
+ self->timeout_id = g_timeout_add_seconds (seconds_between, timeout_cb, self);
+}
+
+/*
+ * Callbacks
+ */
+
+static void
+logind_signal_received_cb (GDBusProxy *logind,
+ const gchar *sender,
+ const gchar *signal,
+ GVariant *params,
+ GtdClock *self)
+{
+ GVariant *child;
+ gboolean resuming;
+
+ if (!g_str_equal (signal, "PrepareForSleep"))
+ return;
+
+ child = g_variant_get_child_value (params, 0);
+ resuming = !g_variant_get_boolean (child);
+
+ /* Only emit :update when resuming */
+ if (resuming)
+ {
+ /* Reschedule the daily timeout */
+ update_current_date (self);
+ schedule_update (self);
+ }
+
+ g_clear_pointer (&child, g_variant_unref);
+}
+
+static void
+login_proxy_acquired_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+ GtdClock *self;
+
+ self = GTD_CLOCK (user_data);
+
+ gtd_object_pop_loading (GTD_OBJECT (self));
+
+ self->logind = g_dbus_proxy_new_for_bus_finish (res, &error);
+
+ if (error)
+ {
+ g_warning ("Error acquiring org.freedesktop.login1: %s", error->message);
+ return;
+ }
+
+ g_signal_connect (self->logind,
+ "g-signal",
+ G_CALLBACK (logind_signal_received_cb),
+ self);
+}
+
+static gboolean
+timeout_cb (gpointer user_data)
+{
+ GtdClock *self = user_data;
+
+ self->timeout_id = 0;
+
+ update_current_date (self);
+ schedule_update (self);
+
+ return G_SOURCE_REMOVE;
+}
+
+
+/*
+ * GObject overrides
+ */
+static void
+gtd_clock_finalize (GObject *object)
+{
+ GtdClock *self = (GtdClock *)object;
+
+ g_cancellable_cancel (self->cancellable);
+
+ if (self->timeout_id > 0)
+ {
+ g_source_remove (self->timeout_id);
+ self->timeout_id = 0;
+ }
+
+ g_clear_pointer (&self->current, g_date_time_unref);
+
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->logind);
+
+ G_OBJECT_CLASS (gtd_clock_parent_class)->finalize (object);
+}
+
+static void
+gtd_clock_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+gtd_clock_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+gtd_clock_class_init (GtdClockClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_clock_finalize;
+ object_class->get_property = gtd_clock_get_property;
+ object_class->set_property = gtd_clock_set_property;
+
+ /**
+ * GtdClock:day-changed:
+ *
+ * Emited when the day changes.
+ */
+ signals[DAY_CHANGED] = g_signal_new ("day-changed",
+ GTD_TYPE_CLOCK,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+ /**
+ * GtdClock:hour-changed:
+ *
+ * Emited when the current hour changes.
+ */
+ signals[HOUR_CHANGED] = g_signal_new ("hour-changed",
+ GTD_TYPE_CLOCK,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+ /**
+ * GtdClock:minute-changed:
+ *
+ * Emited when the current minute changes.
+ */
+ signals[MINUTE_CHANGED] = g_signal_new ("minute-changed",
+ GTD_TYPE_CLOCK,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+gtd_clock_init (GtdClock *self)
+{
+ gtd_object_push_loading (GTD_OBJECT (self));
+
+ self->current = g_date_time_new_now_local ();
+ self->cancellable = g_cancellable_new ();
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.login1",
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ self->cancellable,
+ login_proxy_acquired_cb,
+ self);
+
+ schedule_update (self);
+}
+
+GtdClock*
+gtd_clock_new (void)
+{
+ return g_object_new (GTD_TYPE_CLOCK, NULL);
+}
diff --git a/src/core/gtd-clock.h b/src/core/gtd-clock.h
new file mode 100644
index 0000000..ab09b0b
--- /dev/null
+++ b/src/core/gtd-clock.h
@@ -0,0 +1,33 @@
+/* gtd-clock.h
+ *
+ * Copyright (C) 2017 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-object.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_CLOCK (gtd_clock_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdClock, gtd_clock, GTD, CLOCK, GtdObject)
+
+GtdClock* gtd_clock_new (void);
+
+G_END_DECLS
diff --git a/src/core/gtd-log.c b/src/core/gtd-log.c
new file mode 100644
index 0000000..fc2645d
--- /dev/null
+++ b/src/core/gtd-log.c
@@ -0,0 +1,103 @@
+/* gtd-log.c
+ *
+ * Copyright (C) 2018 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 "gtd-debug.h"
+#include "gtd-log.h"
+
+#include <unistd.h>
+#include <glib.h>
+
+G_LOCK_DEFINE_STATIC (channel_lock);
+
+GIOChannel *standard_channel = NULL;
+
+static const gchar* ignored_domains[] =
+{
+ "GdkPixbuf",
+ NULL
+};
+
+static const gchar *
+log_level_str (GLogLevelFlags log_level)
+{
+ switch (((gulong)log_level & G_LOG_LEVEL_MASK))
+ {
+ case G_LOG_LEVEL_ERROR: return " \033[1;31mERROR\033[0m";
+ case G_LOG_LEVEL_CRITICAL: return "\033[1;35mCRITICAL\033[0m";
+ case G_LOG_LEVEL_WARNING: return " \033[1;33mWARNING\033[0m";
+ case G_LOG_LEVEL_MESSAGE: return " \033[1;34mMESSAGE\033[0m";
+ case G_LOG_LEVEL_INFO: return " \033[1;32mINFO\033[0m";
+ case G_LOG_LEVEL_DEBUG: return " \033[1;32mDEBUG\033[0m";
+ case GTD_LOG_LEVEL_TRACE: return " \033[1;36mTRACE\033[0m";
+
+ default:
+ return " UNKNOWN";
+ }
+}
+
+static void
+gtd_log_handler (const gchar *domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ g_autoptr (GDateTime) now = NULL;
+ g_autofree gchar *buffer = NULL;
+ g_autofree gchar *ftime = NULL;
+ const gchar *level;
+ gint microsecond;
+
+ /* Skip ignored log domains */
+ if (domain && g_strv_contains (ignored_domains, domain))
+ return;
+
+ level = log_level_str (log_level);
+ now = g_date_time_new_now_local ();
+ ftime = g_date_time_format (now, "%H:%M:%S");
+ microsecond = g_date_time_get_microsecond (now);
+ buffer = g_strdup_printf ("%s.%4.4d %28s: %s: %s\n",
+ ftime,
+ microsecond,
+ domain,
+ level,
+ message);
+
+ /* Safely write to the channel */
+ G_LOCK (channel_lock);
+
+ g_io_channel_write_chars (standard_channel, buffer, -1, NULL, NULL);
+ g_io_channel_flush (standard_channel, NULL);
+
+ G_UNLOCK (channel_lock);
+}
+
+void
+gtd_log_init (void)
+{
+ static gsize initialized = FALSE;
+
+ if (g_once_init_enter (&initialized))
+ {
+ standard_channel = g_io_channel_unix_new (STDOUT_FILENO);
+
+ g_log_set_default_handler (gtd_log_handler, NULL);
+
+ g_once_init_leave (&initialized, TRUE);
+ }
+}
+
diff --git a/src/core/gtd-log.h b/src/core/gtd-log.h
new file mode 100644
index 0000000..0ad53a8
--- /dev/null
+++ b/src/core/gtd-log.h
@@ -0,0 +1,27 @@
+/* gtd-log.h
+ *
+ * Copyright (C) 2017 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 <glib-object.h>
+
+G_BEGIN_DECLS
+
+void gtd_log_init (void);
+
+G_END_DECLS
diff --git a/src/core/gtd-manager-protected.h b/src/core/gtd-manager-protected.h
new file mode 100644
index 0000000..142419f
--- /dev/null
+++ b/src/core/gtd-manager-protected.h
@@ -0,0 +1,29 @@
+/* gtd-manager-protected.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-types.h"
+
+G_BEGIN_DECLS
+
+void gtd_manager_load_plugins (GtdManager *manager);
+
+GtdPluginManager* gtd_manager_get_plugin_manager (GtdManager *manager);
+
+G_END_DECLS
diff --git a/src/core/gtd-manager.c b/src/core/gtd-manager.c
new file mode 100644
index 0000000..b499b76
--- /dev/null
+++ b/src/core/gtd-manager.c
@@ -0,0 +1,933 @@
+/* gtd-manager.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 "GtdManager"
+
+#include "models/gtd-list-store.h"
+#include "models/gtd-task-model-private.h"
+#include "gtd-clock.h"
+#include "gtd-debug.h"
+#include "gtd-manager.h"
+#include "gtd-manager-protected.h"
+#include "gtd-notification.h"
+#include "gtd-panel.h"
+#include "gtd-plugin-manager.h"
+#include "gtd-provider.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+#include "gtd-utils.h"
+#include "gtd-workspace.h"
+
+#include <glib/gi18n.h>
+
+/**
+ * SECTION:gtd-manager
+ * @short_description:bridge between plugins and Endeavour
+ * @title:GtdManager
+ * @stability:Unstable
+ * @see_also:#GtdNotification,#GtdActivatable
+ *
+ * The #GtdManager object is a singleton object that exposes all the data
+ * inside the plugin to Endeavour, and vice-versa. From here, plugins have
+ * access to all the tasklists, tasks and panels of the other plugins.
+ *
+ * Objects can use gtd_manager_emit_error_message() to send errors to
+ * Endeavour. This will create a #GtdNotification internally.
+ */
+
+struct _GtdManager
+{
+ GtdObject parent;
+
+ GSettings *settings;
+ GtdPluginManager *plugin_manager;
+
+ GListModel *inbox_model;
+ GListModel *lists_model;
+ GListModel *providers_model;
+ GListModel *tasks_model;
+ GListModel *unarchived_tasks_model;
+
+ GList *providers;
+ GtdProvider *default_provider;
+ GtdClock *clock;
+
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (GtdManager, gtd_manager, GTD_TYPE_OBJECT)
+
+/* Singleton instance */
+static GtdManager *gtd_manager_instance = NULL;
+
+enum
+{
+ LIST_ADDED,
+ LIST_CHANGED,
+ LIST_REMOVED,
+ SHOW_ERROR_MESSAGE,
+ SHOW_NOTIFICATION,
+ PROVIDER_ADDED,
+ PROVIDER_REMOVED,
+ NUM_SIGNALS
+};
+
+enum
+{
+ PROP_0,
+ PROP_DEFAULT_PROVIDER,
+ PROP_CLOCK,
+ PROP_PLUGIN_MANAGER,
+ LAST_PROP
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+
+/*
+ * Auxiliary methods
+ */
+
+static void
+check_provider_is_default (GtdManager *self,
+ GtdProvider *provider)
+{
+ g_autofree gchar *default_provider = NULL;
+
+ default_provider = g_settings_get_string (self->settings, "default-provider");
+
+ if (g_strcmp0 (default_provider, gtd_provider_get_id (provider)) == 0)
+ gtd_manager_set_default_provider (self, provider);
+}
+
+
+/*
+ * Callbacks
+ */
+
+static gboolean
+filter_archived_lists_func (gpointer item,
+ gpointer user_data)
+{
+ GtdTaskList *list;
+ GtdTask *task;
+
+ task = (GtdTask*) item;
+ list = gtd_task_get_list (task);
+
+ return !gtd_task_list_get_archived (list);
+}
+
+static gboolean
+filter_inbox_cb (gpointer item,
+ gpointer user_data)
+{
+ GtdProvider *provider = gtd_task_list_get_provider (item);
+
+ return gtd_provider_get_inbox (provider) == item;
+}
+
+static gint
+compare_lists_cb (GtdTaskList *list_a,
+ GtdTaskList *list_b,
+ gpointer user_data)
+{
+ gint result;
+
+ /* First, compare by their providers */
+ result = gtd_provider_compare (gtd_task_list_get_provider (list_a), gtd_task_list_get_provider (list_b));
+
+ if (result != 0)
+ return result;
+
+ return gtd_collate_compare_strings (gtd_task_list_get_name (list_a), gtd_task_list_get_name (list_b));
+}
+
+static void
+on_task_list_modified_cb (GtdTaskList *list,
+ GtdTask *task,
+ GtdManager *self)
+{
+ GTD_ENTRY;
+ g_signal_emit (self, signals[LIST_CHANGED], 0, list);
+ GTD_EXIT;
+}
+
+static void
+on_list_added_cb (GtdProvider *provider,
+ GtdTaskList *list,
+ GtdManager *self)
+{
+ GTD_ENTRY;
+
+ gtd_list_store_insert_sorted (GTD_LIST_STORE (self->lists_model),
+ list,
+ (GCompareDataFunc) compare_lists_cb,
+ self);
+
+ g_signal_connect (list,
+ "task-added",
+ G_CALLBACK (on_task_list_modified_cb),
+ self);
+
+ g_signal_connect (list,
+ "task-updated",
+ G_CALLBACK (on_task_list_modified_cb),
+ self);
+
+ g_signal_connect (list,
+ "task-removed",
+ G_CALLBACK (on_task_list_modified_cb),
+ self);
+
+ g_signal_emit (self, signals[LIST_ADDED], 0, list);
+
+ GTD_EXIT;
+}
+
+static void
+on_list_changed_cb (GtdProvider *provider,
+ GtdTaskList *list,
+ GtdManager *self)
+{
+ GtkFilter *filter;
+
+ GTD_ENTRY;
+
+ gtd_list_store_sort (GTD_LIST_STORE (self->lists_model),
+ (GCompareDataFunc) compare_lists_cb,
+ self);
+
+ filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (self->unarchived_tasks_model));
+ gtk_filter_changed (filter, GTK_FILTER_CHANGE_DIFFERENT);
+
+ g_signal_emit (self, signals[LIST_CHANGED], 0, list);
+
+ GTD_EXIT;
+}
+
+static void
+on_list_removed_cb (GtdProvider *provider,
+ GtdTaskList *list,
+ GtdManager *self)
+{
+ GTD_ENTRY;
+
+ if (!list)
+ GTD_RETURN ();
+
+ gtd_list_store_remove (GTD_LIST_STORE (self->lists_model), list);
+
+ g_signal_handlers_disconnect_by_func (list,
+ on_task_list_modified_cb,
+ self);
+
+ g_signal_emit (self, signals[LIST_REMOVED], 0, list);
+
+ GTD_EXIT;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_manager_finalize (GObject *object)
+{
+ GtdManager *self = (GtdManager *)object;
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->plugin_manager);
+ g_clear_object (&self->settings);
+ g_clear_object (&self->clock);
+ g_clear_object (&self->unarchived_tasks_model);
+ g_clear_object (&self->lists_model);
+ g_clear_object (&self->inbox_model);
+
+ G_OBJECT_CLASS (gtd_manager_parent_class)->finalize (object);
+}
+
+static void
+gtd_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdManager *self = (GtdManager *) object;
+
+ switch (prop_id)
+ {
+ case PROP_DEFAULT_PROVIDER:
+ g_value_set_object (value, self->default_provider);
+ break;
+
+ case PROP_CLOCK:
+ g_value_set_object (value, self->clock);
+ break;
+
+ case PROP_PLUGIN_MANAGER:
+ g_value_set_object (value, self->plugin_manager);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdManager *self = (GtdManager *) object;
+
+ switch (prop_id)
+ {
+ case PROP_DEFAULT_PROVIDER:
+ if (g_set_object (&self->default_provider, g_value_get_object (value)))
+ g_object_notify (object, "default-provider");
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_manager_class_init (GtdManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_manager_finalize;
+ object_class->get_property = gtd_manager_get_property;
+ object_class->set_property = gtd_manager_set_property;
+
+ /**
+ * GtdManager::default-provider:
+ *
+ * The default provider.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_DEFAULT_PROVIDER,
+ g_param_spec_object ("default-provider",
+ "The default provider of the application",
+ "The default provider of the application",
+ GTD_TYPE_PROVIDER,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtdManager::clock:
+ *
+ * The underlying clock of Endeavour.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CLOCK,
+ g_param_spec_object ("clock",
+ "The clock",
+ "The clock of the application",
+ GTD_TYPE_CLOCK,
+ G_PARAM_READABLE));
+
+ /**
+ * GtdManager::plugin-manager:
+ *
+ * The plugin manager.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_PLUGIN_MANAGER,
+ g_param_spec_object ("plugin-manager",
+ "The plugin manager",
+ "The plugin manager of the application",
+ GTD_TYPE_PLUGIN_MANAGER,
+ G_PARAM_READABLE));
+
+ /**
+ * GtdManager::list-added:
+ * @manager: a #GtdManager
+ * @list: a #GtdTaskList
+ *
+ * The ::list-added signal is emmited after a #GtdTaskList
+ * is connected.
+ */
+ signals[LIST_ADDED] = g_signal_new ("list-added",
+ GTD_TYPE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK_LIST);
+
+ /**
+ * GtdManager::list-changed:
+ * @manager: a #GtdManager
+ * @list: a #GtdTaskList
+ *
+ * The ::list-changed signal is emmited after a #GtdTaskList
+ * has any of it's properties changed.
+ */
+ signals[LIST_CHANGED] = g_signal_new ("list-changed",
+ GTD_TYPE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK_LIST);
+
+ /**
+ * GtdManager::list-removed:
+ * @manager: a #GtdManager
+ * @list: a #GtdTaskList
+ *
+ * The ::list-removed signal is emmited after a #GtdTaskList
+ * is disconnected.
+ */
+ signals[LIST_REMOVED] = g_signal_new ("list-removed",
+ GTD_TYPE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK_LIST);
+
+ /**
+ * GtdManager::show-error-message:
+ * @manager: a #GtdManager
+ * @primary_text: the primary message
+ * @secondary_text: the detailed explanation of the error or the text to the notification button.
+ * @action : optionally action of type GtdNotificationActionFunc ignored if it's null.
+ * @user_data : user data passed to the action.
+ *
+ * Notifies about errors, and sends the error message for widgets
+ * to display.
+ */
+ signals[SHOW_ERROR_MESSAGE] = g_signal_new ("show-error-message",
+ GTD_TYPE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 4,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+
+ /**
+ * GtdManager::show-notification:
+ * @manager: a #GtdManager
+ * @notification: the #GtdNotification
+ *
+ * Sends a notification.
+ */
+ signals[SHOW_NOTIFICATION] = g_signal_new ("show-notification",
+ GTD_TYPE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_NOTIFICATION);
+
+ /**
+ * GtdManager::provider-added:
+ * @manager: a #GtdManager
+ * @provider: a #GtdProvider
+ *
+ * The ::provider-added signal is emmited after a #GtdProvider
+ * is added.
+ */
+ signals[PROVIDER_ADDED] = g_signal_new ("provider-added",
+ GTD_TYPE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_PROVIDER);
+
+ /**
+ * GtdManager::provider-removed:
+ * @manager: a #GtdManager
+ * @provider: a #GtdProvider
+ *
+ * The ::provider-removed signal is emmited after a #GtdProvider
+ * is removed from the list.
+ */
+ signals[PROVIDER_REMOVED] = g_signal_new ("provider-removed",
+ GTD_TYPE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_PROVIDER);
+}
+
+
+static void
+gtd_manager_init (GtdManager *self)
+{
+ GtkCustomFilter *archived_lists_filter;
+ GtkCustomFilter *inbox_filter;
+
+ inbox_filter = gtk_custom_filter_new (filter_inbox_cb, self, NULL);
+ archived_lists_filter = gtk_custom_filter_new (filter_archived_lists_func, self, NULL);
+
+ self->settings = g_settings_new ("org.gnome.todo");
+ self->plugin_manager = gtd_plugin_manager_new ();
+ self->clock = gtd_clock_new ();
+ self->cancellable = g_cancellable_new ();
+ self->lists_model = (GListModel*) gtd_list_store_new (GTD_TYPE_TASK_LIST);
+ self->inbox_model = (GListModel*) gtk_filter_list_model_new (self->lists_model,
+ GTK_FILTER (inbox_filter));
+ self->tasks_model = (GListModel*) _gtd_task_model_new (self);
+ self->unarchived_tasks_model = (GListModel*) gtk_filter_list_model_new (self->tasks_model,
+ GTK_FILTER (archived_lists_filter));
+ self->providers_model = (GListModel*) gtd_list_store_new (GTD_TYPE_PROVIDER);
+}
+
+/**
+ * gtd_manager_get_default:
+ *
+ * Retrieves the singleton #GtdManager instance. You should always
+ * use this function instead of @gtd_manager_new.
+ *
+ * Returns: (transfer none): the singleton #GtdManager instance.
+ */
+GtdManager*
+gtd_manager_get_default (void)
+{
+ if (!gtd_manager_instance)
+ gtd_manager_instance = gtd_manager_new ();
+
+ return gtd_manager_instance;
+}
+
+GtdManager*
+gtd_manager_new (void)
+{
+ return g_object_new (GTD_TYPE_MANAGER, NULL);
+}
+
+/**
+ * gtd_manager_get_providers:
+ * @manager: a #GtdManager
+ *
+ * Retrieves the list of available #GtdProvider.
+ *
+ * Returns: (transfer container) (element-type Gtd.Provider): a newly allocated #GList of
+ * #GtdStorage. Free with @g_list_free after use.
+ */
+GList*
+gtd_manager_get_providers (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return g_list_copy (self->providers);
+}
+
+/**
+ * gtd_manager_add_provider:
+ * @self: a #GtdManager
+ * @provider: a #GtdProvider
+ *
+ * Adds @provider to the list of providers.
+ */
+void
+gtd_manager_add_provider (GtdManager *self,
+ GtdProvider *provider)
+{
+ g_autoptr (GList) lists = NULL;
+ GList *l;
+
+ g_return_if_fail (GTD_IS_MANAGER (self));
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+
+ GTD_ENTRY;
+
+ self->providers = g_list_prepend (self->providers, provider);
+ gtd_list_store_append (GTD_LIST_STORE (self->providers_model), provider);
+
+ /* Add lists */
+ lists = gtd_provider_get_task_lists (provider);
+
+ for (l = lists; l != NULL; l = l->next)
+ on_list_added_cb (provider, l->data, self);
+
+ g_object_connect (provider,
+ "signal::list-added", G_CALLBACK (on_list_added_cb), self,
+ "signal::list-changed", G_CALLBACK (on_list_changed_cb), self,
+ "signal::list-removed", G_CALLBACK (on_list_removed_cb), self,
+ NULL);
+
+ /* If we just added the default provider, update the property */
+ check_provider_is_default (self, provider);
+
+ g_signal_emit (self, signals[PROVIDER_ADDED], 0, provider);
+
+ GTD_EXIT;
+}
+
+/**
+ * gtd_manager_remove_provider:
+ * @self: a #GtdManager
+ * @provider: a #GtdProvider
+ *
+ * Removes @provider from the list of providers.
+ */
+void
+gtd_manager_remove_provider (GtdManager *self,
+ GtdProvider *provider)
+{
+ g_autoptr (GList) lists = NULL;
+ GList *l;
+
+ g_return_if_fail (GTD_IS_MANAGER (self));
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+
+ GTD_ENTRY;
+
+ self->providers = g_list_remove (self->providers, provider);
+ gtd_list_store_remove (GTD_LIST_STORE (self->providers_model), provider);
+
+ /* Remove lists */
+ lists = gtd_provider_get_task_lists (provider);
+
+ for (l = lists; l != NULL; l = l->next)
+ on_list_removed_cb (provider, l->data, self);
+
+ g_signal_handlers_disconnect_by_func (provider, on_list_added_cb, self);
+ g_signal_handlers_disconnect_by_func (provider, on_list_changed_cb, self);
+ g_signal_handlers_disconnect_by_func (provider, on_list_removed_cb, self);
+
+ g_signal_emit (self, signals[PROVIDER_REMOVED], 0, provider);
+
+ GTD_EXIT;
+}
+
+
+/**
+ * gtd_manager_get_default_provider:
+ * @manager: a #GtdManager
+ *
+ * Retrieves the default provider location. Default is "local".
+ *
+ * Returns: (transfer none): the default provider.
+ */
+GtdProvider*
+gtd_manager_get_default_provider (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->default_provider;
+}
+
+/**
+ * gtd_manager_set_default_provider:
+ * @manager: a #GtdManager
+ * @provider: (nullable): the default provider.
+ *
+ * Sets the provider.
+ */
+void
+gtd_manager_set_default_provider (GtdManager *self,
+ GtdProvider *provider)
+{
+ g_return_if_fail (GTD_IS_MANAGER (self));
+
+ if (!g_set_object (&self->default_provider, provider))
+ return;
+
+ g_settings_set_string (self->settings,
+ "default-provider",
+ provider ? gtd_provider_get_id (provider) : "local");
+
+ g_object_notify (G_OBJECT (self), "default-provider");
+}
+
+/**
+ * gtd_manager_get_inbox:
+ * @self: a #GtdManager
+ *
+ * Retrieves the local inbox.
+ *
+ * Returns: (transfer none)(nullable): a #GtdTaskList
+ */
+GtdTaskList*
+gtd_manager_get_inbox (GtdManager *self)
+{
+ GList *l;
+
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ for (l = self->providers; l; l = l->next)
+ {
+ if (g_str_equal (gtd_provider_get_id (l->data), "local"))
+ return gtd_provider_get_inbox (l->data);
+ }
+
+ return NULL;
+}
+
+/**
+ * gtd_manager_get_settings:
+ * @manager: a #GtdManager
+ *
+ * Retrieves the internal #GSettings from @manager.
+ *
+ * Returns: (transfer none): the internal #GSettings of @manager
+ */
+GSettings*
+gtd_manager_get_settings (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->settings;
+}
+
+/**
+ * gtd_manager_get_is_first_run:
+ * @manager: a #GtdManager
+ *
+ * Retrieves the 'first-run' setting.
+ *
+ * Returns: %TRUE if Endeavour was never run before, %FALSE otherwise.
+ */
+gboolean
+gtd_manager_get_is_first_run (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), FALSE);
+
+ return g_settings_get_boolean (self->settings, "first-run");
+}
+
+/**
+ * gtd_manager_set_is_first_run:
+ * @manager: a #GtdManager
+ * @is_first_run: %TRUE to make it first run, %FALSE otherwise.
+ *
+ * Sets the 'first-run' setting.
+ */
+void
+gtd_manager_set_is_first_run (GtdManager *self,
+ gboolean is_first_run)
+{
+ g_return_if_fail (GTD_IS_MANAGER (self));
+
+ g_settings_set_boolean (self->settings, "first-run", is_first_run);
+}
+
+/**
+ * gtd_manager_emit_error_message:
+ * @self: a #GtdManager
+ * @title: (nullable): the title of the error
+ * @description: (nullable): detailed description of the error
+ * @function: (scope call)(nullable): function to be called when the notification is dismissed
+ * @user_data: user data
+ *
+ * Reports an error.
+ */
+void
+gtd_manager_emit_error_message (GtdManager *self,
+ const gchar *title,
+ const gchar *description,
+ GtdErrorActionFunc function,
+ gpointer user_data)
+{
+ g_return_if_fail (GTD_IS_MANAGER (self));
+
+ g_signal_emit (self,
+ signals[SHOW_ERROR_MESSAGE],
+ 0,
+ title,
+ description,
+ function,
+ user_data);
+}
+
+/**
+ * gtd_manager_send_notification:
+ * @self: a #GtdManager
+ * @notification: a #GtdNotification
+ *
+ * Sends a notification to the notification system.
+ */
+void
+gtd_manager_send_notification (GtdManager *self,
+ GtdNotification *notification)
+{
+ g_return_if_fail (GTD_IS_MANAGER (self));
+
+ g_signal_emit (self, signals[SHOW_NOTIFICATION], 0, notification);
+}
+
+/**
+ * gtd_manager_get_clock:
+ * @self: a #GtdManager
+ *
+ * Retrieves the #GtdClock from @self. You can use the
+ * clock to know when your code should be updated.
+ *
+ * Returns: (transfer none): a #GtdClock
+ */
+GtdClock*
+gtd_manager_get_clock (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->clock;
+}
+
+/**
+ * gtd_manager_get_task_lists_model:
+ * @self: a #GtdManager
+ *
+ * Retrieves the #GListModel containing #GtdTaskLists from
+ * @self. You can use the this model to bind to GtkListBox
+ * or other widgets.
+ *
+ * The model is sorted.
+ *
+ * Returns: (transfer none): a #GListModel
+ */
+GListModel*
+gtd_manager_get_task_lists_model (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->lists_model;
+}
+
+
+/**
+ * gtd_manager_get_all_tasks_model:
+ * @self: a #GtdManager
+ *
+ * Retrieves the #GListModel containing #GtdTasks from
+ * @self. You can use the this model to bind to GtkListBox
+ * or other widgets.
+ *
+ * The model is sorted.
+ *
+ * Returns: (transfer none): a #GListModel
+ */
+GListModel*
+gtd_manager_get_all_tasks_model (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->tasks_model;
+}
+
+/**
+ * gtd_manager_get_tasks_model:
+ * @self: a #GtdManager
+ *
+ * Retrieves the #GListModel containing #GtdTasks from
+ * @self. You can use the this model to bind to GtkListBox
+ * or other widgets.
+ *
+ * The model returned by this function is filtered to only
+ * contain tasks from unarchived lists. If you need all tasks,
+ * see gtd_manager_get_all_tasks_model().
+ *
+ * The model is sorted.
+ *
+ * Returns: (transfer none): a #GListModel
+ */
+GListModel*
+gtd_manager_get_tasks_model (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->unarchived_tasks_model;
+}
+
+/**
+ * gtd_manager_get_inbox_model:
+ * @self: a #GtdManager
+ *
+ * Retrieves the #GListModel containing #GtdTaskLists that are
+ * inbox.
+ *
+ * Returns: (transfer none): a #GListModel
+ */
+GListModel*
+gtd_manager_get_inbox_model (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->inbox_model;
+}
+
+/**
+ * gtd_manager_get_providers_model:
+ * @self: a #GtdManager
+ *
+ * Retrieves the #GListModel containing #GtdProviders.
+ *
+ * Returns: (transfer none): a #GListModel
+ */
+GListModel*
+gtd_manager_get_providers_model (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->providers_model;
+}
+
+void
+gtd_manager_load_plugins (GtdManager *self)
+{
+ GTD_ENTRY;
+
+ gtd_plugin_manager_load_plugins (self->plugin_manager);
+
+ GTD_EXIT;
+}
+
+GtdPluginManager*
+gtd_manager_get_plugin_manager (GtdManager *self)
+{
+ g_return_val_if_fail (GTD_IS_MANAGER (self), NULL);
+
+ return self->plugin_manager;
+}
diff --git a/src/core/gtd-manager.h b/src/core/gtd-manager.h
new file mode 100644
index 0000000..68d661b
--- /dev/null
+++ b/src/core/gtd-manager.h
@@ -0,0 +1,86 @@
+/* gtd-manager.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 <gio/gio.h>
+
+#include "gtd-object.h"
+#include "gtd-types.h"
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_MANAGER (gtd_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdManager, gtd_manager, GTD, MANAGER, GtdObject)
+
+/**
+ * GtdErrorActionFunc:
+ */
+typedef void (*GtdErrorActionFunc) (GtdNotification *notification,
+ gpointer user_data);
+
+
+GtdManager* gtd_manager_new (void);
+
+GtdManager* gtd_manager_get_default (void);
+
+GList* gtd_manager_get_providers (GtdManager *manager);
+
+void gtd_manager_add_provider (GtdManager *self,
+ GtdProvider *provider);
+
+void gtd_manager_remove_provider (GtdManager *self,
+ GtdProvider *provider);
+
+GtdProvider* gtd_manager_get_default_provider (GtdManager *manager);
+
+void gtd_manager_set_default_provider (GtdManager *manager,
+ GtdProvider *provider);
+
+GtdTaskList* gtd_manager_get_inbox (GtdManager *self);
+
+GSettings* gtd_manager_get_settings (GtdManager *manager);
+
+gboolean gtd_manager_get_is_first_run (GtdManager *manager);
+
+void gtd_manager_set_is_first_run (GtdManager *manager,
+ gboolean is_first_run);
+
+void gtd_manager_emit_error_message (GtdManager *self,
+ const gchar *title,
+ const gchar *description,
+ GtdErrorActionFunc function,
+ gpointer user_data);
+
+void gtd_manager_send_notification (GtdManager *self,
+ GtdNotification *notification);
+
+GtdClock* gtd_manager_get_clock (GtdManager *self);
+
+GListModel* gtd_manager_get_task_lists_model (GtdManager *self);
+
+GListModel* gtd_manager_get_all_tasks_model (GtdManager *self);
+
+GListModel* gtd_manager_get_tasks_model (GtdManager *self);
+
+GListModel* gtd_manager_get_inbox_model (GtdManager *self);
+
+GListModel* gtd_manager_get_providers_model (GtdManager *self);
+
+G_END_DECLS
diff --git a/src/core/gtd-notification.c b/src/core/gtd-notification.c
new file mode 100644
index 0000000..eee3e46
--- /dev/null
+++ b/src/core/gtd-notification.c
@@ -0,0 +1,441 @@
+/* gtd-notification.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ * Copyright (C) 2022 Jamie Murphy <hello@itsjamie.dev>
+ *
+ * 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 "GtdNotification"
+
+#include "gtd-notification.h"
+#include "gtd-object.h"
+
+#include <glib/gi18n.h>
+
+/**
+ * SECTION: gtd-notification
+ * @short_description: An auxiliary class around #AdwToast
+ * @title: GtdNotification
+ * @stability: Stable
+ *
+ * The #GtdNotification represents a notification shown at the top of
+ * the window. It is an auxiliary class around #AdwToast, and is used only
+ * to store toast data and callbacks.
+ *
+ * A notification may have a dismissal action that is called when the toast
+ * is dismissed, whether by user interaction or a timeout
+ *
+ * Optionally, the notification may have a secondary action
+ * (see gtd_notification_set_secondary_action()), shown as a button.
+ *
+ * A user should not create a UI for a #GtdNotification and should instead
+ * pass it to a #GtdManager, via gtd_manager_send_notification()
+ *
+ * Example:
+ * |[
+ * GtdNotification *notification;
+ *
+ * notification = gtd_notification_new ("Something happened!");
+ *
+ * gtd_notification_set_dismissal_action (notification,
+ * called_when_notification_is_dismissed,
+ * self);
+ *
+ * gtd_notification_set_secondary_action (notification,
+ * "Details",
+ * show_details,
+ * self);
+ * [...]
+ * ]|
+ */
+
+typedef struct
+{
+ gchar *text;
+
+ GtdNotificationActionFunc dismissal_action;
+ gboolean has_dismissal_action;
+ gpointer dismissal_action_data;
+
+ GtdNotificationActionFunc secondary_action;
+ gboolean has_secondary_action;
+ gpointer secondary_action_data;
+ gchar *secondary_action_name;
+} GtdNotificationPrivate;
+
+struct _GtdNotification
+{
+ GtdObject parent;
+
+ /*< private >*/
+ GtdNotificationPrivate *priv;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtdNotification, gtd_notification, GTD_TYPE_OBJECT)
+
+enum
+{
+ PROP_0,
+ PROP_HAS_DISMISSAL_ACTION,
+ PROP_HAS_SECONDARY_ACTION,
+ PROP_SECONDARY_ACTION_NAME,
+ PROP_TEXT,
+ LAST_PROP
+};
+
+enum
+{
+ EXECUTED,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+static void
+gtd_notification_finalize (GObject *object)
+{
+ GtdNotification *self = (GtdNotification *)object;
+ GtdNotificationPrivate *priv = gtd_notification_get_instance_private (self);
+
+ g_clear_pointer (&priv->secondary_action_name, g_free);
+ g_clear_pointer (&priv->text, g_free);
+
+ G_OBJECT_CLASS (gtd_notification_parent_class)->finalize (object);
+}
+
+static void
+gtd_notification_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdNotification *self = GTD_NOTIFICATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_HAS_DISMISSAL_ACTION:
+ g_value_set_boolean (value, self->priv->has_dismissal_action);
+ break;
+
+ case PROP_HAS_SECONDARY_ACTION:
+ g_value_set_boolean (value, self->priv->has_secondary_action);
+ break;
+
+ case PROP_SECONDARY_ACTION_NAME:
+ g_value_set_string (value, self->priv->secondary_action_name ? self->priv->secondary_action_name : "");
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, gtd_notification_get_text (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_notification_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdNotification *self = GTD_NOTIFICATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_SECONDARY_ACTION_NAME:
+ gtd_notification_set_secondary_action (self,
+ g_value_get_string (value),
+ self->priv->secondary_action,
+ self->priv->secondary_action_data);
+ break;
+
+ case PROP_TEXT:
+ gtd_notification_set_text (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_notification_class_init (GtdNotificationClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_notification_finalize;
+ object_class->get_property = gtd_notification_get_property;
+ object_class->set_property = gtd_notification_set_property;
+
+ /**
+ * GtdNotification::has-dismissal-action:
+ *
+ * @TRUE if the notification has a dismissal action or @FALSE otherwise.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_HAS_DISMISSAL_ACTION,
+ g_param_spec_boolean ("has-dismissal-action",
+ "Whether the notification has a dismissal action",
+ "Whether the notification has the dismissal action.",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));
+
+ /**
+ * GtdNotification::has-secondary-action:
+ *
+ * @TRUE if the notification has a secondary action or @FALSE otherwise. The
+ * secondary action is triggered only by user explicit input.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_HAS_SECONDARY_ACTION,
+ g_param_spec_boolean ("has-secondary-action",
+ "Whether the notification has a secondary action",
+ "Whether the notification has the secondary action, activated by the user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));
+
+ /**
+ * GtdNotification::secondary-action-name:
+ *
+ * The main text of the notification, usually a markuped text.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_SECONDARY_ACTION_NAME,
+ g_param_spec_string ("secondary-action-name",
+ "Text of the secondary action button",
+ "The text of the secondary action button",
+ "",
+ G_PARAM_READWRITE));
+
+ /**
+ * GtdNotification::text:
+ *
+ * The main text of the notification, usually a markuped text.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_TEXT,
+ g_param_spec_string ("text",
+ "Notification message",
+ "The main message of the notification",
+ "",
+ G_PARAM_READWRITE));
+
+ /**
+ * GtdNotification::executed:
+ *
+ * The ::executed signal is emitted after the primary or secondary
+ * #GtdNotification action is executed.
+ */
+ signals[EXECUTED] = g_signal_new ("executed",
+ GTD_TYPE_NOTIFICATION,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+gtd_notification_init (GtdNotification *self)
+{
+ self->priv = gtd_notification_get_instance_private (self);
+ self->priv->secondary_action_name = NULL;
+ self->priv->text = NULL;
+}
+
+/**
+ * gtd_notification_new:
+ * @text: (nullable): text of the notification
+ *
+ * Creates a new notification with @text.
+ *
+ * Returns: (transfer full): a new #GtdNotification
+ */
+GtdNotification*
+gtd_notification_new (const gchar *text)
+{
+ return g_object_new (GTD_TYPE_NOTIFICATION,
+ "text", text,
+ NULL);
+}
+
+/**
+ * gtd_notification_set_dismissal_action:
+ * @notification: a #GtdNotification
+ * @func: (closure user_data) (scope call) (nullable): the dismissal action function
+ * @user_data: data passed to @func
+ *
+ * Sets the dismissal action of @notification
+ */
+void
+gtd_notification_set_dismissal_action (GtdNotification *notification,
+ GtdNotificationActionFunc func,
+ gpointer user_data)
+{
+ GtdNotificationPrivate *priv;
+ gboolean has_action;
+
+ g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+ priv = notification->priv;
+ has_action = (func != NULL);
+
+ if (has_action != priv->has_dismissal_action)
+ {
+ priv->has_dismissal_action = has_action;
+
+ priv->dismissal_action = has_action ? func : NULL;
+ priv->dismissal_action_data = has_action ? user_data : NULL;
+
+ g_object_notify (G_OBJECT (notification), "has-dismissal-action");
+ }
+}
+
+/**
+ * gtd_notification_set_secondary_action:
+ * @notification: a #GtdNotification
+ * @name: the name of the secondary action
+ * @func: (closure user_data) (scope call) (nullable): the secondary action function
+ * @user_data: data passed to @func
+ *
+ * Sets the secondary action of @notification, which is triggered
+ * only on user explicit input.
+ */
+void
+gtd_notification_set_secondary_action (GtdNotification *notification,
+ const gchar *name,
+ GtdNotificationActionFunc func,
+ gpointer user_data)
+{
+ GtdNotificationPrivate *priv;
+ gboolean has_action;
+
+ g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+ priv = notification->priv;
+ has_action = (func != NULL);
+
+ if (has_action != priv->has_secondary_action)
+ {
+ priv->has_secondary_action = has_action;
+
+ priv->secondary_action = has_action ? func : NULL;
+ priv->secondary_action_data = has_action ? user_data : NULL;
+
+ if (priv->secondary_action_name != name)
+ {
+ g_clear_pointer (&priv->secondary_action_name, g_free);
+ priv->secondary_action_name = g_strdup (name);
+
+ g_object_notify (G_OBJECT (notification), "secondary-action-name");
+ }
+
+ g_object_notify (G_OBJECT (notification), "has-secondary-action");
+ }
+}
+
+/**
+ * gtd_notification_get_text:
+ * @notification: a #GtdNotification
+ *
+ * Gets the text of @notification.
+ *
+ * Returns: (transfer none): the text of @notification.
+ */
+const gchar*
+gtd_notification_get_text (GtdNotification *notification)
+{
+ g_return_val_if_fail (GTD_IS_NOTIFICATION (notification), NULL);
+
+ return notification->priv->text ? notification->priv->text : "";
+}
+
+/**
+ * gtd_notification_set_text:
+ * @notification: a #GtdNotification
+ * @text: the user-visible text of @notification
+ *
+ * Sets the text of @notification to @text.
+ */
+void
+gtd_notification_set_text (GtdNotification *notification,
+ const gchar *text)
+{
+ GtdNotificationPrivate *priv;
+
+ g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+ priv = notification->priv;
+
+ if (g_strcmp0 (priv->text, text) != 0)
+ {
+ g_clear_pointer (&priv->text, g_free);
+ priv->text = g_strdup (text);
+
+ g_object_notify (G_OBJECT (notification), "text");
+ }
+}
+
+/**
+ * gtd_notification_execute_dismissal_action:
+ * @notification: a #GtdNotification
+ *
+ * Executes the dismissal action of @notification if any.
+ */
+void
+gtd_notification_execute_dismissal_action (GtdNotification *notification)
+{
+ GtdNotificationPrivate *priv;
+
+ g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+ priv = notification->priv;
+
+ if (priv->dismissal_action)
+ priv->dismissal_action (notification, priv->dismissal_action_data);
+
+ g_signal_emit (notification, signals[EXECUTED], 0);
+}
+
+/**
+ * gtd_notification_execute_secondary_action:
+ * @notification: a #GtdNotification
+ *
+ * Executes the secondary action of @notification if any.
+ */
+void
+gtd_notification_execute_secondary_action (GtdNotification *notification)
+{
+ GtdNotificationPrivate *priv;
+
+ g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+ priv = notification->priv;
+
+ if (priv->secondary_action)
+ {
+ priv->secondary_action (notification, priv->secondary_action_data);
+
+ g_signal_emit (notification, signals[EXECUTED], 0);
+ }
+}
diff --git a/src/core/gtd-notification.h b/src/core/gtd-notification.h
new file mode 100644
index 0000000..fd420ad
--- /dev/null
+++ b/src/core/gtd-notification.h
@@ -0,0 +1,68 @@
+/* gtd-notification.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_NOTIFICATION_H
+#define GTD_NOTIFICATION_H
+
+#include "gtd-object.h"
+#include "gtd-types.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_NOTIFICATION (gtd_notification_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdNotification, gtd_notification, GTD, NOTIFICATION, GtdObject)
+
+/**
+ * GtdNotificationActionFunc:
+ * @notification: the #GtdNotification running the function
+ * @user_data: (closure): user data
+ *
+ * Will be called when the dismissal or secondary action of @notification
+ * is executed.
+ */
+typedef void (*GtdNotificationActionFunc) (GtdNotification *notification,
+ gpointer user_data);
+
+
+GtdNotification* gtd_notification_new (const gchar *text);
+
+void gtd_notification_execute_dismissal_action (GtdNotification *notification);
+
+void gtd_notification_execute_secondary_action (GtdNotification *notification);
+
+void gtd_notification_set_dismissal_action (GtdNotification *notification,
+ GtdNotificationActionFunc func,
+ gpointer user_data);
+
+void gtd_notification_set_secondary_action (GtdNotification *notification,
+ const gchar *name,
+ GtdNotificationActionFunc func,
+ gpointer user_data);
+
+const gchar* gtd_notification_get_text (GtdNotification *notification);
+
+void gtd_notification_set_text (GtdNotification *notification,
+ const gchar *text);
+
+G_END_DECLS
+
+#endif /* GTD_NOTIFICATION_H */
diff --git a/src/core/gtd-object.c b/src/core/gtd-object.c
new file mode 100644
index 0000000..c1157fc
--- /dev/null
+++ b/src/core/gtd-object.c
@@ -0,0 +1,313 @@
+/* gtd-object.c
+ *
+ * Copyright © 2018 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 "GtdObject"
+
+#include "gtd-object.h"
+
+#include <glib/gi18n.h>
+
+/**
+ * SECTION:gtd-object
+ * @Short_description: base class for loadable and uniquely identifiable objects
+ * @Title: GtdObject
+ *
+ * #GtdObject is the base class of many object in Endeavour, and it useful for
+ * when a given object is loadable and/or uniquely identifiable. Some examples of
+ * it are #GtdTask, #GtdTaskList and #GtdNotification.
+ *
+ */
+
+typedef struct
+{
+ guint64 loading;
+ gchar *uid;
+} GtdObjectPrivate;
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtdObject, gtd_object, G_TYPE_OBJECT)
+
+
+enum
+{
+ PROP_0,
+ PROP_LOADING,
+ PROP_UID,
+ N_PROPS
+};
+
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+
+static const gchar*
+gtd_object_real_get_uid (GtdObject *object)
+{
+ GtdObjectPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_OBJECT (object), NULL);
+
+ priv = gtd_object_get_instance_private (object);
+
+ return priv->uid;
+}
+
+static void
+gtd_object_real_set_uid (GtdObject *object,
+ const gchar *uid)
+{
+ GtdObjectPrivate *priv;
+
+ g_assert (GTD_IS_OBJECT (object));
+
+ priv = gtd_object_get_instance_private (object);
+
+ if (g_strcmp0 (priv->uid, uid) == 0)
+ return;
+
+ g_clear_pointer (&priv->uid, g_free);
+ priv->uid = g_strdup (uid);
+
+ g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_UID]);
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_object_finalize (GObject *object)
+{
+ GtdObject *self = GTD_OBJECT (object);
+ GtdObjectPrivate *priv = gtd_object_get_instance_private (self);
+
+ g_clear_pointer (&priv->uid, g_free);
+
+ G_OBJECT_CLASS (gtd_object_parent_class)->finalize (object);
+}
+
+static void
+gtd_object_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdObject *self = GTD_OBJECT (object);
+ GtdObjectPrivate *priv = gtd_object_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_LOADING:
+ g_value_set_boolean (value, priv->loading > 0);
+ break;
+
+ case PROP_UID:
+ g_value_set_string (value, GTD_OBJECT_GET_CLASS (self)->get_uid (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_object_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdObject *self = GTD_OBJECT (object);
+
+ switch (prop_id)
+ {
+ case PROP_UID:
+ GTD_OBJECT_GET_CLASS (self)->set_uid (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_object_class_init (GtdObjectClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ klass->get_uid = gtd_object_real_get_uid;
+ klass->set_uid = gtd_object_real_set_uid;
+
+ object_class->finalize = gtd_object_finalize;
+ object_class->get_property = gtd_object_get_property;
+ object_class->set_property = gtd_object_set_property;
+
+ /**
+ * GtdObject::uid:
+ *
+ * The unique identified of the object, set by the backend.
+ */
+ properties[PROP_UID] = g_param_spec_string ("uid",
+ "Unique identifier of the object",
+ "The unique identifier of the object, defined by the backend",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtdObject::loading:
+ *
+ * Whether the object is loading or not.
+ */
+ properties[PROP_LOADING] = g_param_spec_boolean ("loading",
+ "Loading state of the object",
+ "Whether the object is loading or not",
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gtd_object_init (GtdObject *self)
+{
+}
+
+/**
+ * gtd_object_new:
+ * @uid: unique identifier of the object
+ *
+ * Creates a new #GtdObject object.
+ *
+ * Returns: (transfer full): a new #GtdObject
+ */
+GtdObject*
+gtd_object_new (const gchar *uid)
+{
+ return g_object_new (GTD_TYPE_OBJECT, "uid", uid, NULL);
+}
+
+/**
+ * gtd_object_get_uid:
+ * @object: a #GtdObject
+ *
+ * Retrieves the internal unique identifier of @object.
+ *
+ * Returns: (transfer none): the unique identifier of @object. Do
+ * not free after usage.
+ */
+const gchar*
+gtd_object_get_uid (GtdObject *object)
+{
+ GtdObjectClass *class;
+
+ g_return_val_if_fail (GTD_IS_OBJECT (object), NULL);
+
+ class = GTD_OBJECT_GET_CLASS (object);
+
+ g_assert (class);
+ return class->get_uid (object);
+}
+
+/**
+ * gtd_object_set_uid:
+ * @object: a #GtdObject
+ * @uid: the unique identifier of @object
+ *
+ * Sets the unique identifier of @object to @uid. Only
+ * a #GtdBackend should do it.
+ */
+void
+gtd_object_set_uid (GtdObject *object,
+ const gchar *uid)
+{
+ GtdObjectClass *class;
+
+ g_return_if_fail (GTD_IS_OBJECT (object));
+
+ class = GTD_OBJECT_GET_CLASS (object);
+
+ g_assert (class);
+ class->set_uid (object, uid);
+}
+
+/**
+ * gtd_object_get_loading:
+ * @object: a #GtdObject
+ *
+ * Whether @object is loading or not.
+ *
+ * Returns: %TRUE if @object is loading, %FALSE otherwise.
+ */
+gboolean
+gtd_object_get_loading (GtdObject *object)
+{
+ GtdObjectPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_OBJECT (object), FALSE);
+
+ priv = gtd_object_get_instance_private (object);
+
+ return priv->loading > 0;
+}
+
+/**
+ * gtd_object_push_loading:
+ * @object: a #GtdObject
+ *
+ * Increases the loading counter of @object by one. The object is marked
+ * as loading while the loading counter is greater than zero.
+ */
+void
+gtd_object_push_loading (GtdObject *object)
+{
+ GtdObjectPrivate *priv;
+
+ g_return_if_fail (GTD_IS_OBJECT (object));
+
+ priv = gtd_object_get_instance_private (object);
+
+ priv->loading++;
+
+ if (priv->loading == 1)
+ g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_LOADING]);
+}
+
+/**
+ * gtd_object_pop_loading:
+ * @object: a #GtdObject
+ *
+ * Decreases the loading counter of @object by one. The object is marked
+ * as loading while the loading counter is greater than zero.
+ *
+ * It is a programming error to pop more times then push the loading the
+ * counter.
+ */
+void
+gtd_object_pop_loading (GtdObject *object)
+{
+ GtdObjectPrivate *priv;
+
+ g_return_if_fail (GTD_IS_OBJECT (object));
+
+ priv = gtd_object_get_instance_private (object);
+
+ priv->loading--;
+
+ if (priv->loading == 0)
+ g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_LOADING]);
+}
diff --git a/src/core/gtd-object.h b/src/core/gtd-object.h
new file mode 100644
index 0000000..c0033d3
--- /dev/null
+++ b/src/core/gtd-object.h
@@ -0,0 +1,54 @@
+/* gtd-object.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/>.
+ */
+
+#pragma once
+
+#include "gtd-types.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_OBJECT (gtd_object_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GtdObject, gtd_object, GTD, OBJECT, GObject)
+
+struct _GtdObjectClass
+{
+ GObjectClass parent;
+
+ /* public */
+ const gchar* (* get_uid) (GtdObject *object);
+ void (* set_uid) (GtdObject *object,
+ const gchar *uid);
+};
+
+GtdObject* gtd_object_new (const gchar *uid);
+
+const gchar* gtd_object_get_uid (GtdObject *object);
+
+void gtd_object_set_uid (GtdObject *object,
+ const gchar *uid);
+
+gboolean gtd_object_get_loading (GtdObject *object);
+
+void gtd_object_push_loading (GtdObject *object);
+
+void gtd_object_pop_loading (GtdObject *object);
+
+G_END_DECLS
diff --git a/src/core/gtd-plugin-manager.c b/src/core/gtd-plugin-manager.c
new file mode 100644
index 0000000..e159916
--- /dev/null
+++ b/src/core/gtd-plugin-manager.c
@@ -0,0 +1,323 @@
+/* gtd-plugin-manager.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 "GtdPluginManager"
+
+#include "gtd-activatable.h"
+#include "gtd-manager.h"
+#include "gtd-panel.h"
+#include "gtd-provider.h"
+#include "gtd-plugin-manager.h"
+
+#include <libpeas/peas.h>
+
+struct _GtdPluginManager
+{
+ GtdObject parent;
+
+ GHashTable *info_to_extension;
+};
+
+G_DEFINE_TYPE (GtdPluginManager, gtd_plugin_manager, GTD_TYPE_OBJECT)
+
+enum
+{
+ PLUGIN_LOADED,
+ PLUGIN_UNLOADED,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+static const gchar * const default_plugins[] = {
+ "all-tasks-panel",
+ "eds",
+ "task-lists-workspace",
+ "today-panel",
+ "inbox-panel",
+ "next-week-panel",
+ "peace",
+};
+
+static gboolean
+gtd_str_equal0 (gconstpointer a,
+ gconstpointer b)
+{
+ if (a == b)
+ return TRUE;
+ else if (!a || !b)
+ return FALSE;
+ else
+ return g_str_equal (a, b);
+}
+
+static gchar**
+get_loaded_extensions (const gchar **extensions)
+{
+ g_autoptr (GPtrArray) loaded_plugins = NULL;
+ gsize i;
+
+ loaded_plugins = g_ptr_array_new ();
+
+ for (i = 0; extensions && extensions[i]; i++)
+ g_ptr_array_add (loaded_plugins, g_strdup (extensions[i]));
+
+ for (i = 0; i < G_N_ELEMENTS (default_plugins); i++)
+ {
+ if (g_ptr_array_find_with_equal_func (loaded_plugins,
+ default_plugins[i],
+ gtd_str_equal0,
+ NULL))
+ {
+ continue;
+ }
+
+ g_ptr_array_add (loaded_plugins, g_strdup (default_plugins[i]));
+ }
+
+ g_ptr_array_add (loaded_plugins, NULL);
+
+ return (gchar**) g_ptr_array_free (g_steal_pointer (&loaded_plugins), FALSE);
+}
+
+static gboolean
+from_gsetting_to_property_func (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ g_autofree const gchar **extensions = NULL;
+ g_autofree gchar **loaded_extensions = NULL;
+
+ extensions = g_variant_get_strv (variant, NULL);
+ loaded_extensions = get_loaded_extensions (extensions);
+
+ g_value_take_boxed (value, g_steal_pointer (&loaded_extensions));
+
+ return TRUE;
+}
+
+static GVariant*
+from_property_to_gsetting_func (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ g_autofree gchar **loaded_extensions = NULL;
+ const gchar **extensions = NULL;
+
+ extensions = g_value_get_boxed (value);
+ loaded_extensions = get_loaded_extensions (extensions);
+
+ return g_variant_new_strv ((const gchar * const *)loaded_extensions, -1);
+}
+
+static void
+on_plugin_unloaded_cb (PeasEngine *engine,
+ PeasPluginInfo *info,
+ GtdPluginManager *self)
+{
+ GtdActivatable *activatable;
+
+ activatable = g_hash_table_lookup (self->info_to_extension, info);
+
+ if (!activatable)
+ return;
+
+ /* Deactivates the extension */
+ gtd_activatable_deactivate (activatable);
+
+ /* Emit the signal */
+ g_signal_emit (self, signals[PLUGIN_UNLOADED], 0, info, activatable);
+
+ g_hash_table_remove (self->info_to_extension, info);
+
+ /* Destroy the extension */
+ g_clear_object (&activatable);
+}
+
+static void
+on_plugin_loaded_cb (PeasEngine *engine,
+ PeasPluginInfo *info,
+ GtdPluginManager *self)
+{
+ if (peas_engine_provides_extension (engine, info, GTD_TYPE_ACTIVATABLE))
+ {
+ GtdActivatable *activatable;
+ PeasExtension *extension;
+
+ /*
+ * Actually create the plugin object,
+ * which should load all the providers.
+ */
+ extension = peas_engine_create_extension (engine,
+ info,
+ GTD_TYPE_ACTIVATABLE,
+ NULL);
+
+ /* All extensions shall be GtdActivatable impls */
+ activatable = GTD_ACTIVATABLE (extension);
+
+ g_hash_table_insert (self->info_to_extension,
+ info,
+ extension);
+
+ /* Activate extension */
+ gtd_activatable_activate (activatable);
+
+ /* Emit the signal */
+ g_signal_emit (self, signals[PLUGIN_LOADED], 0, info, extension);
+ }
+}
+
+static void
+setup_engine (GtdPluginManager *self)
+{
+ PeasEngine *engine;
+ gchar *plugin_dir;
+
+ engine = peas_engine_get_default ();
+
+ /* Enable Python3 plugins */
+ peas_engine_enable_loader (engine, "python3");
+
+ /* Let Peas search for plugins in the specified directory */
+ plugin_dir = g_build_filename (PACKAGE_LIB_DIR,
+ "plugins",
+ NULL);
+
+ peas_engine_add_search_path (engine,
+ plugin_dir,
+ NULL);
+
+ g_free (plugin_dir);
+
+ /* User-installed plugins shall be detected too */
+ plugin_dir = g_build_filename (g_get_home_dir (),
+ ".local",
+ "lib",
+ "endeavour",
+ "plugins",
+ NULL);
+
+ peas_engine_add_search_path (engine, plugin_dir, NULL);
+ peas_engine_prepend_search_path (engine,
+ "resource:///org/gnome/todo/plugins",
+ "resource:///org/gnome/todo/plugins");
+
+ g_free (plugin_dir);
+
+ /* Hear about loaded plugins */
+ g_signal_connect_after (engine, "load-plugin", G_CALLBACK (on_plugin_loaded_cb), self);
+ g_signal_connect (engine, "unload-plugin",G_CALLBACK (on_plugin_unloaded_cb), self);
+}
+
+static void
+gtd_plugin_manager_finalize (GObject *object)
+{
+ GtdPluginManager *self = (GtdPluginManager *)object;
+
+ g_clear_pointer (&self->info_to_extension, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (gtd_plugin_manager_parent_class)->finalize (object);
+}
+
+static void
+gtd_plugin_manager_class_init (GtdPluginManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_plugin_manager_finalize;
+
+ signals[PLUGIN_LOADED] = g_signal_new ("plugin-loaded",
+ GTD_TYPE_PLUGIN_MANAGER,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 2,
+ PEAS_TYPE_PLUGIN_INFO,
+ GTD_TYPE_ACTIVATABLE);
+
+ signals[PLUGIN_UNLOADED] = g_signal_new ("plugin-unloaded",
+ GTD_TYPE_PLUGIN_MANAGER,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 2,
+ PEAS_TYPE_PLUGIN_INFO,
+ GTD_TYPE_ACTIVATABLE);
+}
+
+static void
+gtd_plugin_manager_init (GtdPluginManager *self)
+{
+ self->info_to_extension = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ gtd_object_push_loading (GTD_OBJECT (self));
+
+ setup_engine (self);
+
+ gtd_object_pop_loading (GTD_OBJECT (self));
+}
+
+GtdPluginManager*
+gtd_plugin_manager_new (void)
+{
+ return g_object_new (GTD_TYPE_PLUGIN_MANAGER, NULL);
+}
+
+void
+gtd_plugin_manager_load_plugins (GtdPluginManager *self)
+{
+ PeasEngine *engine;
+ GSettings *settings;
+
+ engine = peas_engine_get_default ();
+ settings = gtd_manager_get_settings (gtd_manager_get_default ());
+
+ g_settings_bind_with_mapping (settings,
+ "active-extensions",
+ engine,
+ "loaded-plugins",
+ G_SETTINGS_BIND_DEFAULT,
+ from_gsetting_to_property_func,
+ from_property_to_gsetting_func,
+ self,
+ NULL);
+}
+
+GtdActivatable*
+gtd_plugin_manager_get_plugin (GtdPluginManager *self,
+ PeasPluginInfo *info)
+{
+ g_return_val_if_fail (GTD_IS_PLUGIN_MANAGER (self), NULL);
+
+ return g_hash_table_lookup (self->info_to_extension, info);
+}
+
+GList*
+gtd_plugin_manager_get_loaded_plugins (GtdPluginManager *self)
+{
+ g_return_val_if_fail (GTD_IS_PLUGIN_MANAGER (self), NULL);
+
+ return g_hash_table_get_values (self->info_to_extension);
+}
diff --git a/src/core/gtd-plugin-manager.h b/src/core/gtd-plugin-manager.h
new file mode 100644
index 0000000..7bef2c6
--- /dev/null
+++ b/src/core/gtd-plugin-manager.h
@@ -0,0 +1,45 @@
+/* gtd-plugin-manager.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_PLUGIN_MANAGER_H
+#define GTD_PLUGIN_MANAGER_H
+
+#include <glib-object.h>
+#include <libpeas/peas.h>
+
+#include "gtd-object.h"
+#include "gtd-types.h"
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_PLUGIN_MANAGER (gtd_plugin_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdPluginManager, gtd_plugin_manager, GTD, PLUGIN_MANAGER, GtdObject)
+
+GtdPluginManager* gtd_plugin_manager_new (void);
+
+void gtd_plugin_manager_load_plugins (GtdPluginManager *self);
+
+GtdActivatable* gtd_plugin_manager_get_plugin (GtdPluginManager *self,
+ PeasPluginInfo *info);
+
+GList* gtd_plugin_manager_get_loaded_plugins (GtdPluginManager *self);
+
+G_END_DECLS
+
+#endif /* GTD_PLUGIN_MANAGER_H */
diff --git a/src/core/gtd-provider.c b/src/core/gtd-provider.c
new file mode 100644
index 0000000..1d69a80
--- /dev/null
+++ b/src/core/gtd-provider.c
@@ -0,0 +1,681 @@
+/* gtd-provider.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 "GtdProvider"
+
+#include "gtd-provider.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+#include "gtd-utils.h"
+
+/**
+ * SECTION:gtd-provider
+ * @short_description:data sources for Endeavour
+ * @title: GtdProvider
+ * @stability:Unstable
+ *
+ * The #GtdProvider is the interface that Endeavour uses to
+ * connect to data sources. It must provide ways to create, update
+ * and remove tasks and tasklists.
+ *
+ * A provider implementation must also expose which is the default
+ * tasklist among the tasklists it manages.
+ */
+
+G_DEFINE_INTERFACE (GtdProvider, gtd_provider, GTD_TYPE_OBJECT)
+
+enum
+{
+ LIST_ADDED,
+ LIST_CHANGED,
+ LIST_REMOVED,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+
+static void
+gtd_provider_default_init (GtdProviderInterface *iface)
+{
+ /**
+ * GtdProvider::enabled:
+ *
+ * Whether the #GtdProvider is enabled.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("enabled",
+ "Identifier of the provider",
+ "The identifier of the provider",
+ FALSE,
+ G_PARAM_READABLE));
+
+ /**
+ * GtdProvider::icon:
+ *
+ * The icon of the #GtdProvider, e.g. the account icon
+ * of a GNOME Online Accounts' account.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("icon",
+ "Icon of the provider",
+ "The icon of the provider",
+ G_TYPE_ICON,
+ G_PARAM_READABLE));
+
+ /**
+ * GtdProvider::id:
+ *
+ * The unique identifier of the #GtdProvider.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_string ("id",
+ "Identifier of the provider",
+ "The identifier of the provider",
+ NULL,
+ G_PARAM_READABLE));
+
+ /**
+ * GtdProvider::name:
+ *
+ * The user-visible name of the #GtdProvider.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_string ("name",
+ "Name of the provider",
+ "The user-visible name of the provider",
+ NULL,
+ G_PARAM_READABLE));
+
+ /**
+ * GtdProvider::provider-type:
+ *
+ * The type of the #GtdProvider.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_string ("provider-type",
+ "Type of the provider",
+ "The type of the provider",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GtdProvider::description:
+ *
+ * The description of the #GtdProvider, e.g. the account user
+ * of a GNOME Online Accounts' account.
+ */
+ g_object_interface_install_property (iface,
+ g_param_spec_string ("description",
+ "Description of the provider",
+ "The description of the provider",
+ NULL,
+ G_PARAM_READABLE));
+
+ /**
+ * GtdProvider::list-added:
+ * @provider: a #GtdProvider
+ * @list: a #GtdTaskList
+ *
+ * The ::list-added signal is emmited after a #GtdTaskList
+ * is connected.
+ */
+ signals[LIST_ADDED] = g_signal_new ("list-added",
+ GTD_TYPE_PROVIDER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK_LIST);
+
+ /**
+ * GtdProvider::list-changed:
+ * @provider: a #GtdProvider
+ * @list: a #GtdTaskList
+ *
+ * The ::list-changed signal is emmited after a #GtdTaskList
+ * has any of it's properties changed.
+ */
+ signals[LIST_CHANGED] = g_signal_new ("list-changed",
+ GTD_TYPE_PROVIDER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK_LIST);
+
+ /**
+ * GtdProvider::list-removed:
+ * @provider: a #GtdProvider
+ * @list: a #GtdTaskList
+ *
+ * The ::list-removed signal is emmited after a #GtdTaskList
+ * is disconnected.
+ */
+ signals[LIST_REMOVED] = g_signal_new ("list-removed",
+ GTD_TYPE_PROVIDER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK_LIST);
+}
+
+/**
+ * gtd_provider_get_id:
+ * @provider: a #GtdProvider
+ *
+ * Retrieves the identifier of @provider.
+ *
+ * Returns: (transfer none): the id of @provider
+ */
+const gchar*
+gtd_provider_get_id (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_id, NULL);
+
+ return GTD_PROVIDER_GET_IFACE (provider)->get_id (provider);
+}
+
+/**
+ * gtd_provider_get_name:
+ * @provider: a #GtdProvider
+ *
+ * Retrieves the user-visible name of @provider.
+ *
+ * Returns: (transfer none): the name of @provider
+ */
+const gchar*
+gtd_provider_get_name (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_name, NULL);
+
+ return GTD_PROVIDER_GET_IFACE (provider)->get_name (provider);
+}
+
+/**
+ * gtd_provider_get_provider_type:
+ * @provider: a #GtdProvider
+ *
+ * Retrieves the type of the @provider. This should return the
+ * same value, regardless of the account name.
+ *
+ * For example: "todoist", "todo-txt" or "google"
+ *
+ * Returns: (transfer none): the type of the @provider
+ */
+const gchar*
+gtd_provider_get_provider_type (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_name, NULL);
+
+ return GTD_PROVIDER_GET_IFACE (provider)->get_provider_type (provider);
+}
+
+/**
+ * gtd_provider_get_description:
+ * @provider: a #GtdProvider
+ *
+ * Retrieves the description of @provider.
+ *
+ * Returns: (transfer none): the description of @provider
+ */
+const gchar*
+gtd_provider_get_description (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_description, NULL);
+
+ return GTD_PROVIDER_GET_IFACE (provider)->get_description (provider);
+}
+
+/**
+ * gtd_provider_get_enabled:
+ * @provider: a #GtdProvider
+ *
+ * Retrieves whether @provider is enabled or not. A disabled
+ * provider cannot be selected to be default nor be selected
+ * to add tasks to it.
+ *
+ * Returns: %TRUE if provider is enabled, %FALSE otherwise.
+ */
+gboolean
+gtd_provider_get_enabled (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (provider), FALSE);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_enabled, FALSE);
+
+ return GTD_PROVIDER_GET_IFACE (provider)->get_enabled (provider);
+}
+
+/**
+ * gtd_provider_refresh:
+ * @provider: a #GtdProvider
+ *
+ * Asks the provider to refresh. Online providers may want to
+ * synchronize tasks and tasklists, credentials, etc, when this
+ * is called.
+ *
+ * This is an optional feature. Providers that do not implement
+ * the "refresh" vfunc will be ignored.
+ */
+void
+gtd_provider_refresh (GtdProvider *provider)
+{
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+
+ if (GTD_PROVIDER_GET_IFACE (provider)->refresh)
+ GTD_PROVIDER_GET_IFACE (provider)->refresh (provider);
+}
+
+/**
+ * gtd_provider_get_icon:
+ * @provider: a #GtdProvider
+ *
+ * The icon of @provider.
+ *
+ * Returns: (transfer none): a #GIcon
+ */
+GIcon*
+gtd_provider_get_icon (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_icon, NULL);
+
+ return GTD_PROVIDER_GET_IFACE (provider)->get_icon (provider);
+}
+
+/**
+ * gtd_provider_create_task:
+ * @provider: a #GtdProvider
+ * @list: a #GtdTaskLast
+ * @title: The task title
+ * @due_date: (nullable): a #GDateTime
+ * @cancellable: (nullable): a #GCancellable
+ * @callback: (scope async): a callback
+ * @user_data: (closure): user data for @callback
+ *
+ * Creates the given task in @provider.
+ */
+void
+gtd_provider_create_task (GtdProvider *provider,
+ GtdTaskList *list,
+ const gchar *title,
+ GDateTime *due_date,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+ g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->create_task);
+
+ GTD_PROVIDER_GET_IFACE (provider)->create_task (provider,
+ list,
+ title,
+ due_date,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gtd_provider_create_task_finish:
+ * @self: a #GtdProvider
+ * @result: a #GAsyncResult
+ * @error: (out)(nullable): return location for a #GError
+ *
+ * Finishes creating the task.
+ *
+ * Returns: (transfer none)(nullable): a #GtdTask
+ */
+GtdTask*
+gtd_provider_create_task_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
+ g_return_val_if_fail (!error || !*error, FALSE);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->create_task_finish, FALSE);
+
+ return GTD_PROVIDER_GET_IFACE (self)->create_task_finish (self, result, error);
+}
+
+/**
+ * gtd_provider_update_task:
+ * @provider: a #GtdProvider
+ * @task: a #GtdTask
+ * @cancellable: (nullable): a #GCancellable
+ * @callback: (scope async): a callback
+ * @user_data: (closure): user data for @callback
+ *
+ * Updates the given task in @provider.
+ */
+void
+gtd_provider_update_task (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+ g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->update_task);
+
+ GTD_PROVIDER_GET_IFACE (provider)->update_task (provider,
+ task,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gtd_provider_update_task_finish:
+ * @self: a #GtdProvider
+ * @result: a #GAsyncResult
+ * @error: (out)(nullable): return location for a #GError
+ *
+ * Finishes updating the task list.
+ *
+ * Returns: %TRUE if task list was successfully updated, %FALSE otherwise
+ */
+gboolean
+gtd_provider_update_task_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
+ g_return_val_if_fail (!error || !*error, FALSE);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->update_task_finish, FALSE);
+
+ return GTD_PROVIDER_GET_IFACE (self)->update_task_finish (self, result, error);
+}
+
+/**
+ * gtd_provider_remove_task:
+ * @provider: a #GtdProvider
+ * @task: a #GtdTask
+ * @cancellable: (nullable): a #GCancellable
+ * @callback: (scope async): a callback
+ * @user_data: (closure): user data for @callback
+ *
+ * Removes the given task from @provider.
+ */
+void
+gtd_provider_remove_task (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+ g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->remove_task);
+
+ GTD_PROVIDER_GET_IFACE (provider)->remove_task (provider,
+ task,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gtd_provider_remove_task_finish:
+ * @self: a #GtdProvider
+ * @result: a #GAsyncResult
+ * @error: (out)(nullable): return location for a #GError
+ *
+ * Finishes removing the task.
+ *
+ * Returns: %TRUE if task was successfully removed, %FALSE otherwise
+ */
+gboolean
+gtd_provider_remove_task_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
+ g_return_val_if_fail (!error || !*error, FALSE);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->remove_task_finish, FALSE);
+
+ return GTD_PROVIDER_GET_IFACE (self)->remove_task_finish (self, result, error);
+}
+
+/**
+ * gtd_provider_create_task_list:
+ * @provider: a #GtdProvider
+ * @name: (nullable): the name of the new task list
+ * @cancellable: (nullable): a #GCancellable
+ * @callback: (scope async): a callback
+ * @user_data: (closure): user data for @callback
+ *
+ * Creates the given list in @provider.
+ */
+void
+gtd_provider_create_task_list (GtdProvider *provider,
+ const gchar *name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+ g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->create_task_list);
+
+ GTD_PROVIDER_GET_IFACE (provider)->create_task_list (provider,
+ name,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gtd_provider_create_task_list_finish:
+ * @self: a #GtdProvider
+ * @result: a #GAsyncResult
+ * @error: (out)(nullable): return location for a #GError
+ *
+ * Finishes creating the task list. The provider will emit the
+ * GtdProvider:list-added signal after creating the task list.
+ *
+ * Returns: %TRUE if task list was successfully created, %FALSE otherwise
+ */
+gboolean
+gtd_provider_create_task_list_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
+ g_return_val_if_fail (!error || !*error, FALSE);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->create_task_list_finish, FALSE);
+
+ return GTD_PROVIDER_GET_IFACE (self)->create_task_list_finish (self, result, error);
+}
+
+/**
+ * gtd_provider_update_task_list:
+ * @provider: a #GtdProvider
+ * @list: a #GtdTaskList
+ * @cancellable: (nullable): a #GCancellable
+ * @callback: (scope async): a callback
+ * @user_data: (closure): user data for @callback
+ *
+ * Updates the given list in @provider.
+ */
+void
+gtd_provider_update_task_list (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+ g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->update_task_list);
+
+ GTD_PROVIDER_GET_IFACE (provider)->update_task_list (provider,
+ list,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gtd_provider_update_task_list_finish:
+ * @self: a #GtdProvider
+ * @result: a #GAsyncResult
+ * @error: (out)(nullable): return location for a #GError
+ *
+ * Finishes updating the task list. The provider will emit the
+ * GtdProvider:list-updated signal after updating the task list.
+ *
+ * Returns: %TRUE if task list was successfully updated, %FALSE otherwise
+ */
+gboolean
+gtd_provider_update_task_list_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
+ g_return_val_if_fail (!error || !*error, FALSE);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->update_task_list_finish, FALSE);
+
+ return GTD_PROVIDER_GET_IFACE (self)->update_task_list_finish (self, result, error);
+}
+
+/**
+ * gtd_provider_remove_task_list:
+ * @provider: a #GtdProvider
+ * @list: a #GtdTaskList
+ * @cancellable: (nullable): a #GCancellable
+ * @callback: (scope async): a callback
+ * @user_data: (closure): user data for @callback
+ *
+ * Removes the given list from @provider.
+ */
+void
+gtd_provider_remove_task_list (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GTD_IS_PROVIDER (provider));
+ g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->remove_task_list);
+
+ GTD_PROVIDER_GET_IFACE (provider)->remove_task_list (provider,
+ list,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gtd_provider_remove_task_list_finish:
+ * @self: a #GtdProvider
+ * @result: a #GAsyncResult
+ * @error: (out)(nullable): return location for a #GError
+ *
+ * Finishes removing the task list. The provider will emit the
+ * GtdProvider:list-removed signal after removing the task list.
+ *
+ * Returns: %TRUE if task list was successfully removed, %FALSE otherwise
+ */
+gboolean
+gtd_provider_remove_task_list_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
+ g_return_val_if_fail (!error || !*error, FALSE);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->remove_task_list_finish, FALSE);
+
+ return GTD_PROVIDER_GET_IFACE (self)->remove_task_list_finish (self, result, error);
+}
+
+/**
+ * gtd_provider_get_task_lists:
+ * @provider: a #GtdProvider
+ *
+ * Retrieves the tasklists that this provider contains.
+ *
+ * Returns: (transfer container) (element-type Gtd.TaskList): the list of tasks, or %NULL
+ */
+GList*
+gtd_provider_get_task_lists (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_task_lists, NULL);
+
+ return GTD_PROVIDER_GET_IFACE (provider)->get_task_lists (provider);
+}
+
+/**
+ * gtd_provider_get_inbox:
+ * @provider: a #GtdProvider
+ *
+ * Retrieves the inbox of @provider.
+ *
+ * Returns: (transfer none)(nullable): a #GtdTaskList
+ */
+GtdTaskList*
+gtd_provider_get_inbox (GtdProvider *provider)
+{
+ g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_inbox, NULL);
+
+ return GTD_PROVIDER_GET_IFACE (provider)->get_inbox (provider);
+}
+
+/**
+ * gtd_provider_compare:
+ * @a: a #GtdProvider
+ * @b: a #GtdProvider
+ *
+ * Compares @a and @b. The sorting criteria is internal and
+ * may change.
+ *
+ * Returns: -1 if @a comes before @b, 1 for the oposite, and
+ * 0 if they're equal
+ */
+gint
+gtd_provider_compare (GtdProvider *a,
+ GtdProvider *b)
+{
+ gint result;
+
+ g_return_val_if_fail (GTD_IS_PROVIDER (a), 0);
+ g_return_val_if_fail (GTD_IS_PROVIDER (b), 0);
+
+ if (a == b)
+ return 0;
+
+ result = gtd_collate_compare_strings (gtd_provider_get_name (a), gtd_provider_get_name (b));
+
+ if (result != 0)
+ return result;
+
+ return gtd_collate_compare_strings (gtd_provider_get_description (a), gtd_provider_get_description (b));
+}
diff --git a/src/core/gtd-provider.h b/src/core/gtd-provider.h
new file mode 100644
index 0000000..0b8e097
--- /dev/null
+++ b/src/core/gtd-provider.h
@@ -0,0 +1,208 @@
+/* gtd-storage.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_H
+#define GTD_PROVIDER_H
+
+#include "gtd-object.h"
+#include "gtd-types.h"
+
+#include <gio/gio.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_PROVIDER (gtd_provider_get_type ())
+
+G_DECLARE_INTERFACE (GtdProvider, gtd_provider, GTD, PROVIDER, GtdObject)
+
+struct _GtdProviderInterface
+{
+ GTypeInterface parent;
+
+ /* Information */
+ const gchar* (*get_id) (GtdProvider *provider);
+
+ const gchar* (*get_name) (GtdProvider *provider);
+
+ const gchar* (*get_provider_type) (GtdProvider *provider);
+
+ const gchar* (*get_description) (GtdProvider *provider);
+
+ gboolean (*get_enabled) (GtdProvider *provider);
+
+ void (*refresh) (GtdProvider *provider);
+
+ /* Customs */
+ GIcon* (*get_icon) (GtdProvider *provider);
+
+ /* Tasks */
+ void (*create_task) (GtdProvider *provider,
+ GtdTaskList *list,
+ const gchar *title,
+ GDateTime *due_date,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+ GtdTask* (*create_task_finish) (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+ void (*update_task) (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+ gboolean (*update_task_finish) (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+ void (*remove_task) (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+ gboolean (*remove_task_finish) (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+ /* Task lists */
+ void (*create_task_list) (GtdProvider *provider,
+ const gchar *name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+ gboolean (*create_task_list_finish) (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+ void (*update_task_list) (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+ gboolean (*update_task_list_finish) (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+ void (*remove_task_list) (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+ gboolean (*remove_task_list_finish) (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+ GList* (*get_task_lists) (GtdProvider *provider);
+
+ GtdTaskList* (*get_inbox) (GtdProvider *provider);
+};
+
+const gchar* gtd_provider_get_id (GtdProvider *provider);
+
+const gchar* gtd_provider_get_name (GtdProvider *provider);
+
+const gchar* gtd_provider_get_provider_type (GtdProvider *provider);
+
+const gchar* gtd_provider_get_description (GtdProvider *provider);
+
+gboolean gtd_provider_get_enabled (GtdProvider *provider);
+
+void gtd_provider_refresh (GtdProvider *provider);
+
+GIcon* gtd_provider_get_icon (GtdProvider *provider);
+
+void gtd_provider_create_task (GtdProvider *provider,
+ GtdTaskList *list,
+ const gchar *title,
+ GDateTime *due_date,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GtdTask* gtd_provider_create_task_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+void gtd_provider_update_task (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gtd_provider_update_task_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+void gtd_provider_remove_task (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gtd_provider_remove_task_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+void gtd_provider_create_task_list (GtdProvider *provider,
+ const gchar *name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gtd_provider_create_task_list_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+void gtd_provider_update_task_list (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gtd_provider_update_task_list_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+void gtd_provider_remove_task_list (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gtd_provider_remove_task_list_finish (GtdProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+GList* gtd_provider_get_task_lists (GtdProvider *provider);
+
+GtdTaskList* gtd_provider_get_inbox (GtdProvider *provider);
+
+gint gtd_provider_compare (GtdProvider *a,
+ GtdProvider *b);
+
+G_END_DECLS
+
+#endif /* GTD_PROVIDER_H */
diff --git a/src/core/gtd-task-list.c b/src/core/gtd-task-list.c
new file mode 100644
index 0000000..2e71a9a
--- /dev/null
+++ b/src/core/gtd-task-list.c
@@ -0,0 +1,1161 @@
+/* gtd-task-list.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 "GtdTaskList"
+
+#include "gtd-debug.h"
+#include "gtd-provider.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+
+#include <glib/gi18n.h>
+
+/**
+ * SECTION:gtd-task-list
+ * @short_description:a list of tasks
+ * @title:GtdTaskList
+ * @stability:Unstable
+ * @see_also:#GtdTask
+ *
+ * A #GtdTaskList represents a task list, and contains a list of tasks, a
+ * color, a name and the provider who generated it.
+ *
+ * Only a #GtdProvider can create a #GtdTaskList. Equally, a #GtdTaskList
+ * is only valid when associated with a #GtdProvider.
+ *
+ * It implements #GListModel, and can be used as the model for #GtkListBox.
+ */
+
+typedef struct
+{
+ GtdTask *task;
+ GTask *gtask;
+} ImportingTaskData;
+
+typedef struct
+{
+ GtdProvider *provider;
+ GdkRGBA *color;
+
+ GHashTable *task_to_uid;
+ GHashTable *tasks;
+ GSequence *sorted_tasks;
+ guint n_tasks;
+
+ guint freeze_counter;
+
+ gchar *name;
+ gboolean removable;
+ gboolean archived;
+} GtdTaskListPrivate;
+
+
+static gint compare_tasks_cb (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data);
+
+static void task_changed_cb (GtdTask *task,
+ GParamSpec *pspec,
+ GtdTaskList *self);
+
+static void g_list_model_iface_init (GListModelInterface *iface);
+
+
+G_DEFINE_TYPE_WITH_CODE (GtdTaskList, gtd_task_list, GTD_TYPE_OBJECT,
+ G_ADD_PRIVATE (GtdTaskList)
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init))
+
+enum
+{
+ TASK_ADDED,
+ TASK_REMOVED,
+ TASK_UPDATED,
+ NUM_SIGNALS
+};
+
+enum
+{
+ PROP_0,
+ PROP_ARCHIVED,
+ PROP_COLOR,
+ PROP_IS_REMOVABLE,
+ PROP_NAME,
+ PROP_PROVIDER,
+ N_PROPS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+
+/*
+ * Auxiliary functions
+ */
+
+static void
+update_task_uid (GtdTaskList *self,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ const gchar *old_uid;
+ gchar *new_uid;
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ g_debug ("Updating uid of task '%s'", gtd_task_get_title (task));
+
+ new_uid = g_strdup (gtd_object_get_uid (GTD_OBJECT (task)));
+
+ old_uid = g_hash_table_lookup (priv->task_to_uid, task);
+ iter = g_hash_table_lookup (priv->tasks, old_uid);
+
+ g_assert (g_sequence_get (iter) == task);
+
+ g_hash_table_remove (priv->tasks, old_uid);
+
+ g_hash_table_insert (priv->task_to_uid, task, new_uid);
+ g_hash_table_insert (priv->tasks, new_uid, iter);
+}
+
+static guint
+add_task (GtdTaskList *self,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ gchar *uid;
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ uid = g_strdup (gtd_object_get_uid (GTD_OBJECT (task)));
+ iter = g_sequence_insert_sorted (priv->sorted_tasks,
+ g_object_ref (task),
+ compare_tasks_cb,
+ NULL);
+
+ g_hash_table_insert (priv->task_to_uid, task, uid);
+ g_hash_table_insert (priv->tasks, uid, iter);
+
+ g_signal_connect (task, "notify", G_CALLBACK (task_changed_cb), self);
+
+ priv->n_tasks++;
+
+ g_signal_emit (self, signals[TASK_ADDED], 0, task);
+
+ return g_sequence_iter_get_position (iter);
+}
+
+static guint
+remove_task (GtdTaskList *self,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ const gchar *uid;
+ guint position;
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ g_signal_handlers_disconnect_by_func (task, task_changed_cb, self);
+
+ uid = gtd_object_get_uid (GTD_OBJECT (task));
+ iter = g_hash_table_lookup (priv->tasks, uid);
+ position = g_sequence_iter_get_position (iter);
+
+ g_hash_table_remove (priv->task_to_uid, task);
+ g_hash_table_remove (priv->tasks, uid);
+
+ g_sequence_remove (iter);
+
+ priv->n_tasks--;
+
+ g_signal_emit (self, signals[TASK_REMOVED], 0, task);
+
+ return position;
+}
+
+
+/*
+ * Callbacks
+ */
+
+static void
+on_import_old_task_removed_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtdTask *new_task;
+ GTask *gtask;
+ g_autoptr (GError) error = NULL;
+
+ gtask = user_data;
+
+ gtd_provider_remove_task_finish (GTD_PROVIDER (object), result, &error);
+
+ if (error)
+ {
+ g_warning ("Error removing task: %s", error->message);
+ return;
+ }
+
+ new_task = g_task_get_task_data (gtask);
+ g_task_return_pointer (gtask, g_object_ref (new_task), g_object_unref);
+}
+
+static void
+on_task_updated_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+
+ gtd_provider_update_task_finish (GTD_PROVIDER (object), result, &error);
+
+ if (error)
+ {
+ g_warning ("Error updating task: %s", error->message);
+ return;
+ }
+}
+
+static void
+on_import_new_task_added_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GtdTask) new_task = NULL;
+ g_autoptr (GError) error = NULL;
+ GTask *gtask;
+ GtdProvider *provider;
+ GtdTask *importing_task;
+ GtdTaskList *list;
+ ImportingTaskData *data;
+
+ GTD_ENTRY;
+
+ data = user_data;
+ gtask = data->gtask;
+ importing_task = data->task;
+
+ new_task = gtd_provider_create_task_finish (GTD_PROVIDER (object), result, &error);
+ g_task_set_task_data (gtask, g_object_ref (new_task), g_object_unref);
+ list = gtd_task_get_list (new_task);
+
+ if (error)
+ {
+ g_warning ("Error creating task: %s", error->message);
+ GTD_RETURN ();
+ }
+
+ gtd_task_set_complete (new_task, gtd_task_get_complete (importing_task));
+ gtd_task_set_description (new_task, gtd_task_get_description (importing_task));
+ gtd_task_set_due_date (new_task, gtd_task_get_due_date (importing_task));
+ gtd_task_set_important (new_task, gtd_task_get_important (importing_task));
+ gtd_task_set_title (new_task, gtd_task_get_title (importing_task));
+
+ gtd_task_list_update_task (list, new_task);
+
+ provider = gtd_task_get_provider (importing_task);
+
+ gtd_provider_remove_task (provider,
+ importing_task,
+ NULL,
+ on_import_old_task_removed_cb,
+ gtask);
+
+ g_free (data);
+
+ GTD_EXIT;
+}
+
+static gint
+compare_tasks_cb (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ return gtd_task_compare ((GtdTask*) a, (GtdTask*) b);
+}
+
+static void
+task_changed_cb (GtdTask *task,
+ GParamSpec *pspec,
+ GtdTaskList *self)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ guint old_position;
+ guint new_position;
+
+ GTD_ENTRY;
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ if (g_strcmp0 (g_param_spec_get_name (pspec), "loading") == 0)
+ GTD_RETURN ();
+
+ if (g_strcmp0 (g_param_spec_get_name (pspec), "uid") == 0)
+ {
+ update_task_uid (self, task);
+ GTD_RETURN ();
+ }
+
+ /* Don't update when the list is frozen */
+ if (priv->freeze_counter > 0)
+ GTD_RETURN ();
+
+ iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
+
+ old_position = g_sequence_iter_get_position (iter);
+ g_sequence_sort_changed (iter, compare_tasks_cb, NULL);
+ new_position = g_sequence_iter_get_position (iter);
+
+ if (old_position != new_position)
+ {
+ GTD_TRACE_MSG ("Old position: %u, New position: %u", old_position, new_position);
+
+ g_list_model_items_changed (G_LIST_MODEL (self), old_position, 1, 0);
+ g_list_model_items_changed (G_LIST_MODEL (self), new_position, 0, 1);
+ }
+
+ GTD_EXIT;
+}
+
+
+/*
+ * GListModel iface
+ */
+
+static GType
+gtd_list_model_get_type (GListModel *model)
+{
+ return GTD_TYPE_TASK;
+}
+
+static guint
+gtd_list_model_get_n_items (GListModel *model)
+{
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (GTD_TASK_LIST (model));
+ return priv->n_tasks;
+}
+
+static gpointer
+gtd_list_model_get_item (GListModel *model,
+ guint i)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ GtdTask *task;
+
+ priv = gtd_task_list_get_instance_private (GTD_TASK_LIST (model));
+ iter = g_sequence_get_iter_at_pos (priv->sorted_tasks, i);
+ task = g_sequence_get (iter);
+
+ return g_object_ref (task);
+}
+
+static void
+g_list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtd_list_model_get_type;
+ iface->get_n_items = gtd_list_model_get_n_items;
+ iface->get_item = gtd_list_model_get_item;
+}
+
+
+/*
+ * GtdTaskList overrides
+ */
+
+static gboolean
+gtd_task_list_real_get_archived (GtdTaskList *self)
+{
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ return priv->archived;
+}
+
+static void
+gtd_task_list_real_set_archived (GtdTaskList *self,
+ gboolean archived)
+{
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ priv->archived = archived;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_task_list_finalize (GObject *object)
+{
+ GtdTaskList *self = (GtdTaskList*) object;
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ g_clear_object (&priv->provider);
+
+ g_clear_pointer (&priv->color, gdk_rgba_free);
+ g_clear_pointer (&priv->name, g_free);
+ g_clear_pointer (&priv->sorted_tasks, g_sequence_free);
+ g_clear_pointer (&priv->tasks, g_hash_table_destroy);
+ g_clear_pointer (&priv->task_to_uid, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (gtd_task_list_parent_class)->finalize (object);
+}
+
+static void
+gtd_task_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTaskList *self = GTD_TASK_LIST (object);
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_ARCHIVED:
+ g_value_set_boolean (value, gtd_task_list_get_archived (self));
+ break;
+
+ case PROP_COLOR:
+ {
+ GdkRGBA *color = gtd_task_list_get_color (self);
+ g_value_set_boxed (value, color);
+ gdk_rgba_free (color);
+ break;
+ }
+
+ case PROP_IS_REMOVABLE:
+ g_value_set_boolean (value, gtd_task_list_is_removable (self));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+
+ case PROP_PROVIDER:
+ g_value_set_object (value, priv->provider);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTaskList *self = GTD_TASK_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_ARCHIVED:
+ gtd_task_list_set_archived (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_COLOR:
+ gtd_task_list_set_color (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_IS_REMOVABLE:
+ gtd_task_list_set_is_removable (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_NAME:
+ gtd_task_list_set_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_PROVIDER:
+ gtd_task_list_set_provider (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_list_class_init (GtdTaskListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_task_list_finalize;
+ object_class->get_property = gtd_task_list_get_property;
+ object_class->set_property = gtd_task_list_set_property;
+
+ klass->get_archived = gtd_task_list_real_get_archived;
+ klass->set_archived = gtd_task_list_real_set_archived;
+
+ /**
+ * GtdTaskList::archived:
+ *
+ * Whether the task list is archived or not.
+ */
+ properties[PROP_ARCHIVED] = g_param_spec_boolean ("archived",
+ "Whether the list is archived",
+ "Whether the list is archived or not",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtdTaskList::color:
+ *
+ * The color of the list.
+ */
+ properties[PROP_COLOR] = g_param_spec_boxed ("color",
+ "Color of the list",
+ "The color of the list",
+ GDK_TYPE_RGBA,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtdTaskList::is-removable:
+ *
+ * Whether the task list can be removed from the system.
+ */
+ properties[PROP_IS_REMOVABLE] = g_param_spec_boolean ("is-removable",
+ "Whether the task list is removable",
+ "Whether the task list can be removed from the system",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtdTaskList::name:
+ *
+ * The display name of the list.
+ */
+ properties[PROP_NAME] = g_param_spec_string ("name",
+ "Name of the list",
+ "The name of the list",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtdTaskList::provider:
+ *
+ * The data provider of the list.
+ */
+ properties[PROP_PROVIDER] = g_param_spec_object ("provider",
+ "Provider of the list",
+ "The provider that handles the list",
+ GTD_TYPE_PROVIDER,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ /**
+ * GtdTaskList::task-added:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * The ::task-added signal is emitted after a #GtdTask
+ * is added to the list.
+ */
+ signals[TASK_ADDED] = g_signal_new ("task-added",
+ GTD_TYPE_TASK_LIST,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtdTaskListClass, task_added),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK);
+
+ /**
+ * GtdTaskList::task-removed:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * The ::task-removed signal is emitted after a #GtdTask
+ * is removed from the list.
+ */
+ signals[TASK_REMOVED] = g_signal_new ("task-removed",
+ GTD_TYPE_TASK_LIST,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtdTaskListClass, task_removed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK);
+
+ /**
+ * GtdTaskList::task-updated:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * The ::task-updated signal is emitted after a #GtdTask
+ * in the list is updated.
+ */
+ signals[TASK_UPDATED] = g_signal_new ("task-updated",
+ GTD_TYPE_TASK_LIST,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtdTaskListClass, task_updated),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK);
+}
+
+static void
+gtd_task_list_init (GtdTaskList *self)
+{
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ priv->task_to_uid = g_hash_table_new (g_str_hash, g_str_equal);
+ priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ priv->sorted_tasks = g_sequence_new (g_object_unref);
+}
+
+/**
+ * gtd_task_list_new:
+ * @provider: (nullable): a #GtdProvider
+ *
+ * Creates a new list.
+ *
+ * Returns: (transfer full): the new #GtdTaskList
+ */
+GtdTaskList *
+gtd_task_list_new (GtdProvider *provider)
+{
+ return g_object_new (GTD_TYPE_TASK_LIST,
+ "provider", provider,
+ NULL);
+}
+
+/**
+ * gtd_task_list_get_color:
+ * @list: a #GtdTaskList
+ *
+ * Retrieves the color of %list. It is guarantee that it always returns a
+ * color, given a valid #GtdTaskList.
+ *
+ * Returns: (transfer full): the color of %list. Free with %gdk_rgba_free after use.
+ */
+GdkRGBA*
+gtd_task_list_get_color (GtdTaskList *list)
+{
+ GtdTaskListPrivate *priv;
+ GdkRGBA rgba;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ if (!priv->color)
+ {
+ gdk_rgba_parse (&rgba, "#ffffff");
+ priv->color = gdk_rgba_copy (&rgba);
+ }
+
+ return gdk_rgba_copy (priv->color);
+}
+
+/**
+ * gtd_task_list_set_color:
+ * @list: a #GtdTaskList
+ * #color: a #GdkRGBA
+ *
+ * sets the color of @list.
+ */
+void
+gtd_task_list_set_color (GtdTaskList *list,
+ const GdkRGBA *color)
+{
+ GtdTaskListPrivate *priv;
+ GdkRGBA *current_color;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (list));
+
+ priv = gtd_task_list_get_instance_private (list);
+ current_color = gtd_task_list_get_color (list);
+
+ if (!gdk_rgba_equal (current_color, color))
+ {
+ g_clear_pointer (&priv->color, gdk_rgba_free);
+ priv->color = gdk_rgba_copy (color);
+
+ g_object_notify (G_OBJECT (list), "color");
+ }
+
+ gdk_rgba_free (current_color);
+}
+
+/**
+ * gtd_task_list_get_name:
+ * @list: a #GtdTaskList
+ *
+ * Retrieves the user-visible name of @list, or %NULL.
+ *
+ * Returns: (transfer none): the internal name of @list. Do not free
+ * after use.
+ */
+const gchar*
+gtd_task_list_get_name (GtdTaskList *list)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ return priv->name;
+}
+
+/**
+ * gtd_task_list_set_name:
+ * @list: a #GtdTaskList
+ * @name: (nullable): the name of @list
+ *
+ * Sets the @list name to @name.
+ */
+void
+gtd_task_list_set_name (GtdTaskList *list,
+ const gchar *name)
+{
+ GtdTaskListPrivate *priv;
+
+ g_assert (GTD_IS_TASK_LIST (list));
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ if (g_strcmp0 (priv->name, name) != 0)
+ {
+ g_free (priv->name);
+ priv->name = g_strdup (name);
+
+ g_object_notify (G_OBJECT (list), "name");
+ }
+}
+
+/**
+ * gtd_task_list_get_provider:
+ * @list: a #GtdTaskList
+ *
+ * Retrieves the #GtdProvider who owns this list.
+ *
+ * Returns: (transfer none): a #GtdProvider
+ */
+GtdProvider*
+gtd_task_list_get_provider (GtdTaskList *list)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ return priv->provider;
+}
+
+/**
+ * gtd_task_list_set_provider:
+ * @self: a #GtdTaskList
+ * @provider: (nullable): a #GtdProvider, or %NULL
+ *
+ * Sets the provider of this tasklist.
+ */
+void
+gtd_task_list_set_provider (GtdTaskList *self,
+ GtdProvider *provider)
+{
+ GtdTaskListPrivate *priv;
+
+ g_assert (GTD_IS_TASK_LIST (self));
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ if (g_set_object (&priv->provider, provider))
+ g_object_notify (G_OBJECT (self), "provider");
+}
+
+/**
+ * gtd_task_list_add_task:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Adds @task to @list.
+ */
+void
+gtd_task_list_add_task (GtdTaskList *self,
+ GtdTask *task)
+{
+ guint position;
+
+ g_assert (GTD_IS_TASK_LIST (self));
+ g_assert (GTD_IS_TASK (task));
+ g_assert (!gtd_task_list_contains (self, task));
+
+ position = add_task (self, task);
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+}
+
+/**
+ * gtd_task_list_update_task:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Updates @task at @list.
+ */
+void
+gtd_task_list_update_task (GtdTaskList *self,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (self));
+ g_return_if_fail (GTD_IS_TASK (task));
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ g_return_if_fail (gtd_task_list_contains (self, task));
+
+ iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
+
+ g_list_model_items_changed (G_LIST_MODEL (self),
+ g_sequence_iter_get_position (iter),
+ 1,
+ 1);
+
+ g_signal_emit (self, signals[TASK_UPDATED], 0, task);
+}
+
+/**
+ * gtd_task_list_remove_task:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Removes @task from @list if it's inside the list.
+ */
+void
+gtd_task_list_remove_task (GtdTaskList *list,
+ GtdTask *task)
+{
+ guint position;
+
+ g_assert (GTD_IS_TASK_LIST (list));
+ g_assert (GTD_IS_TASK (task));
+ g_assert (gtd_task_list_contains (list, task));
+
+ position = remove_task (list, task);
+ g_list_model_items_changed (G_LIST_MODEL (list), position, 1, 0);
+}
+
+/**
+ * gtd_task_list_contains:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Checks if @task is inside @list.
+ *
+ * Returns: %TRUE if @list contains @task, %FALSE otherwise
+ */
+gboolean
+gtd_task_list_contains (GtdTaskList *list,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+
+ g_assert (GTD_IS_TASK_LIST (list));
+ g_assert (GTD_IS_TASK (task));
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ return g_hash_table_contains (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
+}
+
+/**
+ * gtd_task_list_get_is_removable:
+ * @list: a #GtdTaskList
+ *
+ * Retrieves whether @list can be removed or not.
+ *
+ * Returns: %TRUE if the @list can be removed, %FALSE otherwise
+ */
+gboolean
+gtd_task_list_is_removable (GtdTaskList *list)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (list), FALSE);
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ return priv->removable;
+}
+
+/**
+ * gtd_task_list_set_is_removable:
+ * @list: a #GtdTaskList
+ * @is_removable: %TRUE if @list can be deleted, %FALSE otherwise
+ *
+ * Sets whether @list can be deleted or not.
+ */
+void
+gtd_task_list_set_is_removable (GtdTaskList *list,
+ gboolean is_removable)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (list));
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ if (priv->removable != is_removable)
+ {
+ priv->removable = is_removable;
+
+ g_object_notify (G_OBJECT (list), "is-removable");
+ }
+}
+
+/**
+ * gtd_task_list_get_task_by_id:
+ * @self: a #GtdTaskList
+ * @id: the id of the task
+ *
+ * Retrieves a task from @self with the given @id.
+ *
+ * Returns: (transfer none)(nullable): a #GtdTask, or %NULL
+ */
+GtdTask*
+gtd_task_list_get_task_by_id (GtdTaskList *self,
+ const gchar *id)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (self), NULL);
+
+ priv = gtd_task_list_get_instance_private (self);
+ iter = g_hash_table_lookup (priv->tasks, id);
+
+ if (!iter)
+ return NULL;
+
+ return g_sequence_get (iter);
+}
+
+/**
+ * gtd_task_list_move_task_to_position:
+ * @self: a #GtdTaskList
+ * @task: a #GtdTask
+ * @new_position: the new position of @task inside @self
+ *
+ * Moves @task to @new_position, and repositions the elements
+ * in between as well.
+ *
+ * @task must belong to @self.
+ */
+void
+gtd_task_list_move_task_to_position (GtdTaskList *self,
+ GtdTask *task,
+ guint new_position)
+{
+
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+ GSequenceIter *new_position_iter;
+ GSequenceIter *iter;
+ guint old_position;
+ guint length;
+ guint start;
+ guint i;
+
+
+ g_return_if_fail (GTD_IS_TASK_LIST (self));
+ g_return_if_fail (GTD_IS_TASK (task));
+ g_return_if_fail (gtd_task_list_contains (self, task));
+ g_return_if_fail (g_list_model_get_n_items (G_LIST_MODEL (self)) >= new_position);
+
+ iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
+ old_position = g_sequence_iter_get_position (iter);
+
+ if (old_position == new_position)
+ return;
+
+ GTD_TRACE_MSG ("Moving task '%s' (%s) from %u to %u",
+ gtd_task_get_title (task),
+ gtd_object_get_uid (GTD_OBJECT (task)),
+ old_position,
+ new_position);
+
+ /* Update the GSequence */
+ new_position_iter = new_position < old_position ?
+ g_sequence_get_iter_at_pos (priv->sorted_tasks, new_position) :
+ g_sequence_get_iter_at_pos (priv->sorted_tasks, new_position + 1);
+ g_sequence_move (iter, new_position_iter);
+
+ /* Update the 'position' property of all tasks in between */
+ priv->freeze_counter++;
+
+ length = ABS ((gint) new_position - (gint64) old_position) + 1;
+ start = MIN (old_position, new_position);
+ iter = g_sequence_get_iter_at_pos (priv->sorted_tasks, start);
+
+ for (i = 0; i < length; i++)
+ {
+ GtdTask *aux = g_sequence_get (iter);
+
+ g_signal_handlers_block_by_func (aux, task_changed_cb, self);
+ gtd_task_set_position (aux, start + i);
+ g_signal_handlers_unblock_by_func (aux, task_changed_cb, self);
+
+ gtd_provider_update_task (priv->provider, aux, NULL, on_task_updated_cb, self);
+
+ iter = g_sequence_iter_next (iter);
+ }
+
+ g_list_model_items_changed (G_LIST_MODEL (self), old_position, 1, 0);
+ g_list_model_items_changed (G_LIST_MODEL (self), new_position, 0, 1);
+
+ priv->freeze_counter--;
+}
+
+/**
+ * gtd_task_list_get_archived:
+ * @self: a #GtdTaskList
+ *
+ * Retrieves whether @self is archived or not. Archived task lists
+ * are hidden by default, and new tasks cannot be added.
+ *
+ * Returns: %TRUE if @self is archived, %FALSE otherwise.
+ */
+gboolean
+gtd_task_list_get_archived (GtdTaskList *self)
+{
+ g_return_val_if_fail (GTD_IS_TASK_LIST (self), FALSE);
+
+ return GTD_TASK_LIST_GET_CLASS (self)->get_archived (self);
+}
+
+/**
+ * gtd_task_list_set_archived:
+ * @self: a #GtdTaskList
+ * @archived: whether @self is archived or not
+ *
+ * Sets the "archive" property of @self to @archived.
+ */
+void
+gtd_task_list_set_archived (GtdTaskList *self,
+ gboolean archived)
+{
+ gboolean was_archived;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (self));
+
+ was_archived = gtd_task_list_get_archived (self);
+
+ if (archived == was_archived)
+ return;
+
+ GTD_TASK_LIST_GET_CLASS (self)->set_archived (self, archived);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ARCHIVED]);
+}
+
+/**
+ * gtd_task_list_import_task:
+ * @self: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Imports task into @self
+ */
+void
+gtd_task_list_import_task (GtdTaskList *self,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) gtask = NULL;
+ GtdProvider *provider;
+ ImportingTaskData *data;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (self));
+
+ gtask = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (gtask, gtd_task_list_import_task);
+
+ provider = gtd_task_get_provider (task);
+
+ data = g_new0 (ImportingTaskData, 1);
+ data->gtask = g_object_ref (gtask);
+ data->task = g_object_ref (task);
+
+ gtd_provider_create_task (provider,
+ self,
+ gtd_task_get_title (task),
+ gtd_task_get_due_date (task),
+ NULL,
+ on_import_new_task_added_cb,
+ data);
+}
+
+/**
+ * gtd_task_list_import_task_finish:
+ * @self: a #GtdTaskList
+ * @result: a #GAsyncResult
+ * @error: a #GError
+ *
+ * Imports task into @self
+ *
+ * Returns: (transfer full): a #GTask
+ */
+GTask *
+gtd_task_list_import_task_finish (GtdTaskList *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), NULL);
+ g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gtd_task_list_import_task, NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+
+/**
+ * gtd_task_list_is_inbox:
+ * @self: a #GtdTaskList
+ *
+ * Retrieves whether @self is the inbox task list of its provider.
+ *
+ * Returns: %TRUE if @self is the inbox of it's provider, %FALSE otherwise.
+ */
+gboolean
+gtd_task_list_is_inbox (GtdTaskList *self)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (self), FALSE);
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ return self == gtd_provider_get_inbox (priv->provider);
+}
diff --git a/src/core/gtd-task-list.h b/src/core/gtd-task-list.h
new file mode 100644
index 0000000..6f06490
--- /dev/null
+++ b/src/core/gtd-task-list.h
@@ -0,0 +1,118 @@
+/* gtd-task-list.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_H
+#define GTD_TASK_LIST_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <glib.h>
+
+#include "gtd-object.h"
+#include "gtd-types.h"
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_TASK_LIST (gtd_task_list_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GtdTaskList, gtd_task_list, GTD, TASK_LIST, GtdObject)
+
+struct _GtdTaskListClass
+{
+ GtdObjectClass parent;
+
+ /* Vfuncs */
+ gboolean (*get_archived) (GtdTaskList *self);
+
+ void (*set_archived) (GtdTaskList *self,
+ gboolean archived);
+
+ /* Signal methods */
+ void (*task_added) (GtdTaskList *list,
+ GtdTask *task);
+
+ void (*task_updated) (GtdTaskList *list,
+ GtdTask *task);
+
+ void (*task_removed) (GtdTaskList *list,
+ GtdTask *task);
+
+ gpointer padding[10];
+};
+
+GtdTaskList* gtd_task_list_new (GtdProvider *provider);
+
+GdkRGBA* gtd_task_list_get_color (GtdTaskList *list);
+
+void gtd_task_list_set_color (GtdTaskList *list,
+ const GdkRGBA *color);
+
+gboolean gtd_task_list_is_removable (GtdTaskList *list);
+
+void gtd_task_list_set_is_removable (GtdTaskList *list,
+ gboolean is_removable);
+
+const gchar* gtd_task_list_get_name (GtdTaskList *list);
+
+void gtd_task_list_set_name (GtdTaskList *list,
+ const gchar *name);
+
+GtdProvider* gtd_task_list_get_provider (GtdTaskList *list);
+
+void gtd_task_list_set_provider (GtdTaskList *self,
+ GtdProvider *provider);
+
+void gtd_task_list_add_task (GtdTaskList *list,
+ GtdTask *task);
+
+void gtd_task_list_update_task (GtdTaskList *list,
+ GtdTask *task);
+
+void gtd_task_list_remove_task (GtdTaskList *list,
+ GtdTask *task);
+
+gboolean gtd_task_list_contains (GtdTaskList *list,
+ GtdTask *task);
+
+GtdTask* gtd_task_list_get_task_by_id (GtdTaskList *self,
+ const gchar *id);
+
+void gtd_task_list_move_task_to_position (GtdTaskList *self,
+ GtdTask *task,
+ guint new_position);
+
+gboolean gtd_task_list_get_archived (GtdTaskList *self);
+
+void gtd_task_list_set_archived (GtdTaskList *self,
+ gboolean archived);
+
+void gtd_task_list_import_task (GtdTaskList *self,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GTask * gtd_task_list_import_task_finish (GtdTaskList *self,
+ GAsyncResult *result,
+ GError **error);
+
+gboolean gtd_task_list_is_inbox (GtdTaskList *self);
+
+G_END_DECLS
+
+#endif /* GTD_TASK_LIST_H */
diff --git a/src/core/gtd-task.c b/src/core/gtd-task.c
new file mode 100644
index 0000000..6951dec
--- /dev/null
+++ b/src/core/gtd-task.c
@@ -0,0 +1,993 @@
+/* gtd-task.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 "GtdTask"
+
+#include "gtd-debug.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+
+#include <glib/gi18n.h>
+
+/**
+ * SECTION:gtd-task
+ * @short_description: a task
+ * @title:GtdTask
+ * @stability:Unstable
+ * @see_also:#GtdTaskList
+ *
+ * A #GtdTask is an object that represents a task. All #GtdTasks
+ * must be inside a #GtdTaskList.
+ */
+
+typedef struct
+{
+ gchar *description;
+ GtdTaskList *list;
+
+ GDateTime *creation_date;
+ GDateTime *completion_date;
+ GDateTime *due_date;
+
+ gchar *title;
+
+ gint32 priority;
+ gint64 position;
+ gboolean complete;
+ gboolean important;
+} GtdTaskPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtdTask, gtd_task, GTD_TYPE_OBJECT)
+
+enum
+{
+ PROP_0,
+ PROP_COMPLETE,
+ PROP_DESCRIPTION,
+ PROP_CREATION_DATE,
+ PROP_DUE_DATE,
+ PROP_IMPORTANT,
+ PROP_LIST,
+ PROP_POSITION,
+ PROP_TITLE,
+ LAST_PROP
+};
+
+static void
+task_list_weak_notified (gpointer data,
+ GObject *where_the_object_was)
+{
+ GtdTask *task = GTD_TASK (data);
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (task);
+ priv->list = NULL;
+}
+
+/*
+ * GtdTask default implementations
+ */
+
+static GDateTime*
+gtd_task_real_get_completion_date (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ return priv->completion_date ? g_date_time_ref (priv->completion_date) : NULL;
+}
+
+static void
+gtd_task_real_set_completion_date (GtdTask *self,
+ GDateTime *dt)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ g_clear_pointer (&priv->completion_date, g_date_time_unref);
+ priv->completion_date = dt ? g_date_time_ref (dt) : NULL;
+}
+
+static gboolean
+gtd_task_real_get_complete (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ return priv->complete;
+}
+
+static void
+gtd_task_real_set_complete (GtdTask *self,
+ gboolean complete)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+ GDateTime *dt;
+
+ dt = complete ? g_date_time_new_now_local () : NULL;
+ gtd_task_real_set_completion_date (self, dt);
+
+ priv->complete = complete;
+}
+
+static GDateTime*
+gtd_task_real_get_creation_date (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ return priv->creation_date ? g_date_time_ref (priv->creation_date) : NULL;
+}
+
+static void
+gtd_task_real_set_creation_date (GtdTask *self,
+ GDateTime *dt)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ g_clear_pointer (&priv->creation_date, g_date_time_unref);
+ priv->creation_date = dt ? g_date_time_ref (dt) : NULL;
+}
+
+static const gchar*
+gtd_task_real_get_description (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ return priv->description ? priv->description : "";
+}
+
+static void
+gtd_task_real_set_description (GtdTask *self,
+ const gchar *description)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ g_clear_pointer (&priv->description, g_free);
+ priv->description = g_strdup (description);
+}
+
+static GDateTime*
+gtd_task_real_get_due_date (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ return priv->due_date ? g_date_time_ref (priv->due_date) : NULL;
+}
+
+static void
+gtd_task_real_set_due_date (GtdTask *self,
+ GDateTime *due_date)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ g_clear_pointer (&priv->due_date, g_date_time_unref);
+
+ if (due_date)
+ priv->due_date = g_date_time_ref (due_date);
+}
+
+static gboolean
+gtd_task_real_get_important (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ return priv->important;
+}
+
+static void
+gtd_task_real_set_important (GtdTask *self,
+ gboolean important)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ if (priv->important == important)
+ return;
+
+ priv->important = important;
+}
+
+static gint64
+gtd_task_real_get_position (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ return priv->position;
+}
+
+static void
+gtd_task_real_set_position (GtdTask *self,
+ gint64 position)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ priv->position = position;
+}
+
+static const gchar*
+gtd_task_real_get_title (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ return priv->title;
+}
+
+static void
+gtd_task_real_set_title (GtdTask *self,
+ const gchar *title)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ g_clear_pointer (&priv->title, g_free);
+ priv->title = title ? g_strdup (title) : NULL;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_task_finalize (GObject *object)
+{
+ GtdTask *self = (GtdTask*) object;
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ if (priv->list)
+ g_object_weak_unref (G_OBJECT (priv->list), task_list_weak_notified, self);
+
+ priv->list = NULL;
+ g_free (priv->description);
+
+ G_OBJECT_CLASS (gtd_task_parent_class)->finalize (object);
+}
+
+static void
+gtd_task_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTask *self = GTD_TASK (object);
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+ GDateTime *date;
+
+ switch (prop_id)
+ {
+ case PROP_COMPLETE:
+ g_value_set_boolean (value, gtd_task_get_complete (self));
+ break;
+
+ case PROP_CREATION_DATE:
+ g_value_set_boxed (value, gtd_task_get_creation_date (self));
+ break;
+
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, gtd_task_get_description (self));
+ break;
+
+ case PROP_DUE_DATE:
+ date = gtd_task_get_due_date (self);
+ g_value_set_boxed (value, date);
+ g_clear_pointer (&date, g_date_time_unref);
+ break;
+
+ case PROP_IMPORTANT:
+ g_value_set_boolean (value, gtd_task_get_important (self));
+ break;
+
+ case PROP_LIST:
+ g_value_set_object (value, priv->list);
+ break;
+
+ case PROP_POSITION:
+ g_value_set_int64 (value, gtd_task_get_position (self));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtd_task_get_title (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTask *self = GTD_TASK (object);
+
+ switch (prop_id)
+ {
+ case PROP_COMPLETE:
+ gtd_task_set_complete (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_CREATION_DATE:
+ gtd_task_set_creation_date (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_DESCRIPTION:
+ gtd_task_set_description (self, g_value_get_string (value));
+ break;
+
+ case PROP_DUE_DATE:
+ gtd_task_set_due_date (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_IMPORTANT:
+ gtd_task_set_important (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_LIST:
+ gtd_task_set_list (self, g_value_get_object (value));
+ break;
+
+ case PROP_POSITION:
+ gtd_task_set_position (self, g_value_get_int64 (value));
+ break;
+
+ case PROP_TITLE:
+ gtd_task_set_title (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_class_init (GtdTaskClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ klass->get_complete = gtd_task_real_get_complete;
+ klass->set_complete = gtd_task_real_set_complete;
+ klass->get_creation_date = gtd_task_real_get_creation_date;
+ klass->set_creation_date = gtd_task_real_set_creation_date;
+ klass->get_completion_date = gtd_task_real_get_completion_date;
+ klass->set_completion_date = gtd_task_real_set_completion_date;
+ klass->get_description = gtd_task_real_get_description;
+ klass->set_description = gtd_task_real_set_description;
+ klass->get_due_date = gtd_task_real_get_due_date;
+ klass->set_due_date = gtd_task_real_set_due_date;
+ klass->get_important = gtd_task_real_get_important;
+ klass->set_important = gtd_task_real_set_important;
+ klass->get_position = gtd_task_real_get_position;
+ klass->set_position = gtd_task_real_set_position;
+ klass->get_title = gtd_task_real_get_title;
+ klass->set_title = gtd_task_real_set_title;
+
+ object_class->finalize = gtd_task_finalize;
+ object_class->get_property = gtd_task_get_property;
+ object_class->set_property = gtd_task_set_property;
+
+ /**
+ * GtdTask::complete:
+ *
+ * @TRUE if the task is marked as complete or @FALSE otherwise. Usually
+ * represented by a checkbox at user interfaces.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_COMPLETE,
+ g_param_spec_boolean ("complete",
+ "Whether the task is completed or not",
+ "Whether the task is marked as completed by the user",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtdTask::creation-date:
+ *
+ * The @GDateTime that represents the time in which the task was created.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CREATION_DATE,
+ g_param_spec_boxed ("creation-date",
+ "Creation date of the task",
+ "The day the task was created.",
+ G_TYPE_DATE_TIME,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GtdTask::description:
+ *
+ * Description of the task.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_DESCRIPTION,
+ g_param_spec_string ("description",
+ "Description of the task",
+ "Optional string describing the task",
+ NULL,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtdTask::due-date:
+ *
+ * The @GDateTime that represents the time in which the task should
+ * be completed before.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_DUE_DATE,
+ g_param_spec_boxed ("due-date",
+ "End date of the task",
+ "The day the task is supposed to be completed",
+ G_TYPE_DATE_TIME,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtdTask::important:
+ *
+ * @TRUE if the task is important, @FALSE otherwise.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_IMPORTANT,
+ g_param_spec_boolean ("important",
+ "Whether the task is important or not",
+ "Whether the task is important or not",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GtdTask::list:
+ *
+ * The @GtdTaskList that contains this task.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_LIST,
+ g_param_spec_object ("list",
+ "List of the task",
+ "The list that owns this task",
+ GTD_TYPE_TASK_LIST,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtdTask::position:
+ *
+ * Position of the task, -1 if not set.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_POSITION,
+ g_param_spec_int64 ("position",
+ "Position of the task",
+ "The position of the task. -1 means no position, and tasks will be sorted alphabetically.",
+ -1,
+ G_MAXINT64,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GtdTask::title:
+ *
+ * The title of the task, usually the task name.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_TITLE,
+ g_param_spec_string ("title",
+ "Title of the task",
+ "The title of the task",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+gtd_task_init (GtdTask *self)
+{
+ GtdTaskPrivate *priv = gtd_task_get_instance_private (self);
+
+ priv->position = -1;
+}
+
+/**
+ * gtd_task_new:
+ *
+ * Creates a new #GtdTask
+ *
+ * Returns: (transfer full): a #GtdTask
+ */
+GtdTask *
+gtd_task_new (void)
+{
+ return g_object_new (GTD_TYPE_TASK, NULL);
+}
+
+/**
+ * gtd_task_get_complete:
+ * @self: a #GtdTask
+ *
+ * Retrieves whether the task is complete or not.
+ *
+ * Returns: %TRUE if the task is complete, %FALSE otherwise
+ */
+gboolean
+gtd_task_get_complete (GtdTask *self)
+{
+ g_return_val_if_fail (GTD_IS_TASK (self), FALSE);
+
+ return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (self))->get_complete (self);
+}
+
+/**
+ * gtd_task_set_complete:
+ * @self: a #GtdTask
+ * @complete: the new value
+ *
+ * Updates the complete state of @task.
+ */
+void
+gtd_task_set_complete (GtdTask *task,
+ gboolean complete)
+{
+ g_return_if_fail (GTD_IS_TASK (task));
+
+ if (gtd_task_get_complete (task) == complete)
+ return;
+
+ GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_complete (task, complete);
+
+ g_object_notify (G_OBJECT (task), "complete");
+}
+
+/**
+ * gtd_task_get_creation_date:
+ * @self: a #GtdTask
+ *
+ * Returns the #GDateTime that represents the task's creation date.
+ * The value is referenced for thread safety. Returns %NULL if
+ * no date is set.
+ *
+ * Returns: (transfer full): the internal #GDateTime referenced
+ * for thread safety, or %NULL. Unreference it after use.
+ */
+GDateTime*
+gtd_task_get_creation_date (GtdTask *task)
+{
+ g_return_val_if_fail (GTD_IS_TASK (task), NULL);
+
+ return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_creation_date (task);
+}
+
+/**
+ * gtd_task_set_creation_date:
+ * @self: a #GtdTask
+ *
+ * Sets the creation date of @task.
+ */
+void
+gtd_task_set_creation_date (GtdTask *task,
+ GDateTime *dt)
+{
+ g_return_if_fail (GTD_IS_TASK (task));
+
+ if (gtd_task_get_creation_date (task) == dt)
+ return;
+
+ GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_creation_date (task, dt);
+
+ g_object_notify (G_OBJECT (task), "complete");
+}
+
+/**
+ * gtd_task_get_completion_date:
+ * @self: a #GtdTask
+ *
+ * Returns the #GDateTime that represents the task's completion date.
+ * Returns %NULL if no date is set.
+ *
+ * Returns: (transfer full)(nullable): the internal #GDateTime or %NULL.
+ * Unreference it after use.
+ */
+GDateTime*
+gtd_task_get_completion_date (GtdTask *task)
+{
+ g_return_val_if_fail (GTD_IS_TASK (task), NULL);
+
+ return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_completion_date (task);
+}
+
+/**
+ * gtd_task_get_description:
+ * @self: a #GtdTask
+ *
+ * Retrieves the description of the task.
+ *
+ * Returns: (transfer none): the description of @task
+ */
+const gchar*
+gtd_task_get_description (GtdTask *task)
+{
+ g_return_val_if_fail (GTD_IS_TASK (task), NULL);
+
+ return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_description (task);
+}
+
+/**
+ * gtd_task_set_description:
+ * @self: a #GtdTask
+ * @description: (nullable): the new description, or %NULL
+ *
+ * Updates the description of @task. The string is not stripped off of
+ * spaces to preserve user data.
+ */
+void
+gtd_task_set_description (GtdTask *task,
+ const gchar *description)
+{
+ GtdTaskPrivate *priv;
+
+ g_assert (GTD_IS_TASK (task));
+ g_assert (g_utf8_validate (description, -1, NULL));
+
+ priv = gtd_task_get_instance_private (task);
+
+ if (g_strcmp0 (priv->description, description) == 0)
+ return;
+
+ GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_description (task, description);
+
+ g_object_notify (G_OBJECT (task), "description");
+}
+
+/**
+ * gtd_task_get_due_date:
+ * @self: a #GtdTask
+ *
+ * Returns the #GDateTime that represents the task's due date.
+ * The value is referenced for thread safety. Returns %NULL if
+ * no date is set.
+ *
+ * Returns: (transfer full) (nullable): the internal #GDateTime referenced
+ * for thread safety, or %NULL. Unreference it after use.
+ */
+GDateTime*
+gtd_task_get_due_date (GtdTask *task)
+{
+ g_return_val_if_fail (GTD_IS_TASK (task), NULL);
+
+ return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_due_date (task);
+}
+
+/**
+ * gtd_task_set_due_date:
+ * @self: a #GtdTask
+ * @dt: (nullable): a #GDateTime
+ *
+ * Updates the internal @GtdTask::due-date property.
+ */
+void
+gtd_task_set_due_date (GtdTask *task,
+ GDateTime *dt)
+{
+ g_autoptr (GDateTime) current_dt = NULL;
+
+ g_assert (GTD_IS_TASK (task));
+
+ current_dt = gtd_task_get_due_date (task);
+
+ /* Don't do anything if the date is equal */
+ if (current_dt == dt || (current_dt && dt && g_date_time_equal (current_dt, dt)))
+ return;
+
+ GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_due_date (task, dt);
+
+ g_object_notify (G_OBJECT (task), "due-date");
+}
+
+/**
+ * gtd_task_get_important:
+ * @self: a #GtdTask
+ *
+ * Retrieves whether @self is @important or not.
+ *
+ * Returns: %TRUE if @self is important, %FALSE otherwise
+ */
+gboolean
+gtd_task_get_important (GtdTask *self)
+{
+ g_return_val_if_fail (GTD_IS_TASK (self), FALSE);
+
+ return GTD_TASK_GET_CLASS (self)->get_important (self);
+}
+
+/**
+ * gtd_task_set_important:
+ * @self: a #GtdTask
+ * @important: whether @self is important or not
+ *
+ * Sets whether @self is @important or not.
+ */
+void
+gtd_task_set_important (GtdTask *self,
+ gboolean important)
+{
+ g_return_if_fail (GTD_IS_TASK (self));
+
+ important = !!important;
+
+ GTD_TASK_GET_CLASS (self)->set_important (self, important);
+ g_object_notify (G_OBJECT (self), "important");
+}
+
+/**
+ * gtd_task_get_list:
+ *
+ * Returns a weak reference to the #GtdTaskList that
+ * owns the given @task.
+ *
+ * Returns: (transfer none): a weak reference to the
+ * #GtdTaskList that owns @task. Do not free after
+ * usage.
+ */
+GtdTaskList*
+gtd_task_get_list (GtdTask *task)
+{
+ GtdTaskPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK (task), NULL);
+
+ priv = gtd_task_get_instance_private (task);
+
+ return priv->list;
+}
+
+/**
+ * gtd_task_set_list:
+ * @self: a #GtdTask
+ * @list: (nullable): a #GtdTaskList
+ *
+ * Sets the parent #GtdTaskList of @task.
+ */
+void
+gtd_task_set_list (GtdTask *task,
+ GtdTaskList *list)
+{
+ GtdTaskPrivate *priv;
+
+ g_assert (GTD_IS_TASK (task));
+ g_assert (GTD_IS_TASK_LIST (list));
+
+ priv = gtd_task_get_instance_private (task);
+
+ if (priv->list == list)
+ return;
+
+ if (priv->list)
+ g_object_weak_unref (G_OBJECT (priv->list), task_list_weak_notified, task);
+
+ priv->list = list;
+ g_object_weak_ref (G_OBJECT (priv->list), task_list_weak_notified, task);
+ g_object_notify (G_OBJECT (task), "list");
+}
+
+/**
+ * gtd_task_get_position:
+ * @self: a #GtdTask
+ *
+ * Returns the position of @task inside the parent #GtdTaskList,
+ * or -1 if not set.
+ *
+ * Returns: the position of the task, or -1
+ */
+gint64
+gtd_task_get_position (GtdTask *self)
+{
+ g_return_val_if_fail (GTD_IS_TASK (self), -1);
+
+ return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (self))->get_position (self);
+}
+
+/**
+ * gtd_task_set_position:
+ * @self: a #GtdTask
+ * @position: the priority of @task, or -1
+ *
+ * Sets the @task position inside the parent #GtdTaskList. It
+ * is up to the interface to handle two or more #GtdTask with
+ * the same position value.
+ */
+void
+gtd_task_set_position (GtdTask *self,
+ gint64 position)
+{
+ g_return_if_fail (GTD_IS_TASK (self));
+
+ if (gtd_task_get_position (self) == position)
+ return;
+
+ GTD_TASK_CLASS (G_OBJECT_GET_CLASS (self))->set_position (self, position);
+
+ g_object_notify (G_OBJECT (self), "position");
+}
+
+/**
+ * gtd_task_get_title:
+ * @self: a #GtdTask
+ *
+ * Retrieves the title of the task, or %NULL.
+ *
+ * Returns: (transfer none): the title of @task, or %NULL
+ */
+const gchar*
+gtd_task_get_title (GtdTask *task)
+{
+ const gchar *title;
+
+ g_return_val_if_fail (GTD_IS_TASK (task), NULL);
+
+ title = GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_title (task);
+
+ return title ? title : "";
+}
+
+/**
+ * gtd_task_set_title:
+ * @self: a #GtdTask
+ * @title: (nullable): the new title, or %NULL
+ *
+ * Updates the title of @task. The string is stripped off of
+ * leading spaces.
+ */
+void
+gtd_task_set_title (GtdTask *task,
+ const gchar *title)
+{
+ const gchar *current_title;
+
+ g_return_if_fail (GTD_IS_TASK (task));
+ g_return_if_fail (g_utf8_validate (title, -1, NULL));
+
+ current_title = gtd_task_get_title (task);
+
+ if (g_strcmp0 (current_title, title) == 0)
+ return;
+
+ GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_title (task, title);
+
+ g_object_notify (G_OBJECT (task), "title");
+}
+
+/**
+ * gtd_task_compare:
+ * @t1: (nullable): a #GtdTask
+ * @t2: (nullable): a #GtdTask
+ *
+ * Compare @t1 and @t2.
+ *
+ * Returns: %-1 if @t1 comes before @t2, %1 for the opposite, %0 if they're equal
+ */
+gint
+gtd_task_compare (GtdTask *t1,
+ GtdTask *t2)
+{
+ GDateTime *dt1;
+ GDateTime *dt2;
+ gchar *txt1;
+ gchar *txt2;
+ gint retval;
+
+ if (!t1 && !t2)
+ return 0;
+ if (!t1)
+ return 1;
+ if (!t2)
+ return -1;
+
+ /*
+ * The custom position overrides any comparison we can make. To keep compatibility,
+ * for now, we only compare by position if both tasks have a custom position set.
+ */
+ if (gtd_task_get_position (t1) != -1 && gtd_task_get_position (t2) != -1)
+ {
+ retval = gtd_task_get_position (t1) - gtd_task_get_position (t2);
+
+ if (retval != 0)
+ return retval;
+ }
+
+ /* Compare by due date */
+ dt1 = gtd_task_get_due_date (t1);
+ dt2 = gtd_task_get_due_date (t2);
+
+ if (!dt1 && !dt2)
+ retval = 0;
+ else if (!dt1)
+ retval = 1;
+ else if (!dt2)
+ retval = -1;
+ else
+ retval = g_date_time_compare (dt1, dt2);
+
+ if (dt1)
+ g_date_time_unref (dt1);
+ if (dt2)
+ g_date_time_unref (dt2);
+
+ if (retval != 0)
+ return retval;
+
+ /* Compare by creation date */
+ dt1 = gtd_task_get_creation_date (t1);
+ dt2 = gtd_task_get_creation_date (t2);
+
+ if (!dt1 && !dt2)
+ retval = 0;
+ else if (!dt1)
+ retval = 1;
+ else if (!dt2)
+ retval = -1;
+ else
+ retval = g_date_time_compare (dt1, dt2);
+
+ g_clear_pointer (&dt1, g_date_time_unref);
+ g_clear_pointer (&dt2, g_date_time_unref);
+
+ if (retval != 0)
+ return retval;
+
+ /* If they're equal up to now, compare by title */
+ txt1 = txt2 = NULL;
+
+ txt1 = g_utf8_casefold (gtd_task_get_title (t1), -1);
+ txt2 = g_utf8_casefold (gtd_task_get_title (t2), -1);
+
+ retval = g_strcmp0 (txt1, txt2);
+
+ g_free (txt1);
+ g_free (txt2);
+
+ return retval;
+}
+
+/**
+ * gtd_task_get_provider:
+ * @self: a #GtdTaskList
+ *
+ * Utility function to retrieve the data provider that backs this
+ * task. Notice that this is exactly the same as writing:
+ *
+ * |[<!-- language="C" -->
+ * GtdTaskList *list;
+ * GtdProvider *provider;
+ *
+ * list = gtd_task_get_list (task);
+ * provider = gtd_task_list_get_provider (list);
+ * ]|
+ *
+ * Returns: (transfer none)(nullable): the #GtdProvider of this task's list.
+ */
+GtdProvider*
+gtd_task_get_provider (GtdTask *self)
+{
+ GtdTaskPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK (self), NULL);
+
+ priv = gtd_task_get_instance_private (self);
+
+ if (priv->list)
+ return gtd_task_list_get_provider (priv->list);
+
+ return NULL;
+}
diff --git a/src/core/gtd-task.h b/src/core/gtd-task.h
new file mode 100644
index 0000000..0c2b5aa
--- /dev/null
+++ b/src/core/gtd-task.h
@@ -0,0 +1,122 @@
+/* gtd-task.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_H
+#define GTD_TASK_H
+
+#include "gtd-object.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_TASK (gtd_task_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GtdTask, gtd_task, GTD, TASK, GtdObject)
+
+struct _GtdTaskClass
+{
+ GtdObjectClass parent;
+
+ gboolean (*get_complete) (GtdTask *self);
+ void (*set_complete) (GtdTask *self,
+ gboolean complete);
+
+ GDateTime* (*get_creation_date) (GtdTask *self);
+ void (*set_creation_date) (GtdTask *self,
+ GDateTime *dt);
+
+ GDateTime* (*get_completion_date) (GtdTask *self);
+ void (*set_completion_date) (GtdTask *self,
+ GDateTime *dt);
+
+ const gchar* (*get_description) (GtdTask *self);
+ void (*set_description) (GtdTask *self,
+ const gchar *description);
+
+ GDateTime* (*get_due_date) (GtdTask *self);
+ void (*set_due_date) (GtdTask *self,
+ GDateTime *dt);
+
+ gboolean (*get_important) (GtdTask *self);
+ void (*set_important) (GtdTask *self,
+ gboolean important);
+
+ gint64 (*get_position) (GtdTask *self);
+ void (*set_position) (GtdTask *self,
+ gint64 position);
+
+ const gchar* (*get_title) (GtdTask *self);
+ void (*set_title) (GtdTask *self,
+ const gchar *title);
+
+ gpointer padding[8];
+};
+
+GtdTask* gtd_task_new (void);
+
+gboolean gtd_task_get_complete (GtdTask *self);
+
+void gtd_task_set_complete (GtdTask *self,
+ gboolean complete);
+
+GDateTime* gtd_task_get_creation_date (GtdTask *self);
+
+void gtd_task_set_creation_date (GtdTask *self,
+ GDateTime *dt);
+
+GDateTime* gtd_task_get_completion_date (GtdTask *self);
+
+const gchar* gtd_task_get_description (GtdTask *self);
+
+void gtd_task_set_description (GtdTask *self,
+ const gchar *description);
+
+GDateTime* gtd_task_get_due_date (GtdTask *self);
+
+void gtd_task_set_due_date (GtdTask *self,
+ GDateTime *dt);
+
+gboolean gtd_task_get_important (GtdTask *self);
+
+void gtd_task_set_important (GtdTask *self,
+ gboolean important);
+
+GtdTaskList* gtd_task_get_list (GtdTask *self);
+
+void gtd_task_set_list (GtdTask *self,
+ GtdTaskList *list);
+
+gint64 gtd_task_get_position (GtdTask *self);
+
+void gtd_task_set_position (GtdTask *self,
+ gint64 position);
+
+const gchar* gtd_task_get_title (GtdTask *self);
+
+void gtd_task_set_title (GtdTask *self,
+ const gchar *title);
+
+gint gtd_task_compare (GtdTask *t1,
+ GtdTask *t2);
+
+GtdProvider* gtd_task_get_provider (GtdTask *self);
+
+G_END_DECLS
+
+#endif /* GTD_TASK_H */