[gnome-todo] todoist: Implement queueing of post requests



commit c25a0f013551011def004b4ac8fbf7cffbc5080a
Author: Rohit Kaushik <kaushikrohit325 gmail com>
Date:   Tue Aug 29 16:48:26 2017 +0530

    todoist: Implement queueing of post requests
    
    This patch implements queuing of requests and disptaching it
    properly to avoid conflicts and time limits. It adds:
    
    1) dispatch_requests - dispatches the first request from queue.
       If there are more request they are dispatched in post callback.
    
    2) push_post_request - When user changes any todoist task/lists,
       this push the request to queue and also adds a timeout of 5
       seconds for dispatching the requests.
    
    A queue is added to provider class which holds post requests.
    With this patch, the problem of reaching a limit is fixed since
    on error the request is not removed from queue and hence is not
    lost.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=772278

 plugins/todoist/gtd-provider-todoist.c |  253 +++++++++++++++++++++++---------
 1 files changed, 185 insertions(+), 68 deletions(-)
---
diff --git a/plugins/todoist/gtd-provider-todoist.c b/plugins/todoist/gtd-provider-todoist.c
index 41c5c15..713db8c 100644
--- a/plugins/todoist/gtd-provider-todoist.c
+++ b/plugins/todoist/gtd-provider-todoist.c
@@ -30,6 +30,21 @@
 
 #define TODOIST_URL "https://todoist.com/API/v7/sync";
 
+typedef enum
+{
+  LIST_CREATE,
+  TASK_CREATE
+} RequestType;
+
+typedef struct
+{
+  GtdProviderTodoist *self;
+  GtdObject          *object;
+  JsonObject         *params;
+  gchar              *command_uid;
+  RequestType         request_type;
+} PostCallbackData;
+
 struct _GtdProviderTodoist
 {
   GtdObject           parent;
@@ -41,12 +56,21 @@ struct _GtdProviderTodoist
   gchar              *description;
   GIcon              *icon;
 
+  /* Map between id to list and task instance */
   GHashTable         *lists;
   GHashTable         *tasks;
+
+  /* Queue to hold Request Data */
+  GQueue             *queue;
+
+  /* timeout ids */
+  guint               dispatch_id;
 };
 
 static void          gtd_provider_iface_init                     (GtdProviderInterface *iface);
 
+static gboolean      dispatch_requests                           (GtdProviderTodoist *self);
+
 G_DEFINE_TYPE_WITH_CODE (GtdProviderTodoist, gtd_provider_todoist, GTD_TYPE_OBJECT,
                          G_IMPLEMENT_INTERFACE (GTD_TYPE_PROVIDER,
                                                 gtd_provider_iface_init))
@@ -89,20 +113,6 @@ static const gchar *colormap[] =
   "#777777"
 };
 
-typedef enum
-{
-  DEFAULT_TYPE,
-  LIST_CREATE,
-  TASK_CREATE
-} RequestType;
-
-typedef struct
-{
-  GtdProviderTodoist *self;
-  GtdObject *object;
-  RequestType request_type;
-} PostCallbackData;
-
 /*
  * GtdProviderInterface implementation
  */
@@ -145,12 +155,6 @@ gtd_provider_todoist_get_icon (GtdProvider *provider)
   return self->icon;
 }
 
-GoaObject*
-gtd_provider_todoist_get_goa_object (GtdProviderTodoist  *self)
-{
-  return self->account_object;
-}
-
 static gint
 optimized_eucledian_color_distance (GdkRGBA *color1,
                                     GdkRGBA *color2)
@@ -266,10 +270,10 @@ parse_array_to_list (GtdProviderTodoist *self,
 
   for (l = lists; l != NULL; l = l->next)
     {
+      g_autofree gchar *uid;
       JsonObject *object;
       GtdTaskList *list;
       const gchar *name;
-      gchar *uid;
       guint32 id;
       guint color_index;
 
@@ -279,7 +283,6 @@ parse_array_to_list (GtdProviderTodoist *self,
       name = json_object_get_string_member (object, "name");
       color_index = json_object_get_int_member (object, "color");
       id = json_object_get_int_member (object, "id");
-
       uid = g_strdup_printf ("%u", id);
 
       gtd_task_list_set_name (list, name);
@@ -288,8 +291,6 @@ parse_array_to_list (GtdProviderTodoist *self,
       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);
-
-      g_free (uid);
     }
 }
 
@@ -323,12 +324,12 @@ parse_array_to_task (GtdProviderTodoist *self,
 
   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;
-      gchar *uid;
       guint32 id;
       guint32 project_id;
       gint priority;
@@ -372,8 +373,6 @@ parse_array_to_task (GtdProviderTodoist *self,
 
       g_hash_table_insert (self->tasks, GUINT_TO_POINTER (id), task);
       gtd_task_list_save_task (list, task);
-
-      g_free (uid);
     }
 }
 
@@ -392,10 +391,12 @@ load_tasks (GtdProviderTodoist *self,
 }
 
 static gboolean
-check_post_response_for_errors (RestProxyCall *call,
-                                JsonParser    *parser,
-                                const GError  *error)
+check_post_response_for_errors (RestProxyCall    *call,
+                                JsonParser       *parser,
+                                PostCallbackData *data,
+                                const GError     *error)
 {
+  JsonObject *object;
   GError *parse_error;
   const gchar *payload;
   guint status_code;
@@ -434,6 +435,23 @@ check_post_response_for_errors (RestProxyCall *call,
       return TRUE;
     }
 
+  object = json_node_get_object (json_parser_get_root (parser));
+
+  if (json_object_has_member (object, "sync_status"))
+    {
+      JsonObject *response;
+      JsonNode *command_status;
+
+      response = json_object_get_object_member (object, "sync_status");
+      command_status = json_object_get_member (response, data->command_uid);
+
+      if (JSON_NODE_TYPE (command_status) == JSON_NODE_OBJECT)
+        return TRUE;
+
+      if (g_strcmp0 (json_node_get_string (command_status), "ok") != 0)
+        return TRUE;
+    }
+
   return FALSE;
 }
 
@@ -526,13 +544,13 @@ synchronize_call_cb (RestProxyCall      *call,
                      GObject            *weak_object,
                      GtdProviderTodoist *self)
 {
+  g_autoptr (JsonParser) parser = NULL;
   JsonObject *object;
-  JsonParser *parser;
 
   parser = json_parser_new ();
 
-  if (check_post_response_for_errors (call, parser, error))
-    goto out;
+  if (check_post_response_for_errors (call, parser, NULL, error))
+    return;
 
   object = json_node_get_object (json_parser_get_root (parser));
 
@@ -543,9 +561,16 @@ synchronize_call_cb (RestProxyCall      *call,
     }
 
   load_tasks (self, object);
+}
 
-out:
-  g_object_unref (parser);
+static void
+push_post_request (GtdProviderTodoist *self,
+                   PostCallbackData   *data)
+{
+  g_queue_push_head (self->queue, data);
+
+  if (!self->dispatch_id)
+    self->dispatch_id = g_timeout_add_seconds (5, (GSourceFunc) dispatch_requests, self);
 }
 
 static void
@@ -554,20 +579,36 @@ post_generic_cb (RestProxyCall       *call,
                  GObject             *weak_object,
                  PostCallbackData    *data)
 {
+  g_autoptr (JsonParser) parser = NULL;
+  GtdProviderTodoist *self;
   JsonObject *object;
-  JsonParser *parser;
 
+  self = data->self;
   parser = json_parser_new ();
 
-  if (check_post_response_for_errors (call, parser, error))
-    goto out;
+  /*
+   * 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 (self->dispatch_id)
+        g_source_remove (self->dispatch_id);
+
+      self->dispatch_id = g_timeout_add_seconds (60, (GSourceFunc) dispatch_requests, data->self);
 
-  object = json_node_get_object (json_parser_get_root (parser));
+      /* Push the RequestData back into queue since the request was not successful */
+      push_post_request (self, data);
+
+      return;
+    }
 
+  object = json_node_get_object (json_parser_get_root (parser));
   if (json_object_has_member (object, "sync_token"))
     {
-      g_clear_pointer (&data->self->sync_token, g_free);
-      data->self->sync_token = g_strdup (json_object_get_string_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"));
     }
 
   /* Update temp-id if response contains temp-id mapping */
@@ -577,12 +618,33 @@ post_generic_cb (RestProxyCall       *call,
 
       temp_id_map = json_object_get_object_member (object, "temp_id_mapping");
 
-      update_transient_id (data->self, temp_id_map, data->object, data->request_type);
+      update_transient_id (self, temp_id_map, data->object, data->request_type);
     }
 
-out:
-  g_object_unref (parser);
-  g_free (data);
+  g_clear_pointer (&data->params, json_object_unref);
+  g_clear_pointer (&data->command_uid, g_free);
+  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);
+    }
+}
+
+static gboolean
+dispatch_requests (GtdProviderTodoist *self)
+{
+  PostCallbackData *data;
+
+  data = g_queue_pop_tail (self->queue);
+  post (data->params, (RestProxyCallAsyncCallback) post_generic_cb, data);
+
+  /* Remove further timeout since rest of requests are handled in callback */
+  self->dispatch_id = 0;
+
+  return G_SOURCE_REMOVE;
 }
 
 static void
@@ -613,18 +675,20 @@ gtd_provider_todoist_create_task (GtdProvider *provider,
 {
   GtdProviderTodoist *self;
   PostCallbackData *data;
-  GtdTaskList *project;
-  JsonObject *params;
-  GtdTask *parent;
-  GDateTime *due_date;
   g_autofree gchar *command;
   g_autofree gchar *command_uuid;
   g_autofree gchar *temp_id;
   g_autofree gchar *due_dt;
+  g_autofree gchar *escaped_title;
+  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);
@@ -641,6 +705,7 @@ gtd_provider_todoist_create_task (GtdProvider *provider,
   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);
 
   if (due_date)
     {
@@ -656,9 +721,10 @@ gtd_provider_todoist_create_task (GtdProvider *provider,
 
   command_uuid = 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), temp_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\", "
@@ -669,7 +735,7 @@ gtd_provider_todoist_create_task (GtdProvider *provider,
                              "\"due_date_utc\": %s}}]",
                              temp_id,
                              command_uuid,
-                             gtd_task_get_title (task),
+                             escaped_title,
                              gtd_task_get_priority (task),
                              parent ? gtd_object_get_uid (GTD_OBJECT (parent)) : "null",
                              project_id,
@@ -680,7 +746,11 @@ gtd_provider_todoist_create_task (GtdProvider *provider,
   json_object_set_string_member (params, "token", self->access_token);
   json_object_set_string_member (params, "commands", command);
 
-  post (params, (RestProxyCallAsyncCallback) post_generic_cb, data);
+  /* 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);
 }
@@ -691,15 +761,17 @@ gtd_provider_todoist_update_task (GtdProvider *provider,
 {
   GtdProviderTodoist *self;
   PostCallbackData *data;
-  JsonObject *params;
-  GtdTask *parent;
-  GDateTime *due_date;
   g_autofree gchar *command;
   g_autofree gchar *command_uuid;
   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;
+  escaped_title = NULL;
 
   data = g_new0 (PostCallbackData, 1);
   data->self = self;
@@ -714,6 +786,7 @@ gtd_provider_todoist_update_task (GtdProvider *provider,
   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)
     {
@@ -728,6 +801,8 @@ gtd_provider_todoist_update_task (GtdProvider *provider,
     }
 
   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, "
@@ -735,7 +810,7 @@ gtd_provider_todoist_update_task (GtdProvider *provider,
                              "\"due_date_utc\": %s}}]",
                              command_uuid,
                              gtd_object_get_uid (GTD_OBJECT (task)),
-                             gtd_task_get_title (task),
+                             escaped_title,
                              gtd_task_get_priority (task),
                              parent ? gtd_object_get_uid (GTD_OBJECT (parent)) : "null",
                              gtd_task_get_depth (task) + 1,
@@ -745,7 +820,11 @@ gtd_provider_todoist_update_task (GtdProvider *provider,
   json_object_set_string_member (params, "token", self->access_token);
   json_object_set_string_member (params, "commands", command);
 
-  post (params, (RestProxyCallAsyncCallback) post_generic_cb, data);
+  /* 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);
 }
@@ -775,7 +854,10 @@ gtd_provider_todoist_remove_task (GtdProvider *provider,
   params = json_object_new ();
 
   command_uuid = g_uuid_string_random ();
-  command = g_strdup_printf ("[{\"type\": \"item_delete\", \"uuid\": \"%s\", "
+  data->command_uid = g_strdup (command_uuid);
+
+  command = g_strdup_printf ("[{\"type\": \"item_delete\", "
+                             "\"uuid\": \"%s\", "
                              "\"args\": {\"ids\": [%s]}}]",
                              command_uuid,
                              gtd_object_get_uid (GTD_OBJECT (task)));
@@ -783,7 +865,11 @@ gtd_provider_todoist_remove_task (GtdProvider *provider,
   json_object_set_string_member (params, "token", self->access_token);
   json_object_set_string_member (params, "commands", command);
 
-  post (params, (RestProxyCallAsyncCallback) post_generic_cb, data);
+  /* Set params for post request */
+  data->params = json_object_ref (params);
+
+  /* Push post request data to queue */
+  push_post_request (self, data);
 }
 
 static void
@@ -792,14 +878,16 @@ gtd_provider_todoist_create_task_list (GtdProvider *provider,
 {
   GtdProviderTodoist *self;
   PostCallbackData *data;
-  JsonObject *params;
-  GdkRGBA *list_color;
   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);
@@ -814,24 +902,31 @@ gtd_provider_todoist_create_task_list (GtdProvider *provider,
   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 ();
   temp_id = g_uuid_string_random ();
+  data->command_uid = g_strdup (command_uuid);
 
   /* Set the temporary id */
-  gtd_object_set_uid (GTD_OBJECT (list), temp_id);
+  gtd_object_set_uid (GTD_OBJECT (list), g_strdup_printf ("\"%s\"", temp_id));
 
-  command = g_strdup_printf ("[{\"type\": \"project_add\", \"temp_id\": \"%s\", "
+  command = g_strdup_printf ("[{\"type\": \"project_add\", "
+                             "\"temp_id\": \"%s\", "
                              "\"uuid\": \"%s\", "
                              "\"args\": {\"name\": \"%s\", \"color\": %d}}]",
                              temp_id,
                              command_uuid,
-                             gtd_task_list_get_name (list),
+                             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);
 
-  post (params, (RestProxyCallAsyncCallback) post_generic_cb, data);
+  /* Set params for post request */
+  data->params = json_object_ref (params);
+
+  /* Push post request data to queue */
+  push_post_request (self, data);
 
   g_signal_emit_by_name (provider, "list-added", list);
 
@@ -866,6 +961,8 @@ gtd_provider_todoist_update_task_list (GtdProvider *provider,
   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,
@@ -876,7 +973,11 @@ gtd_provider_todoist_update_task_list (GtdProvider *provider,
   json_object_set_string_member (params, "token", self->access_token);
   json_object_set_string_member (params, "commands", command);
 
-  post (params, (RestProxyCallAsyncCallback) post_generic_cb, data);
+  /* 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);
 }
@@ -906,6 +1007,8 @@ gtd_provider_todoist_remove_task_list (GtdProvider *provider,
   params = json_object_new ();
 
   command_uuid = g_uuid_string_random ();
+  data->command_uid = g_strdup (command_uuid);
+
   command = g_strdup_printf ("[{\"type\": \"project_delete\", \"uuid\": \"%s\", "
                              "\"args\": {\"ids\": [%s]}}]",
                              command_uuid,
@@ -914,7 +1017,11 @@ gtd_provider_todoist_remove_task_list (GtdProvider *provider,
   json_object_set_string_member (params, "token", self->access_token);
   json_object_set_string_member (params, "commands", command);
 
-  post (params, (RestProxyCallAsyncCallback) post_generic_cb, data);
+  /* Set params for post request */
+  data->params = json_object_ref (params);
+
+  /* Push post request data to queue */
+  push_post_request (self, data);
 }
 
 static GList*
@@ -991,6 +1098,7 @@ gtd_provider_todoist_finalize (GObject *object)
   g_clear_object (&self->icon);
   g_clear_pointer (&self->sync_token, g_free);
   g_clear_pointer (&self->description, g_free);
+  g_queue_free (self->queue);
 
   G_OBJECT_CLASS (gtd_provider_todoist_parent_class)->finalize (object);
 }
@@ -1102,6 +1210,15 @@ gtd_provider_todoist_init (GtdProviderTodoist *self)
   /* Session token from GOA */
   self->sync_token = g_strdup ("*");
 
+  /* Queue for post requests */
+  self->queue = g_queue_new ();
+
   /* icon */
-  self->icon = G_ICON (g_themed_icon_new_with_default_fallbacks ("computer-symbolic"));
+  self->icon = G_ICON (g_themed_icon_new_with_default_fallbacks ("goa-account-todoist"));
+}
+
+GoaObject*
+gtd_provider_todoist_get_goa_object (GtdProviderTodoist  *self)
+{
+  return self->account_object;
 }


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