Poll for task additions, removals, and changes
authorTravis Reitter <treitter@gmail.com>
Mon, 30 Nov 2009 01:47:27 +0000 (17:47 -0800)
committerTravis Reitter <treitter@gmail.com>
Fri, 4 Dec 2009 06:01:13 +0000 (22:01 -0800)
src/milk-auth.c
src/milk-auth.h
src/milk-task-model.c

index aa075fe..e8e78f1 100644 (file)
@@ -179,10 +179,11 @@ auth_response_cb_OUT:
 }
 
 GList *
-milk_auth_get_tasks (MilkAuth *auth)
+milk_auth_get_tasks (MilkAuth    *auth,
+                     const char  *last_sync,
+                     GError     **error)
 {
         MilkAuthPrivate *priv;
-        GError *error = NULL;
         GList *rtm_tasks;
 
         g_return_val_if_fail (auth, NULL);
@@ -194,13 +195,9 @@ milk_auth_get_tasks (MilkAuth *auth)
         priv = MILK_AUTH_PRIVATE (auth);
 
         /* FIXME: cache this */
-        rtm_tasks = rtm_glib_tasks_get_list (priv->rtm_glib, NULL, NULL, NULL,
-                        &error);
-        if (error != NULL) {
-                g_error ("%s", rtm_error_get_message (error));
-                return NULL;
-        }
-        
+        rtm_tasks = rtm_glib_tasks_get_list (priv->rtm_glib, NULL, NULL,
+                        (char*) last_sync, error);
+
         return rtm_tasks;
 }
 
index d3a2046..845e854 100644 (file)
@@ -70,8 +70,10 @@ GType milk_auth_get_type (void);
 
 
 MilkAuth*     milk_auth_get_default (void);
-void          milk_auth_log_in      (MilkAuth* auth);
-MilkAuthState milk_auth_get_state   (MilkAuth* auth);
-GList*        milk_auth_get_tasks   (MilkAuth* auth);
+void          milk_auth_log_in      (MilkAuth    *auth);
+MilkAuthState milk_auth_get_state   (MilkAuth    *auth);
+GList*        milk_auth_get_tasks   (MilkAuth    *auth,
+                                     const char  *last_sync,
+                                     GError     **error);
 
 #endif /* _MILK_AUTH_H */
index 9e20765..605e4a5 100644 (file)
@@ -42,16 +42,30 @@ G_DEFINE_TYPE_EXTENDED (MilkTaskModel,
 /* less expensive than G_TYPE_INSTANCE_GET_PRIVATE */
 #define MILK_TASK_MODEL_PRIVATE(o) ((MILK_TASK_MODEL ((o)))->priv)
 
+/* FIXME: make this configurable at runtime, pref. as a gconf value */
+/* time between syncing with the server, in ms */
+#define MODEL_UPDATE_PERIOD 60000
+
 struct _MilkTaskModelPrivate
 {
+        GHashTable *tasks;
         GtkListStore *store;
         MilkAuth *auth;
+        guint update_id;
+        char *last_sync;
 };
 
 enum {
         PROP_AUTH = 1,
 };
 
+static gboolean
+task_is_finished (RtmTask *task)
+{
+        return (rtm_task_get_completed_date (task) ||
+                rtm_task_get_deleted_date (task));
+}
+
 static GtkTreeModelFlags
 milk_task_model_get_flags (GtkTreeModel *model)
 {
@@ -230,46 +244,129 @@ milk_task_model_iter_parent (GtkTreeModel *model,
                         GTK_TREE_MODEL (priv->store), iter, child);
 }
 
-static void
-populate_model (MilkTaskModel *model)
+static gboolean
+model_store_find_task (MilkTaskModel *model,
+                       RtmTask       *task_in,
+                       GtkTreeIter   *iter_in)
+{
+        MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
+        gboolean valid;
+        GtkTreeIter iter;
+        gboolean found = FALSE;
+
+        valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store),
+                        &iter);
+        while (valid && !found) {
+                MilkTask *task;
+                char *task_id;
+
+                gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+                                        MILK_TASK_MODEL_COLUMN_TASK, &task,
+                                        -1);
+                g_object_get (task, "id", &task_id, NULL);
+
+                if (!g_strcmp0 (rtm_task_get_id (task_in), task_id)) {
+                        *iter_in = iter;
+                        found = TRUE;
+                }
+
+                g_object_unref (task);
+
+                valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store),
+                                &iter);
+        }
+
+        return found;
+}
+
+static gboolean
+update_model (MilkTaskModel *model)
 {
         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
         GList *rtm_tasks;
         GList *l;
-        GtkTreeIter iter;
+        GTimeVal current_time;
+        char *new_sync;
+        GError *error = NULL;
 
-        gtk_list_store_clear (priv->store);
+        if (milk_auth_get_state (priv->auth) != MILK_AUTH_STATE_CONNECTED) {
+                return TRUE;
+        }
+
+        g_get_current_time (&current_time);
+        new_sync = g_time_val_to_iso8601 (&current_time);
+        rtm_tasks = milk_auth_get_tasks (priv->auth, priv->last_sync, &error);
 
-        /* FIXME: poll for new tasks periodically -- there's rtm-glib API to
-         * optimize just fetching the latest ones */
-        rtm_tasks = milk_auth_get_tasks (priv->auth);
+        if (error) {
+                g_error (G_STRLOC ": failed to retrieve latest tasks: %s",
+                                error->message);
+                g_clear_error (&error);
+        } else {
+                g_free (priv->last_sync);
+                priv->last_sync = new_sync;
+        }
 
         /* Populate model */
         for (l = rtm_tasks; l; l = g_list_delete_link (l, l)) {
+                GtkTreeIter iter;
                 RtmTask *rtm_task;
                 MilkTask *task;
+                const char *id;
+                gboolean task_in_store;
 
                 rtm_task = RTM_TASK (l->data);
 
-                /* XXX: if possible, avoid fetching these in the first place */
-                /* Skip tasks deleted or completed. */
-                if (rtm_task_get_completed_date (rtm_task) ||
-                    rtm_task_get_deleted_date (rtm_task)) {
-                        continue;
+                id = rtm_task_get_id (rtm_task);
+                g_hash_table_insert (priv->tasks, g_strdup (id),
+                                g_object_ref (rtm_task));
+
+                task_in_store = model_store_find_task (model, rtm_task, &iter);
+
+                /* Task is deleted or completed */
+                if (task_is_finished (rtm_task)) {
+                        if (task_in_store) {
+                                gtk_list_store_remove (priv->store, &iter);
+                        }
+                /* Task has been changed */
+                } else if (task_in_store) {
+                        GtkTreePath *path;
+
+                        gtk_tree_model_get (
+                                        GTK_TREE_MODEL (priv->store), &iter,
+                                        MILK_TASK_MODEL_COLUMN_TASK, &task,
+                                        -1);
+
+                        milk_task_set_title (task,
+                                        rtm_task_get_name (rtm_task));
+
+                        path = gtk_tree_model_get_path (
+                                        GTK_TREE_MODEL (priv->store), &iter);
+                        gtk_tree_model_row_changed (
+                                        GTK_TREE_MODEL (priv->store),
+                                        path, &iter);
+                        gtk_tree_path_free (path);
+
+                        g_object_unref (task);
+
+                /* Task is new */
+                } else {
+                        /* FIXME: remove the MilkTask class; just use RtmTask
+                         * directly */
+                        task = milk_task_new (id, rtm_task_get_name (rtm_task),
+                                        /* FIXME: switch priority from int to
+                                         * string */
+                                        g_ascii_strtod (rtm_task_get_priority
+                                                        (rtm_task), NULL));
+
+                        gtk_list_store_append (priv->store, &iter);
+                        gtk_list_store_set (
+                                        priv->store, &iter,
+                                        MILK_TASK_MODEL_COLUMN_TASK, task,
+                                        -1);
                 }
-
-                task = milk_task_new (rtm_task_get_id (rtm_task),
-                                rtm_task_get_name (rtm_task),
-                                /* FIXME: switch priority from int to string */
-                                g_ascii_strtod (rtm_task_get_priority
-                                                (rtm_task), NULL));
-
-                gtk_list_store_append (priv->store, &iter);
-                gtk_list_store_set (
-                                priv->store, &iter,
-                                MILK_TASK_MODEL_COLUMN_TASK, task,
-                                -1);
         }
+
+        return TRUE;
 }
 
 static void
@@ -278,7 +375,7 @@ auth_notify_cb (MilkAuth      *auth,
                 MilkTaskModel *model)
 {
         if (milk_auth_get_state (auth) == MILK_AUTH_STATE_CONNECTED) {
-                populate_model (model);
+                update_model (model);
         }
 }
 
@@ -337,12 +434,17 @@ milk_task_model_set_auth (MilkTaskModel *model,
         }
         priv->auth = g_object_ref (auth);
 
-        if (milk_auth_get_state (priv->auth) == MILK_AUTH_STATE_CONNECTED) {
-                populate_model (model);
-        } else {
-                g_signal_connect (priv->auth, "notify::state",
-                                G_CALLBACK (auth_notify_cb), model);
+        if (priv->update_id) {
+                g_source_remove (priv->update_id);
         }
+        priv->update_id = g_timeout_add (MODEL_UPDATE_PERIOD,
+                        (GSourceFunc) update_model, model);
+
+        gtk_list_store_clear (priv->store);
+
+        g_signal_connect (priv->auth, "notify::state",
+                          G_CALLBACK (auth_notify_cb), model);
+        auth_notify_cb (priv->auth, NULL, model);
 }
 
 static void
@@ -408,10 +510,30 @@ milk_task_model_dispose (GObject *object)
                 priv->store = NULL;
         }
 
+        if (priv->update_id) {
+                g_source_remove (priv->update_id);
+                priv->update_id = 0;
+        }
+
+        if (priv->tasks) {
+                g_hash_table_destroy (priv->tasks);
+                priv->tasks = NULL;
+        }
+
         G_OBJECT_CLASS (milk_task_model_parent_class)->dispose (object);
 }
 
 static void
+milk_task_model_finalize (GObject *object)
+{
+        MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
+
+        g_free (priv->last_sync);
+
+        G_OBJECT_CLASS (milk_task_model_parent_class)->finalize (object);
+}
+
+static void
 milk_task_model_class_init (MilkTaskModelClass *klass)
 {
         GObjectClass *object_class = G_OBJECT_CLASS (klass);
@@ -421,6 +543,7 @@ milk_task_model_class_init (MilkTaskModelClass *klass)
         object_class->get_property = milk_task_model_get_property;
         object_class->set_property = milk_task_model_set_property;
         object_class->dispose = milk_task_model_dispose;
+        object_class->finalize = milk_task_model_finalize;
 
         g_object_class_install_property
                 (object_class,
@@ -442,6 +565,11 @@ milk_task_model_init (MilkTaskModel *self)
         self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (
                         self, MILK_TYPE_TASK_MODEL, MilkTaskModelPrivate);
 
+        priv->last_sync = NULL;
+
+        priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
+                        g_free, g_object_unref);
+
         priv->store = gtk_list_store_new (
                         MILK_TASK_MODEL_N_COLUMNS, MILK_TYPE_TASK);