[gnome-todo] todoist: Reimplement provider



commit 1158f5d04ee9122a5be1824ce0b54d57d3f45425
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Feb 2 12:38:27 2018 -0200

    todoist: Reimplement provider
    
    This is a more robust implementation, where tasks are properly
    stored (hey, I came up with a super cool stacking algorithm to
    get that right!) and properly escapes the strings before sending
    them through POST requests.
    
    This also reorganizes the code, adds a much valued debugging and
    tracing spews, and takes error management to a new level with
    proper error reporting, GQuarks and stuff.
    
    And to put the cherry at the top of this reimplementation cake,
    this make the code a little bit less redundant in many places.
    
    A few things to do still:
    
     * Figure out how to implement comments and attachments to tasks.
       This is a premium feature of Todoist.
     * Stop using Online Accounts. It is already condemneded to doom.
     * Properly report errors to users. This is coming up later.

 plugins/todoist/gtd-plugin-todoist.c   |   81 ++-
 plugins/todoist/gtd-provider-todoist.c | 1026 +++++++++++++++++---------------
 2 files changed, 588 insertions(+), 519 deletions(-)
---
diff --git a/plugins/todoist/gtd-plugin-todoist.c b/plugins/todoist/gtd-plugin-todoist.c
index 0f7422c..3c25598 100644
--- a/plugins/todoist/gtd-plugin-todoist.c
+++ b/plugins/todoist/gtd-plugin-todoist.c
@@ -1,6 +1,7 @@
 /* gtd-plugin-todoist.c
  *
  * Copyright (C) 2017 Rohit Kaushik <kaushikrohit325 gmail com>
+ *               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
@@ -36,6 +37,8 @@ struct _GtdPluginTodoist
 
   GtkWidget          *preferences;
 
+  GoaClient          *goa_client;
+
   /* Providers */
   GList              *providers;
 };
@@ -99,9 +102,9 @@ gtd_plugin_todoist_get_providers (GtdActivatable *activatable)
 }
 
 static void
-gtd_plugin_todoist_account_added (GtdTodoistPreferencesPanel *panel,
-                                  GoaObject                  *account_object,
-                                  GtdPluginTodoist           *self)
+on_account_added_cb (GoaClient        *client,
+                     GoaObject        *account_object,
+                     GtdPluginTodoist *self)
 {
   GtdProviderTodoist *provider;
   GoaAccount *goa_account;
@@ -110,6 +113,8 @@ gtd_plugin_todoist_account_added (GtdTodoistPreferencesPanel *panel,
   goa_account = goa_object_get_account (account_object);
   provider_name = goa_account_get_provider_name (goa_account);
 
+  g_object_unref (goa_account);
+
   if (g_strcmp0 (provider_name, "Todoist") != 0)
     return;
 
@@ -121,9 +126,9 @@ gtd_plugin_todoist_account_added (GtdTodoistPreferencesPanel *panel,
 }
 
 static void
-gtd_plugin_todoist_account_removed (GtdTodoistPreferencesPanel *panel,
-                                    GoaObject                  *account_object,
-                                    GtdPluginTodoist           *self)
+on_account_removed_cb (GoaClient        *client,
+                       GoaObject        *account_object,
+                       GtdPluginTodoist *self)
 {
   GoaAccount *goa_account;
   const gchar *provider_name;
@@ -133,6 +138,8 @@ gtd_plugin_todoist_account_removed (GtdTodoistPreferencesPanel *panel,
   provider_name = goa_account_get_provider_name (goa_account);
   l = NULL;
 
+  g_object_unref (goa_account);
+
   if (g_strcmp0 (provider_name, "Todoist") != 0)
     return;
 
@@ -147,38 +154,29 @@ gtd_plugin_todoist_account_removed (GtdTodoistPreferencesPanel *panel,
           self->providers = g_list_remove (self->providers, l->data);
 
           g_signal_emit_by_name (self, "provider-removed", l->data);
-
           break;
         }
     }
 }
 
 static void
-gtd_plugin_todoist_account_changed (GtdTodoistPreferencesPanel *panel,
-                                    GoaObject                  *account_object,
-                                    GtdPluginTodoist           *self)
+on_goa_client_ready_cb (GObject          *source,
+                        GAsyncResult     *result,
+                        GtdPluginTodoist *self)
 {
-  GoaAccount *goa_account;
-  const gchar *provider_name;
-
-  goa_account = goa_object_get_account (account_object);
-  provider_name = goa_account_get_provider_name (goa_account);
-
-  if (g_strcmp0 (provider_name, "Todoist") != 0)
-    return;
-}
-
-static void
-goa_client_ready (GObject           *source,
-                  GAsyncResult      *res,
-                  GtdPluginTodoist  *self)
-{
-  GoaClient *client;
+  g_autoptr (GError) error = NULL;
   GList *accounts;
   GList *l;
 
-  client = goa_client_new_finish (res, NULL);
-  accounts = goa_client_get_accounts (client);
+  self->goa_client = goa_client_new_finish (result, &error);
+
+  if (error)
+    {
+      g_warning ("Error retriving GNOME Online Accounts client: %s", error->message);
+      return;
+    }
+
+  accounts = goa_client_get_accounts (self->goa_client);
 
   for (l = accounts; l != NULL; l = l->next)
     {
@@ -189,23 +187,18 @@ goa_client_ready (GObject           *source,
       provider_type = goa_account_get_provider_type (account);
 
       if (g_strcmp0 (provider_type, "todoist") == 0)
-        {
-          gtd_plugin_todoist_account_added (GTD_TODOIST_PREFERENCES_PANEL (self->preferences),
-                                            l->data,
-                                            self);
-        }
+        on_account_added_cb (self->goa_client, l->data, self);
 
       g_object_unref (account);
     }
 
   /* Connect signals */
-  g_signal_connect (client, "account-added", G_CALLBACK (gtd_plugin_todoist_account_added), self);
-  g_signal_connect (client, "account-removed", G_CALLBACK (gtd_plugin_todoist_account_removed), self);
-  g_signal_connect (client, "account-changed", G_CALLBACK (gtd_plugin_todoist_account_changed), self);
+  g_signal_connect (self->goa_client, "account-added", G_CALLBACK (on_account_added_cb), self);
+  g_signal_connect (self->goa_client, "account-removed", G_CALLBACK (on_account_removed_cb), self);
 
-  gtd_todoist_preferences_panel_set_client (GTD_TODOIST_PREFERENCES_PANEL (self->preferences), client);
+  gtd_todoist_preferences_panel_set_client (GTD_TODOIST_PREFERENCES_PANEL (self->preferences), 
self->goa_client);
 
-  g_list_free_full (accounts,  g_object_unref);
+  g_list_free_full (accounts, g_object_unref);
 }
 
 static void
@@ -220,7 +213,7 @@ gtd_activatable_iface_init (GtdActivatableInterface *iface)
 }
 
 /*
- * Init
+ * GObject overrides
  */
 
 static void
@@ -231,6 +224,8 @@ gtd_plugin_todoist_finalize (GObject *object)
   g_list_free_full (self->providers, g_object_unref);
   self->providers = NULL;
 
+  g_clear_object (&self->goa_client);
+
   G_OBJECT_CLASS (gtd_plugin_todoist_parent_class)->finalize (object);
 }
 
@@ -257,12 +252,10 @@ gtd_plugin_todoist_class_init (GtdPluginTodoistClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  object_class->finalize     = gtd_plugin_todoist_finalize;
+  object_class->finalize = gtd_plugin_todoist_finalize;
   object_class->get_property = gtd_plugin_todoist_get_property;
 
-  g_object_class_override_property (object_class,
-                                    PROP_PREFERENCES_PANEL,
-                                    "preferences-panel");
+  g_object_class_override_property (object_class, PROP_PREFERENCES_PANEL, "preferences-panel");
 }
 
 static void
@@ -270,7 +263,7 @@ gtd_plugin_todoist_init (GtdPluginTodoist *self)
 {
   self->preferences = GTK_WIDGET (gtd_todoist_preferences_panel_new ());
 
-  goa_client_new (NULL, (GAsyncReadyCallback) goa_client_ready, self);
+  goa_client_new (NULL, (GAsyncReadyCallback) on_goa_client_ready_cb, self);
 }
 
 /* Empty class_finalize method */
diff --git a/plugins/todoist/gtd-provider-todoist.c b/plugins/todoist/gtd-provider-todoist.c
index b23d1b7..650ba2f 100644
--- a/plugins/todoist/gtd-provider-todoist.c
+++ b/plugins/todoist/gtd-provider-todoist.c
@@ -1,6 +1,7 @@
 /* gtd-provider-todoist.c
  *
  * Copyright (C) 2017 Rohit Kaushik <kaushikrohit325 gmail com>
+ *               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
@@ -20,8 +21,10 @@
 
 #define _XOPEN_SOURCE
 
+#include "gtd-debug.h"
 #include "gtd-provider-todoist.h"
 #include "gtd-plugin-todoist.h"
+#include "gtd-utils.h"
 
 #include <rest/oauth2-proxy.h>
 #include <json-glib/json-glib.h>
@@ -30,14 +33,25 @@
 #include <time.h>
 #include <glib/gi18n.h>
 
-#define TODOIST_URL "https://todoist.com/API/v7/sync";
+#define GTD_PROVIDER_TODOIST_ERROR (gtd_provider_todoist_error_quark ())
+#define TODOIST_URL                "https://todoist.com/API/v7/sync";
 
 typedef enum
 {
-  LIST_CREATE,
-  TASK_CREATE
+  REQUEST_LIST_CREATE,
+  REQUEST_TASK_CREATE,
+  REQUEST_OTHER,
 } RequestType;
 
+typedef enum
+{
+  GTD_PROVIDER_TODOIST_ERROR_BAD_REQUEST,
+  GTD_PROVIDER_TODOIST_ERROR_BAD_STATUS_CODE,
+  GTD_PROVIDER_TODOIST_ERROR_INVALID_RESPONSE,
+  GTD_PROVIDER_TODOIST_ERROR_LIMIT_REACHED,
+  GTD_PROVIDER_TODOIST_ERROR_NOT_ALLOWED,
+} GtdProviderTodoistError;
+
 typedef struct
 {
   GtdProviderTodoist *self;
@@ -65,17 +79,30 @@ struct _GtdProviderTodoist
   /* Queue to hold Request Data */
   GQueue             *queue;
 
-  /* timeout ids */
-  guint               dispatch_id;
+  guint               long_wait_timeout_id;
 };
 
+
+GQuark               gtd_provider_todoist_error_quark            (void);
+
+static void          on_operation_completed_cb                   (RestProxyCall    *call,
+                                                                  const GError     *error,
+                                                                  GObject          *weak_object,
+                                                                  PostCallbackData *data);
+
+static void          on_synchronize_completed_cb                 (RestProxyCall      *call,
+                                                                  const GError       *error,
+                                                                  GObject            *weak_object,
+                                                                  GtdProviderTodoist *self);
+
 static void          gtd_provider_iface_init                     (GtdProviderInterface *iface);
 
-static gboolean      dispatch_requests                           (GtdProviderTodoist *self);
+
+G_DEFINE_QUARK (GtdProviderTodoistError, gtd_provider_todoist_error)
 
 G_DEFINE_TYPE_WITH_CODE (GtdProviderTodoist, gtd_provider_todoist, GTD_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GTD_TYPE_PROVIDER,
-                                                gtd_provider_iface_init))
+                         G_IMPLEMENT_INTERFACE (GTD_TYPE_PROVIDER, gtd_provider_iface_init))
+
 
 enum {
   PROP_0,
@@ -115,46 +142,24 @@ static const gchar *colormap[] =
   "#777777"
 };
 
+
 /*
- * GtdProviderInterface implementation
+ * Auxiliary methods
  */
-static const gchar*
-gtd_provider_todoist_get_id (GtdProvider *provider)
-{
-  return "todoist";
-}
 
-static const gchar*
-gtd_provider_todoist_get_name (GtdProvider *provider)
-{
-  return _("Todoist");
-}
+#define CHECK_ACCESS_TOKEN(self)  \
+G_STMT_START {                    \
+  if (!self->access_token) {      \
+      emit_access_token_error (); \
+      return;                     \
+    }                             \
+} G_STMT_END
 
-static const gchar*
-gtd_provider_todoist_get_description (GtdProvider *provider)
+static gchar*
+escape_string_for_post (const gchar *string)
 {
-  GtdProviderTodoist *self;
-
-  self = GTD_PROVIDER_TODOIST (provider);
-
-  return self->description;
-}
-
-
-static gboolean
-gtd_provider_todoist_get_enabled (GtdProvider *provider)
-{
-  return TRUE;
-}
-
-static GIcon*
-gtd_provider_todoist_get_icon (GtdProvider *provider)
-{
-  GtdProviderTodoist *self;
-
-  self = GTD_PROVIDER_TODOIST (provider);
-
-  return self->icon;
+  g_autofree gchar *replaced_slash = gtd_str_replace (string, "\\", "\\\\");
+  return gtd_str_replace (replaced_slash, "\"", "\\\"");
 }
 
 static gint
@@ -186,21 +191,6 @@ convert_color_code (gint index)
   return gdk_rgba_copy (&rgba);
 }
 
-static void
-emit_generic_error (const GError *error)
-{
-  g_warning ("%s: %s: %s",
-             G_STRFUNC,
-             "Error making a sync call to Todoist",
-             error->message);
-
-  gtd_manager_emit_error_message (gtd_manager_get_default (),
-                                  _("Error loading Todoist tasks"),
-                                  error->message,
-                                  NULL,
-                                  NULL);
-}
-
 static void
 emit_access_token_error (void)
 {
@@ -246,24 +236,8 @@ get_color_code_index (GdkRGBA *rgba)
 }
 
 static void
-store_access_token (GtdProviderTodoist *self)
-{
-  GoaOAuth2Based *o_auth2;
-  GError *error;
-
-  error = NULL;
-  o_auth2 = goa_object_get_oauth2_based (self->account_object);
-
-  if (!goa_oauth2_based_call_get_access_token_sync (o_auth2, &self->access_token, NULL, NULL, &error))
-    {
-      emit_generic_error (error);
-      g_clear_error (&error);
-    }
-}
-
-static void
-parse_array_to_list (GtdProviderTodoist *self,
-                     JsonArray          *projects)
+parse_task_lists (GtdProviderTodoist *self,
+                  JsonArray          *projects)
 {
   GList *lists;
   GList *l;
@@ -272,7 +246,7 @@ parse_array_to_list (GtdProviderTodoist *self,
 
   for (l = lists; l != NULL; l = l->next)
     {
-      g_autofree gchar *uid;
+      g_autofree gchar *uid = NULL;
       JsonObject *object;
       GtdTaskList *list;
       const gchar *name;
@@ -280,6 +254,11 @@ parse_array_to_list (GtdProviderTodoist *self,
       guint color_index;
 
       object = json_node_get_object (l->data);
+
+      /* Ignore deleted tasklists */
+      if (json_object_get_boolean_member (object, "is_deleted"))
+        continue;
+
       list = gtd_task_list_new (GTD_PROVIDER (self));
 
       name = json_object_get_string_member (object, "name");
@@ -291,8 +270,8 @@ parse_array_to_list (GtdProviderTodoist *self,
       gtd_task_list_set_color (list, convert_color_code (color_index));
       gtd_task_list_set_is_removable (list, TRUE);
       gtd_object_set_uid (GTD_OBJECT (list), uid);
+
       g_hash_table_insert (self->lists, GUINT_TO_POINTER (id), list);
-      g_signal_emit_by_name (self, "list-added", list);
     }
 }
 
@@ -315,69 +294,167 @@ parse_due_date (const gchar *due_date)
   return dt;
 }
 
+static gint
+compare_tasks_by_position (gconstpointer a,
+                           gconstpointer b)
+{
+  GTask *task_a, *task_b;
+  gint64 position_a, position_b;
+
+  task_a = *((GTask **) a);
+  task_b = *((GTask **) b);
+  position_a = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task_a), "position"));
+  position_b = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task_b), "position"));
+
+  return position_a - position_b;
+}
+
 static void
-parse_array_to_task (GtdProviderTodoist *self,
-                     JsonArray          *items)
+parse_tasks (GtdProviderTodoist *self,
+             JsonArray          *items)
 {
+  g_autoptr (GHashTable) list_to_tasks = NULL;
+  GHashTableIter iter;
+  GPtrArray *tasks;
+  GQueue tasks_stack;
+  gint64 project;
   GList *lists;
   GList *l;
 
   lists = json_array_get_elements (items);
 
+  /* First, create all the tasks and store them temporarily in a GPtrArray */
+  list_to_tasks = g_hash_table_new_full (g_direct_hash,
+                                         g_direct_equal,
+                                         NULL,
+                                         (GDestroyNotify) g_ptr_array_unref);
+
   for (l = lists; l != NULL; l = l->next)
     {
       g_autofree gchar *uid = NULL;
       JsonObject *object;
-      GtdTaskList *list;
       GtdTask *task;
       const gchar *title;
       const gchar *due_date;
-      guint32 id;
-      guint32 project_id;
+      gint64 complete;
+      gint64 position;
+      gint64 indent;
+      gint64 id;
       gint priority;
-      guint is_complete;
 
       object = json_node_get_object (l->data);
 
+      /* Ignore deleted tasklists */
+      if (json_object_get_boolean_member (object, "is_deleted"))
+        continue;
+
       title = json_object_get_string_member (object, "content");
       priority = json_object_get_int_member (object, "priority");
       id = json_object_get_int_member (object, "id");
-      project_id = json_object_get_int_member (object, "project_id");
-      is_complete = json_object_get_int_member (object, "checked");
+      project = json_object_get_int_member (object, "project_id");
+      complete = json_object_get_int_member (object, "checked");
       due_date = json_object_get_string_member (object, "due_date_utc");
+      indent = json_object_get_int_member (object, "indent");
+      position = json_object_get_int_member (object, "item_order");
 
-      list = g_hash_table_lookup (self->lists, GUINT_TO_POINTER (project_id));
-      uid = g_strdup_printf ("%u", id);
+      uid = g_strdup_printf ("%ld", id);
 
-      /* Setup the task */
+      /* Setup the new task */
       task = gtd_task_new ();
       gtd_object_set_uid (GTD_OBJECT (task), uid);
       gtd_task_set_title (task, title);
-      gtd_task_set_list (task, list);
       gtd_task_set_priority (task, priority);
-      gtd_task_set_complete (task, is_complete);
+      gtd_task_set_complete (task, complete != 0);
 
-      /* Setup the parent task */
-      if (!json_object_get_null_member (object, "parent_id"))
+      GTD_TRACE_MSG ("  '%s' → %d", title, gtd_task_get_complete (task));
+
+      /* Due date */
+      if (due_date)
+        gtd_task_set_due_date (task, parse_due_date (due_date));
+
+      g_object_set_data (G_OBJECT (task), "indent", GINT_TO_POINTER (indent));
+      g_object_set_data (G_OBJECT (task), "position", GINT_TO_POINTER (position));
+
+      g_hash_table_insert (self->tasks, (gpointer) id, task);
+
+      /* Add to the temporary GPtrArray that will be consumed below */
+      if (!g_hash_table_contains (list_to_tasks, GINT_TO_POINTER (project)))
+        g_hash_table_insert (list_to_tasks, GINT_TO_POINTER (project), g_ptr_array_new ());
+
+      tasks = g_hash_table_lookup (list_to_tasks, GINT_TO_POINTER (project));
+      g_ptr_array_add (tasks, task);
+    }
+
+  /*
+   * Now that all the tasks are created and properly stored in a GPtrArray,
+   * we have to go through each GPtrArray, sort it, and figure out the parent
+   * & children relationship between the tasks.
+   */
+  g_queue_init (&tasks_stack);
+  g_hash_table_iter_init (&iter, list_to_tasks);
+
+  while (g_hash_table_iter_next (&iter, (gpointer) &project, (gpointer) &tasks))
+    {
+      GtdTaskList *list;
+      gint64 previous_indent;
+      gint64 i;
+
+      list = g_hash_table_lookup (self->lists, GUINT_TO_POINTER (project));
+      previous_indent = 0;
+
+      GTD_TRACE_MSG ("Setting up tasklist '%s'", gtd_task_list_get_name (list));
+
+      g_ptr_array_sort (tasks, compare_tasks_by_position);
+
+      for (i = 0; tasks && i < tasks->len; i++)
         {
           GtdTask *parent_task;
-          guint32 parent_id;
+          GtdTask *task;
+          gint64 indent;
+          gint j;
 
-          parent_id = json_object_get_int_member (object, "parent_id");
-          parent_task = g_hash_table_lookup (self->tasks, GUINT_TO_POINTER (parent_id));
+          task = g_ptr_array_index (tasks, i);
+          parent_task = NULL;
+          indent = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "indent"));
 
-          gtd_task_add_subtask (parent_task, task);
-        }
+          GTD_TRACE_MSG ("  Adding task '%s' (%s)",
+                         gtd_task_get_title (task),
+                         gtd_object_get_uid (GTD_OBJECT (task)));
 
-      /* Due date */
-      if (due_date)
-        gtd_task_set_due_date (task, parse_due_date (due_date));
+          /* If the indent changed, remove from the difference in level from stack */
+          for (j = 0; j <= previous_indent - indent; j++)
+            g_queue_pop_head (&tasks_stack);
+
+          parent_task = g_queue_peek_head (&tasks_stack);
+
+          if (parent_task)
+            gtd_task_add_subtask (parent_task, task);
 
-      g_hash_table_insert (self->tasks, GUINT_TO_POINTER (id), task);
-      gtd_task_list_save_task (list, task);
+          gtd_task_set_list (task, list);
+          gtd_task_list_save_task (list, task);
+
+          g_queue_push_head (&tasks_stack, task);
+
+          previous_indent = indent;
+        }
+
+      /* Clear the queue since we're changing projects */
+      g_queue_clear (&tasks_stack);
     }
 }
 
+static void
+notify_task_lists (GtdProviderTodoist *self)
+{
+  g_autoptr (GList) lists = NULL;
+  GList *l;
+
+  lists = g_hash_table_get_values (self->lists);
+
+  for (l = lists; l; l = l->next)
+    g_signal_emit_by_name (self, "list-added", l->data);
+}
+
 static void
 load_tasks (GtdProviderTodoist *self,
             JsonObject         *object)
@@ -388,15 +465,17 @@ load_tasks (GtdProviderTodoist *self,
   projects = json_object_get_array_member (object, "projects");
   items = json_object_get_array_member (object, "items");
 
-  parse_array_to_list (self, projects);
-  parse_array_to_task (self, items);
+  parse_task_lists (self, projects);
+  parse_tasks (self, items);
+  notify_task_lists (self);
 }
 
-static gboolean
-check_post_response_for_errors (RestProxyCall    *call,
-                                JsonParser       *parser,
-                                PostCallbackData *data,
-                                const GError     *error)
+static void
+check_post_response_for_errors (RestProxyCall     *call,
+                                JsonParser        *parser,
+                                PostCallbackData  *data,
+                                const GError      *post_error,
+                                GError           **error)
 {
   JsonObject *object;
   GError *parse_error;
@@ -406,25 +485,30 @@ check_post_response_for_errors (RestProxyCall    *call,
 
   status_code = rest_proxy_call_get_status_code (call);
 
-  if (error)
+  if (post_error)
     {
-      emit_generic_error (error);
-      return TRUE;
+      g_propagate_error (error, g_error_copy (post_error));
+      return;
     }
 
-  if (status_code != 200)
+  switch (status_code)
     {
-      gchar *error_message;
+    case 400:
+      g_set_error (error,
+                   GTD_PROVIDER_TODOIST_ERROR,
+                   GTD_PROVIDER_TODOIST_ERROR_BAD_REQUEST,
+                   "Bad request");
+      return;
 
-      error_message = g_strdup_printf (_("Bad status code (%d) received. Please check your connection."), 
status_code);
+    case 429:
+      g_set_error (error,
+                   GTD_PROVIDER_TODOIST_ERROR,
+                   GTD_PROVIDER_TODOIST_ERROR_LIMIT_REACHED,
+                   "Too many requests");
+      return;
 
-      gtd_manager_emit_error_message (gtd_manager_get_default (),
-                                      _("Error loading Todoist tasks"),
-                                      error_message,
-                                      NULL,
-                                      NULL);
-      g_free (error_message);
-      return TRUE;
+    default:
+      break;
     }
 
   payload = rest_proxy_call_get_payload (call);
@@ -432,11 +516,24 @@ check_post_response_for_errors (RestProxyCall    *call,
 
   if (!json_parser_load_from_data (parser, payload, payload_length, &parse_error))
     {
-      emit_generic_error (parse_error);
-      g_clear_error (&parse_error);
-      return TRUE;
+      g_propagate_error (error, g_steal_pointer (&parse_error));
+      return;
     }
 
+  /* Print the response if tracing is enabled */
+#ifdef GTD_ENABLE_TRACE
+  {
+    g_autoptr (JsonGenerator) generator;
+
+    generator = g_object_new (JSON_TYPE_GENERATOR,
+                              "root", json_parser_get_root (parser),
+                              "pretty", TRUE,
+                              NULL);
+
+    g_debug ("Response: \n%s", json_generator_to_data (generator, NULL));
+  }
+#endif
+
   object = json_node_get_object (json_parser_get_root (parser));
 
   if (json_object_has_member (object, "sync_status"))
@@ -447,14 +544,35 @@ check_post_response_for_errors (RestProxyCall    *call,
       response = json_object_get_object_member (object, "sync_status");
       command_status = json_object_get_member (response, data->command_uid);
 
+      /* This was a Todoist-specific error, parse the message */
       if (JSON_NODE_TYPE (command_status) == JSON_NODE_OBJECT)
-        return TRUE;
-
-      if (g_strcmp0 (json_node_get_string (command_status), "ok") != 0)
-        return TRUE;
+        {
+          JsonObject *error_object;
+          const gchar *message;
+          gint error_code;
+
+          error_object = json_node_get_object (command_status);
+          error_code = json_object_get_int_member (error_object, "error_code");
+          message = json_object_get_string_member (error_object, "error");
+
+          switch (error_code)
+            {
+            case 34:
+              g_set_error (error,
+                           GTD_PROVIDER_TODOIST_ERROR,
+                           GTD_PROVIDER_TODOIST_ERROR_NOT_ALLOWED,
+                           "GNOME To Do doesn't have the necessary permissions to perform this action: %s",
+                           message);
+              break;
+
+            default:
+              g_set_error (error,
+                           GTD_PROVIDER_TODOIST_ERROR,
+                           GTD_PROVIDER_TODOIST_ERROR_INVALID_RESPONSE,
+                           "Invalid response received");
+            }
+        }
     }
-
-  return FALSE;
 }
 
 static void
@@ -463,7 +581,7 @@ update_transient_id (GtdProviderTodoist *self,
                      GtdObject          *object,
                      RequestType         req)
 {
-  GList *temp_ids;
+  g_autoptr (GList) temp_ids;
   GList *l;
 
   temp_ids = json_object_get_members (id_map);
@@ -479,16 +597,14 @@ update_transient_id (GtdProviderTodoist *self,
       uid = g_strdup_printf ("%u", id);
 
       /* Update temp id to permanent id if temp-id in response matches object temo-id */
-      if (!g_strcmp0 (temp_id, l->data))
+      if (!g_str_equal (temp_id, l->data))
         gtd_object_set_uid (object, uid);
 
-      if (req == LIST_CREATE)
+      if (req == REQUEST_LIST_CREATE)
         g_hash_table_insert (self->lists, GUINT_TO_POINTER (id), GTD_TASK_LIST (object));
-      else if (req == TASK_CREATE)
+      else if (req == REQUEST_TASK_CREATE)
         g_hash_table_insert (self->tasks, GUINT_TO_POINTER (id), GTD_TASK (object));
     }
-
-  g_list_free (temp_ids);
 }
 
 static void
@@ -496,21 +612,23 @@ post (JsonObject                 *params,
       RestProxyCallAsyncCallback  callback,
       gpointer                    user_data)
 {
-  RestProxy *proxy;
+  g_autoptr (GError) error = NULL;
   RestProxyCall *call;
+  RestProxy *proxy;
   GList *param;
   GList *l;
-  GError *error;
 
-  error = NULL;
   proxy = rest_proxy_new (TODOIST_URL, FALSE);
   call = rest_proxy_new_call (proxy);
   param = json_object_get_members (params);
 
+  g_debug ("Sending POST request");
+
+  /* Hold the application when starting a POST op, release on the callback */
+  g_application_hold (g_application_get_default ());
+
   rest_proxy_call_set_method (call, "POST");
-  rest_proxy_call_add_header (call,
-                              "content-type",
-                              "application/x-www-form-urlencoded");
+  rest_proxy_call_add_header (call, "content-type", "application/x-www-form-urlencoded");
 
   for (l = param; l != NULL; l = l->next)
     {
@@ -523,92 +641,180 @@ post (JsonObject                 *params,
       rest_proxy_call_add_param (call, l->data, value);
     }
 
-  if (!rest_proxy_call_async (call,
-                              callback,
-                              NULL,
-                              user_data,
-                              &error))
-    {
-      emit_generic_error (error);
-      g_clear_error (&error);
-      goto out;
-    }
+  rest_proxy_call_async (call, callback, NULL, user_data, &error);
 
-out:
   g_object_unref (proxy);
   g_object_unref (call);
   g_list_free (param);
 }
 
 static void
-synchronize_call_cb (RestProxyCall      *call,
-                     const GError       *error,
-                     GObject            *weak_object,
-                     GtdProviderTodoist *self)
+synchronize (GtdProviderTodoist *self)
 {
-  g_autoptr (JsonParser) parser = NULL;
-  JsonObject *object;
+  g_autoptr (JsonObject) params = NULL;
 
-  parser = json_parser_new ();
+  CHECK_ACCESS_TOKEN (self);
+
+  params = json_object_new ();
+
+  json_object_set_string_member (params, "token", self->access_token);
+  json_object_set_string_member (params, "sync_token", self->sync_token);
+  json_object_set_string_member (params, "resource_types", "[\"all\"]");
+
+  post (params, (RestProxyCallAsyncCallback) on_synchronize_completed_cb, self);
+}
 
-  if (check_post_response_for_errors (call, parser, NULL, error))
+static void
+process_request_queue (GtdProviderTodoist *self)
+{
+  PostCallbackData *data;
+
+  if (g_queue_is_empty (self->queue) || self->long_wait_timeout_id > 0)
     return;
 
-  object = json_node_get_object (json_parser_get_root (parser));
+  GTD_TRACE_MSG ("Processing request queue");
 
-  if (json_object_has_member (object, "sync_token"))
-    {
-      g_clear_pointer (&self->sync_token, g_free);
-      self->sync_token = g_strdup (json_object_get_string_member (object, "sync_token"));
-    }
+  data = g_queue_pop_tail (self->queue);
 
-  load_tasks (self, object);
+  post (data->params, (RestProxyCallAsyncCallback) on_operation_completed_cb, data);
 }
 
 static void
-push_post_request (GtdProviderTodoist *self,
-                   PostCallbackData   *data)
+schedule_post_request (GtdProviderTodoist *self,
+                       gpointer            object,
+                       RequestType         type,
+                       const gchar        *command_uid,
+                       const gchar        *command)
 {
+  PostCallbackData *data;
+
+  /* Set params for post request */
+  data = g_new0 (PostCallbackData, 1);
+  data->self = self;
+  data->request_type = type;
+  data->object = GTD_OBJECT (object);
+  data->command_uid = g_strdup (command_uid);
+  data->params = json_object_new ();
+
+  json_object_set_string_member (data->params, "commands", command);
+  json_object_set_string_member (data->params, "token", self->access_token);
+
+  GTD_TRACE_MSG ("Sending POST request:\n%s", command);
+
   g_queue_push_head (self->queue, data);
 
-  /*
-   * When the queue holds post request, the application should
-   * not quit till the time queue gets empty. This hold is released
-   * in post_generic_cb where requests are dispatched.
-   */
-  g_application_hold (g_application_get_default ());
+  process_request_queue (self);
+}
+
+static void
+store_access_token (GtdProviderTodoist *self)
+{
+  g_autoptr (GError) error = NULL;
+  GoaOAuth2Based *oauth2;
+
+  oauth2 = goa_object_get_oauth2_based (self->account_object);
+
+  goa_oauth2_based_call_get_access_token_sync (oauth2, &self->access_token, NULL, NULL, &error);
+
+  if (error)
+    g_warning ("Error retrieving OAuth2 access token: %s", error->message);
 
-  if (!self->dispatch_id)
-    self->dispatch_id = g_timeout_add_seconds (5, (GSourceFunc) dispatch_requests, self);
+  g_clear_object (&oauth2);
+}
+
+
+/*
+ * Callbacks
+ */
+
+static gboolean
+on_long_wait_timeout_cb (gpointer data)
+{
+  GtdProviderTodoist *self = (GtdProviderTodoist *) data;
+
+  self->long_wait_timeout_id = 0;
+
+  process_request_queue (self);
+
+  return G_SOURCE_REMOVE;
 }
 
 static void
-post_generic_cb (RestProxyCall       *call,
-                 const GError        *error,
-                 GObject             *weak_object,
-                 PostCallbackData    *data)
+on_synchronize_completed_cb (RestProxyCall      *call,
+                             const GError       *post_error,
+                             GObject            *weak_object,
+                             GtdProviderTodoist *self)
 {
   g_autoptr (JsonParser) parser = NULL;
+  g_autoptr (JsonObject) object = NULL;
+  g_autoptr (GError) error = NULL;
+
+  parser = json_parser_new ();
+
+       g_application_release (g_application_get_default ());
+
+  check_post_response_for_errors (call, parser, NULL, post_error, &error);
+
+  if (error)
+    {
+      g_warning ("Error synchronizing with Todoist: %s", error->message);
+      return;
+    }
+
+  object = json_node_dup_object (json_parser_get_root (parser));
+
+  if (json_object_has_member (object, "sync_token"))
+    {
+      g_clear_pointer (&self->sync_token, g_free);
+      self->sync_token = g_strdup (json_object_get_string_member (object, "sync_token"));
+    }
+
+  load_tasks (self, object);
+}
+
+static void
+on_operation_completed_cb (RestProxyCall    *call,
+                           const GError     *post_error,
+                           GObject          *weak_object,
+                           PostCallbackData *data)
+{
+  g_autoptr (JsonParser) parser = NULL;
+  g_autoptr (GError) error = NULL;
   GtdProviderTodoist *self;
   JsonObject *object;
 
   self = data->self;
   parser = json_parser_new ();
 
+  g_debug ("Received response for POST request");
+
+  /* Release the hold since queue is empty */
+       g_application_release (g_application_get_default ());
+
+  check_post_response_for_errors (call, parser, data, post_error, &error);
+
   /*
    * Remove the current dispatch timeout and add a new timeout with
    * interval as 60 seconds (50 request per minute, since we assume
    * the error was caused because of exceeding request limit).
    */
-  if (check_post_response_for_errors (call, parser, data, error))
+  if (error)
     {
-      if (self->dispatch_id)
-        g_source_remove (self->dispatch_id);
+      if (g_error_matches (error,
+                           GTD_PROVIDER_TODOIST_ERROR,
+                           GTD_PROVIDER_TODOIST_ERROR_LIMIT_REACHED))
+        {
+          GTD_TRACE_MSG ("Rescheduling dispatch timeout to 60 seconds");
 
-      self->dispatch_id = g_timeout_add_seconds (60, (GSourceFunc) dispatch_requests, data->self);
+          g_queue_push_tail (self->queue, data);
 
-      /* Push the RequestData back into queue since the request was not successful */
-      push_post_request (self, data);
+          if (self->long_wait_timeout_id == 0)
+            self->long_wait_timeout_id = g_timeout_add_seconds (60, on_long_wait_timeout_cb, self);
+        }
+      else
+        {
+          g_warning ("Error executing request: %s", error->message);
+        }
 
       return;
     }
@@ -635,50 +841,51 @@ post_generic_cb (RestProxyCall       *call,
   g_clear_pointer (&data, g_free);
 
   /* Dispatch next queued request */
-  if (!g_queue_is_empty (self->queue))
-    {
-      data = g_queue_pop_tail (self->queue);
-      post (data->params, (RestProxyCallAsyncCallback) post_generic_cb, data);
-    }
-
-  /* Release the hold since queue is empty */
-       g_application_release (g_application_get_default ());
+  process_request_queue (self);
 }
 
-static gboolean
-dispatch_requests (GtdProviderTodoist *self)
-{
-  PostCallbackData *data;
 
-  data = g_queue_pop_tail (self->queue);
-  post (data->params, (RestProxyCallAsyncCallback) post_generic_cb, data);
+/*
+ * GtdProviderInterface implementation
+ */
 
-  /* Remove further timeout since rest of requests are handled in callback */
-  self->dispatch_id = 0;
+static const gchar*
+gtd_provider_todoist_get_id (GtdProvider *provider)
+{
+  return "todoist";
+}
 
-  return G_SOURCE_REMOVE;
+static const gchar*
+gtd_provider_todoist_get_name (GtdProvider *provider)
+{
+  return _("Todoist");
 }
 
-static void
-synchronize_call (GtdProviderTodoist *self)
+static const gchar*
+gtd_provider_todoist_get_description (GtdProvider *provider)
 {
-  JsonObject *params;
+  GtdProviderTodoist *self;
 
-  if (!self->access_token)
-    {
-      emit_access_token_error ();
-      return;
-    }
+  self = GTD_PROVIDER_TODOIST (provider);
 
-  params = json_object_new ();
+  return self->description;
+}
 
-  json_object_set_string_member (params, "token", self->access_token);
-  json_object_set_string_member (params, "sync_token", self->sync_token);
-  json_object_set_string_member (params, "resource_types", "[\"all\"]");
 
-  post (params, (RestProxyCallAsyncCallback) synchronize_call_cb, self);
+static gboolean
+gtd_provider_todoist_get_enabled (GtdProvider *provider)
+{
+  return TRUE;
+}
+
+static GIcon*
+gtd_provider_todoist_get_icon (GtdProvider *provider)
+{
+  GtdProviderTodoist *self;
+
+  self = GTD_PROVIDER_TODOIST (provider);
 
-  json_object_unref (params);
+  return self->icon;
 }
 
 static void
@@ -688,67 +895,49 @@ gtd_provider_todoist_create_task (GtdProvider   *provider,
                                   GError       **error)
 {
   GtdProviderTodoist *self;
-  PostCallbackData *data;
-  g_autofree gchar *command;
-  g_autofree gchar *command_uuid;
-  g_autofree gchar *temp_id;
-  g_autofree gchar *due_dt;
-  g_autofree gchar *escaped_title;
+  g_autoptr (GDateTime) due_date = NULL;
+  g_autofree gchar *command = NULL;
+  g_autofree gchar *command_uid = NULL;
+  g_autofree gchar *temp_id = NULL;
+  g_autofree gchar *due_dt = NULL;
+  g_autofree gchar *escaped_title = NULL;
   GtdTaskList *project;
-  JsonObject *params;
   GtdTask *parent;
-  GDateTime *due_date;
   const gchar *project_id;
 
   self = GTD_PROVIDER_TODOIST (provider);
-  due_dt = command = command_uuid = temp_id = NULL;
-  escaped_title = NULL;
-  data = g_new0 (PostCallbackData, 1);
-  data->self = self;
-  data->object = GTD_OBJECT (task);
-  data->request_type = TASK_CREATE;
 
-  if (!self->access_token)
-    {
-      emit_access_token_error ();
-      return;
-    }
+  CHECK_ACCESS_TOKEN (self);
 
-  params = json_object_new ();
   parent = gtd_task_get_parent (task);
   due_date = gtd_task_get_due_date (task);
   project = gtd_task_get_list (task);
   project_id = gtd_object_get_uid (GTD_OBJECT (project));
-  escaped_title = g_strescape (gtd_task_get_title (task), NULL);
+  escaped_title = escape_string_for_post (gtd_task_get_title (task));
+  due_dt = due_date ? g_date_time_format (due_date, "\"%FT%R\"") : g_strdup ("null");
 
-  if (due_date)
-    {
-      g_autofree gchar *date_format;
-
-      date_format = g_date_time_format (due_date, "%FT%R");
-      due_dt = g_strdup_printf ("\"%s\"", date_format);
-    }
-  else
-    {
-      due_dt = g_strdup ("null");
-    }
-
-  command_uuid = g_uuid_string_random ();
+  command_uid = g_uuid_string_random ();
   temp_id = g_uuid_string_random ();
-  data->command_uid = g_strdup (command_uuid);
 
   /* Set the temporary id */
-  gtd_object_set_uid (GTD_OBJECT (task), g_strdup_printf ("\"%s\"", temp_id));
-
-  command = g_strdup_printf ("[{\"type\": \"item_add\", \"temp_id\": \"%s\", "
-                             "\"uuid\": \"%s\", "
-                             "\"args\": {\"content\": \"%s\", "
-                             "\"priority\": %d, \"parent_id\": %s, "
-                             "\"project_id\": %s, "
-                             "\"indent\": %d, \"checked\": %d, "
-                             "\"due_date_utc\": %s}}]",
+  gtd_object_set_uid (GTD_OBJECT (task), temp_id);
+
+  command = g_strdup_printf ("[{                             \n"
+                             "    \"type\": \"item_add\",    \n"
+                             "    \"temp_id\": \"%s\",       \n"
+                             "    \"uuid\": \"%s\",          \n"
+                             "    \"args\": {                \n"
+                             "        \"content\": \"%s\",   \n"
+                             "        \"priority\": %d,      \n"
+                             "        \"parent_id\": %s,     \n"
+                             "        \"project_id\": %s,    \n"
+                             "        \"indent\": %d,        \n"
+                             "        \"checked\": %d,       \n"
+                             "        \"due_date_utc\": %s   \n"
+                             "    }                          \n"
+                             "}]",
                              temp_id,
-                             command_uuid,
+                             command_uid,
                              escaped_title,
                              gtd_task_get_priority (task),
                              parent ? gtd_object_get_uid (GTD_OBJECT (parent)) : "null",
@@ -757,16 +946,7 @@ gtd_provider_todoist_create_task (GtdProvider   *provider,
                              gtd_task_get_complete (task),
                              due_dt);
 
-  json_object_set_string_member (params, "token", self->access_token);
-  json_object_set_string_member (params, "commands", command);
-
-  /* Set params for post request */
-  data->params = json_object_ref (params);
-
-  /* Push post request data to queue */
-  push_post_request (self, data);
-
-  g_clear_pointer (&due_date, g_date_time_unref);
+  schedule_post_request (self, task, REQUEST_TASK_CREATE, command_uid, command);
 }
 
 static void
@@ -775,56 +955,40 @@ gtd_provider_todoist_update_task (GtdProvider   *provider,
                                   GCancellable  *cancellable,
                                   GError       **error)
 {
+  g_autoptr (GDateTime) due_date = NULL;
   GtdProviderTodoist *self;
-  PostCallbackData *data;
   g_autofree gchar *command;
-  g_autofree gchar *command_uuid;
+  g_autofree gchar *command_uid;
   g_autofree gchar *due_dt;
   g_autofree gchar *escaped_title;
-  JsonObject *params;
   GtdTask *parent;
-  GDateTime *due_date;
 
   self = GTD_PROVIDER_TODOIST (provider);
-  due_dt = command = command_uuid = NULL;
+  due_dt = command = command_uid = NULL;
   escaped_title = NULL;
 
-  data = g_new0 (PostCallbackData, 1);
-  data->self = self;
-  data->object = GTD_OBJECT (task);
+  CHECK_ACCESS_TOKEN (self);
 
-  if (!self->access_token)
-    {
-      emit_access_token_error ();
-      return;
-    }
-
-  params = json_object_new ();
   parent = gtd_task_get_parent (task);
   due_date = gtd_task_get_due_date (task);
-  escaped_title = g_strescape (gtd_task_get_title (task), NULL);
-
-  if (due_date)
-    {
-      g_autofree gchar *date_format;
-
-      date_format = g_date_time_format (due_date, "%FT%R");
-      due_dt = g_strdup_printf ("\"%s\"", date_format);
-    }
-  else
-    {
-      due_dt = g_strdup ("null");
-    }
-
-  command_uuid = g_uuid_string_random ();
-  data->command_uid = g_strdup (command_uuid);
-
-  command = g_strdup_printf ("[{\"type\": \"item_update\", \"uuid\": \"%s\", "
-                             "\"args\": {\"id\": %s, \"content\": \"%s\", "
-                             "\"priority\": %d, \"parent_id\": %s, "
-                             "\"indent\": %d, \"checked\": %d, "
-                             "\"due_date_utc\": %s}}]",
-                             command_uuid,
+  escaped_title = escape_string_for_post (gtd_task_get_title (task));
+  due_dt = due_date ? g_date_time_format (due_date, "\"%FT%R\"") : g_strdup ("null");
+
+  command_uid = g_uuid_string_random ();
+  command = g_strdup_printf ("[{                               \n"
+                             "    \"type\": \"item_update\",   \n"
+                             "    \"uuid\": \"%s\",            \n"
+                             "    \"args\": {                  \n"
+                             "        \"id\": %s,              \n"
+                             "        \"content\": \"%s\",     \n"
+                             "        \"priority\": %d,        \n"
+                             "        \"parent_id\": %s,       \n"
+                             "        \"indent\": %d,          \n"
+                             "        \"checked\": %d,         \n"
+                             "        \"due_date_utc\": %s     \n"
+                             "    }                            \n"
+                             "}]",
+                             command_uid,
                              gtd_object_get_uid (GTD_OBJECT (task)),
                              escaped_title,
                              gtd_task_get_priority (task),
@@ -833,16 +997,7 @@ gtd_provider_todoist_update_task (GtdProvider   *provider,
                              gtd_task_get_complete (task),
                              due_dt);
 
-  json_object_set_string_member (params, "token", self->access_token);
-  json_object_set_string_member (params, "commands", command);
-
-  /* Set params for post request */
-  data->params = json_object_ref (params);
-
-  /* Push post request data to queue */
-  push_post_request (self, data);
-
-  g_clear_pointer (&due_date, g_date_time_unref);
+  schedule_post_request (self, task, REQUEST_OTHER, command_uid, command);
 }
 
 static void
@@ -851,43 +1006,24 @@ gtd_provider_todoist_remove_task (GtdProvider   *provider,
                                   GCancellable  *cancellable,
                                   GError       **error)
 {
+  g_autofree gchar *command_uid = NULL;
+  g_autofree gchar *command = NULL;
   GtdProviderTodoist *self;
-  PostCallbackData *data;
-  JsonObject *params;
-  g_autofree gchar *command;
-  g_autofree gchar *command_uuid;
 
   self = GTD_PROVIDER_TODOIST (provider);
-  command = command_uuid = NULL;
-  data = g_new0 (PostCallbackData, 1);
-  data->self = self;
-  data->object = GTD_OBJECT (task);
-
-  if (!self->access_token)
-    {
-      emit_access_token_error ();
-      return;
-    }
 
-  params = json_object_new ();
+  CHECK_ACCESS_TOKEN (self);
 
-  command_uuid = g_uuid_string_random ();
-  data->command_uid = g_strdup (command_uuid);
-
-  command = g_strdup_printf ("[{\"type\": \"item_delete\", "
-                             "\"uuid\": \"%s\", "
-                             "\"args\": {\"ids\": [%s]}}]",
-                             command_uuid,
+  command_uid = g_uuid_string_random ();
+  command = g_strdup_printf ("[{                                \n"
+                             "    \"type\": \"item_delete\",    \n"
+                             "    \"uuid\": \"%s\",             \n"
+                             "    \"args\": { \"ids\": [%s] }   \n"
+                             "}]",
+                             command_uid,
                              gtd_object_get_uid (GTD_OBJECT (task)));
 
-  json_object_set_string_member (params, "token", self->access_token);
-  json_object_set_string_member (params, "commands", command);
-
-  /* Set params for post request */
-  data->params = json_object_ref (params);
-
-  /* Push post request data to queue */
-  push_post_request (self, data);
+  schedule_post_request (self, task, REQUEST_OTHER, command_uid, command);
 }
 
 static void
@@ -896,61 +1032,43 @@ gtd_provider_todoist_create_task_list (GtdProvider   *provider,
                                        GCancellable  *cancellable,
                                        GError       **error)
 {
+  g_autoptr (GdkRGBA) list_color = NULL;
+  g_autofree gchar *command = NULL;
+  g_autofree gchar *command_uid = NULL;
+  g_autofree gchar *escaped_name = NULL;
+  g_autofree gchar *temp_id = NULL;
   GtdProviderTodoist *self;
-  PostCallbackData *data;
-  g_autofree gchar *command;
-  g_autofree gchar *command_uuid;
-  g_autofree gchar *escaped_name;
-  g_autofree gchar *temp_id;
-  JsonObject *params;
-  GdkRGBA *list_color;
 
   self = GTD_PROVIDER_TODOIST (provider);
-  command = command_uuid = temp_id = NULL;
-  escaped_name = NULL;
-  data = g_new0 (PostCallbackData, 1);
-  data->self = self;
-  data->object = GTD_OBJECT (list);
-  data->request_type = LIST_CREATE;
 
-  if (!self->access_token)
-    {
-      emit_access_token_error ();
-      return;
-    }
+  CHECK_ACCESS_TOKEN (self);
 
-  params = json_object_new ();
   list_color = gtd_task_list_get_color (list);
 
-  escaped_name = g_strescape (gtd_task_list_get_name (list), NULL);
-  command_uuid = g_uuid_string_random ();
+  escaped_name = escape_string_for_post (gtd_task_list_get_name (list));
+  command_uid = g_uuid_string_random ();
   temp_id = g_uuid_string_random ();
-  data->command_uid = g_strdup (command_uuid);
 
   /* Set the temporary id */
-  gtd_object_set_uid (GTD_OBJECT (list), g_strdup_printf ("\"%s\"", temp_id));
-
-  command = g_strdup_printf ("[{\"type\": \"project_add\", "
-                             "\"temp_id\": \"%s\", "
-                             "\"uuid\": \"%s\", "
-                             "\"args\": {\"name\": \"%s\", \"color\": %d}}]",
+  gtd_object_set_uid (GTD_OBJECT (list), temp_id);
+
+  command = g_strdup_printf ("[{                                \n"
+                             "    \"type\": \"project_add\",    \n"
+                             "    \"temp_id\": \"%s\",          \n"
+                             "    \"uuid\": \"%s\",             \n"
+                             "    \"args\": {                   \n"
+                             "        \"name\": \"%s\",         \n"
+                             "        \"color\": %d             \n"
+                             "    }                             \n"
+                             "}]",
                              temp_id,
-                             command_uuid,
+                             command_uid,
                              escaped_name,
                              get_color_code_index (list_color));
 
-  json_object_set_string_member (params, "token", self->access_token);
-  json_object_set_string_member (params, "commands", command);
-
-  /* Set params for post request */
-  data->params = json_object_ref (params);
-
-  /* Push post request data to queue */
-  push_post_request (self, data);
+  schedule_post_request (self, list, REQUEST_LIST_CREATE, command_uid, command);
 
   g_signal_emit_by_name (provider, "list-added", list);
-
-  gdk_rgba_free (list_color);
 }
 
 static void
@@ -960,48 +1078,32 @@ gtd_provider_todoist_update_task_list (GtdProvider   *provider,
                                        GError       **error)
 {
   GtdProviderTodoist *self;
-  PostCallbackData *data;
-  JsonObject *params;
-  GdkRGBA *list_color;
-  g_autofree gchar *command;
-  g_autofree gchar *command_uuid;
+  g_autoptr (GdkRGBA) list_color = NULL;
+  g_autofree gchar *command = NULL;
+  g_autofree gchar *command_uid = NULL;
 
   self = GTD_PROVIDER_TODOIST (provider);
-  command = command_uuid = NULL;
 
-  data = g_new0 (PostCallbackData, 1);
-  data->self = self;
-  data->object = GTD_OBJECT (list);
+  CHECK_ACCESS_TOKEN (self);
 
-  if (!self->access_token)
-    {
-      emit_access_token_error ();
-      return;
-    }
-
-  params = json_object_new ();
   list_color = gtd_task_list_get_color (list);
 
-  command_uuid = g_uuid_string_random ();
-  data->command_uid = g_strdup (command_uuid);
-
-  command = g_strdup_printf ("[{\"type\": \"project_update\", \"uuid\": \"%s\", "
-                             "\"args\": {\"id\": %s, \"name\": \"%s\", \"color\": %d}}]",
-                             command_uuid,
+  command_uid = g_uuid_string_random ();
+  command = g_strdup_printf ("[{                                   \n"
+                             "    \"type\": \"project_update\",    \n"
+                             "    \"uuid\": \"%s\",                \n"
+                             "    \"args\": {                      \n"
+                             "        \"id\": %s,                  \n"
+                             "        \"name\": \"%s\",            \n"
+                             "        \"color\": %d                \n"
+                             "    }                                \n"
+                             "}]",
+                             command_uid,
                              gtd_object_get_uid (GTD_OBJECT (list)),
                              gtd_task_list_get_name (list),
                              get_color_code_index (list_color));
 
-  json_object_set_string_member (params, "token", self->access_token);
-  json_object_set_string_member (params, "commands", command);
-
-  /* Set params for post request */
-  data->params = json_object_ref (params);
-
-  /* Push post request data to queue */
-  push_post_request (self, data);
-
-  gdk_rgba_free (list_color);
+  schedule_post_request (self, list, REQUEST_OTHER, command_uid, command);
 }
 
 static void
@@ -1011,41 +1113,23 @@ gtd_provider_todoist_remove_task_list (GtdProvider   *provider,
                                        GError       **error)
 {
   GtdProviderTodoist *self;
-  PostCallbackData *data;
-  JsonObject *params;
-  g_autofree gchar *command;
-  g_autofree gchar *command_uuid;
+  g_autofree gchar *command = NULL;
+  g_autofree gchar *command_uid = NULL;
 
   self = GTD_PROVIDER_TODOIST (provider);
-  command = command_uuid = NULL;
-  data = g_new0 (PostCallbackData, 1);
-  data->self = self;
-  data->object = GTD_OBJECT (list);
-
-  if (!self->access_token)
-    {
-      emit_access_token_error ();
-      return;
-    }
-
-  params = json_object_new ();
 
-  command_uuid = g_uuid_string_random ();
-  data->command_uid = g_strdup (command_uuid);
+  CHECK_ACCESS_TOKEN (self);
 
-  command = g_strdup_printf ("[{\"type\": \"project_delete\", \"uuid\": \"%s\", "
-                             "\"args\": {\"ids\": [%s]}}]",
-                             command_uuid,
+  command_uid = g_uuid_string_random ();
+  command = g_strdup_printf ("[{                                 \n"
+                             "    \"type\": \"project_delete\",  \n"
+                             "    \"uuid\": \"%s\",              \n"
+                             "    \"args\": { \"ids\": [ %s ]}     \n"
+                             "}]",
+                             command_uid,
                              gtd_object_get_uid (GTD_OBJECT (list)));
 
-  json_object_set_string_member (params, "token", self->access_token);
-  json_object_set_string_member (params, "commands", command);
-
-  /* Set params for post request */
-  data->params = json_object_ref (params);
-
-  /* Push post request data to queue */
-  push_post_request (self, data);
+  schedule_post_request (self, list, REQUEST_OTHER, command_uid, command);
 }
 
 static GList*
@@ -1103,15 +1187,6 @@ gtd_provider_iface_init (GtdProviderInterface *iface)
   iface->set_default_task_list = gtd_provider_todoist_set_default_task_list;
 }
 
-GtdProviderTodoist*
-gtd_provider_todoist_new (GoaObject *account_object)
-{
-
-  return g_object_new (GTD_TYPE_PROVIDER_TODOIST,
-                       "goa object",account_object,
-                       NULL);
-}
-
 static void
 gtd_provider_todoist_finalize (GObject *object)
 {
@@ -1178,15 +1253,12 @@ gtd_provider_todoist_set_property (GObject      *object,
     case PROP_GOA_OBJECT:
       self->account_object = GOA_OBJECT (g_value_dup_object (value));
 
-      /* Setup a nice user visible description */
       update_description (self);
-
-      /* Retrieve the store the session token from the account */
       store_access_token (self);
 
       /* Only synchronize if we have an access token */
       if (self->access_token)
-        synchronize_call (self);
+        synchronize (self);
 
       break;
 
@@ -1206,7 +1278,7 @@ gtd_provider_todoist_class_init (GtdProviderTodoistClass *klass)
 
   g_object_class_install_property (object_class,
                                    PROP_GOA_OBJECT,
-                                   g_param_spec_object ("goa object",
+                                   g_param_spec_object ("goa-object",
                                                         "Goa Object",
                                                         "GOA Object around a Todoist Goa Account",
                                                         GOA_TYPE_OBJECT,
@@ -1225,20 +1297,24 @@ gtd_provider_todoist_init (GtdProviderTodoist *self)
 {
   gtd_object_set_ready (GTD_OBJECT (self), TRUE);
 
+  self->sync_token = g_strdup ("*");
+  self->queue = g_queue_new ();
+  self->icon = G_ICON (g_themed_icon_new_with_default_fallbacks ("goa-account-todoist"));
+
   /* Project id → GtdTaskList */
   self->lists = g_hash_table_new (g_direct_hash, g_direct_equal);
 
   /* Task id → GtdTask */
   self->tasks = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
 
-  /* Session token from GOA */
-  self->sync_token = g_strdup ("*");
-
-  /* Queue for post requests */
-  self->queue = g_queue_new ();
+GtdProviderTodoist*
+gtd_provider_todoist_new (GoaObject *account_object)
+{
 
-  /* icon */
-  self->icon = G_ICON (g_themed_icon_new_with_default_fallbacks ("goa-account-todoist"));
+  return g_object_new (GTD_TYPE_PROVIDER_TODOIST,
+                       "goa-object", account_object,
+                       NULL);
 }
 
 GoaObject*


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