Add a basic task cache
[milk] / src / milk-task-model.c
index a1d6594..cb24974 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "milk-task-model.h"
 #include "milk-auth.h"
+#include "milk-cache.h"
 
 static void
 milk_task_model_tree_model_init (GtkTreeModelIface *iface);
@@ -41,30 +42,18 @@ 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;
+        MilkCache *cache;
 };
 
 enum {
-        PROP_AUTH = 1,
+        PROP_0,
+        PROP_CACHE,
 };
 
-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)
 {
@@ -243,10 +232,13 @@ milk_task_model_iter_parent (GtkTreeModel *model,
                         GTK_TREE_MODEL (priv->store), iter, child);
 }
 
+typedef gchar* (*RtmTaskAttrFunc) (RtmTask*);
+
 static gboolean
-model_store_find_task (MilkTaskModel *model,
-                       RtmTask       *task_in,
-                       GtkTreeIter   *iter_in)
+model_store_find_task_by_attr (MilkTaskModel   *model,
+                               RtmTask         *task_in,
+                               RtmTaskAttrFunc  attr_func,
+                               GtkTreeIter     *iter_in)
 {
         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
         gboolean valid;
@@ -262,8 +254,8 @@ model_store_find_task (MilkTaskModel *model,
                                         MILK_TASK_MODEL_COLUMN_TASK, &task,
                                         -1);
 
-                if (!g_strcmp0 (rtm_task_get_id (task_in),
-                                rtm_task_get_id (task))) {
+                if (!g_strcmp0 (attr_func (task_in),
+                                attr_func (task))) {
                         *iter_in = iter;
                         found = TRUE;
                 }
@@ -278,99 +270,21 @@ model_store_find_task (MilkTaskModel *model,
 }
 
 static gboolean
-update_model (MilkTaskModel *model)
+model_store_find_task (MilkTaskModel *model,
+                       RtmTask       *task_in,
+                       GtkTreeIter   *iter_in)
 {
-        MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
-        GList *rtm_tasks;
-        GList *l;
-        GTimeVal current_time;
-        char *new_sync;
-        GError *error = NULL;
-
-        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);
-
-        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;
-                const char *id;
-                gboolean task_in_store;
-
-                rtm_task = RTM_TASK (l->data);
-
-                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) {
-                        RtmTask *old_task;
-                        GtkTreePath *path;
-
-                        /* rtm-glib doesn't re-use task structs when they're
-                         * updated, so we have to replace the changed */
-                        gtk_tree_model_get (
-                                        GTK_TREE_MODEL (priv->store), &iter,
-                                        MILK_TASK_MODEL_COLUMN_TASK, &old_task,
-                                        -1);
-
-                        gtk_list_store_set (
-                                        priv->store, &iter,
-                                        MILK_TASK_MODEL_COLUMN_TASK, rtm_task,
-                                        -1);
-
-                        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 (old_task);
-
-                /* Task is new */
-                } else {
-                        gtk_list_store_append (priv->store, &iter);
-                        gtk_list_store_set (
-                                        priv->store, &iter,
-                                        MILK_TASK_MODEL_COLUMN_TASK, rtm_task,
-                                        -1);
-                }
-        }
-
-        return TRUE;
+        return model_store_find_task_by_attr (model, task_in, rtm_task_get_id,
+                        iter_in);
 }
 
-static void
-auth_notify_cb (MilkAuth      *auth,
-                GParamSpec    *spec,
-                MilkTaskModel *model)
+static gboolean
+model_store_find_local_only_task (MilkTaskModel *model,
+                                  RtmTask       *task_in,
+                                  GtkTreeIter   *iter_in)
 {
-        if (milk_auth_get_state (auth) == MILK_AUTH_STATE_CONNECTED) {
-                update_model (model);
-        }
+        return model_store_find_task_by_attr (model, task_in,rtm_task_get_name, 
+                        iter_in);
 }
 
 static void
@@ -410,35 +324,151 @@ rows_reordered_cb (GtkTreeModel  *model,
                         new_order);
 }
 
-void
-milk_task_model_set_auth (MilkTaskModel *model,
-                          MilkAuth      *auth)
+static void
+cache_cleared_cb (MilkCache     *cache,
+                  MilkTaskModel *model)
 {
         MilkTaskModelPrivate *priv;
+        priv = MILK_TASK_MODEL_PRIVATE (model);
 
-        g_return_if_fail (model);
-        g_return_if_fail (MILK_IS_TASK_MODEL (model));
-        g_return_if_fail (auth);
-        g_return_if_fail (MILK_IS_AUTH (auth));
+        gtk_list_store_clear (priv->store);
+}
+
+static void
+cache_task_added_cb (MilkCache     *cache,
+                     RtmTask       *task,
+                     MilkTaskModel *model)
+{
+        MilkTaskModelPrivate *priv;
+        GtkTreeIter iter;
+        const char *id;
+        gboolean task_in_store;
+
+        priv = MILK_TASK_MODEL_PRIVATE (model);
+
+        /* local-only tasks don't have a set task ID */
+        id = rtm_task_get_id (task);
+        if (id) {
+                /* clear out any entries for the task created before we knew its
+                 * server-side ID */
+                g_hash_table_remove (priv->tasks, rtm_task_get_name (task));
+        } else {
+                id = rtm_task_get_name (task);
+        }
+        g_return_if_fail (id);
+
+        g_hash_table_insert (priv->tasks, g_strdup (id),
+                        g_object_ref (task));
+
+        task_in_store = model_store_find_task (model, task, &iter);
+
+        gtk_list_store_append (priv->store, &iter);
+        gtk_list_store_set (priv->store, &iter,
+                        MILK_TASK_MODEL_COLUMN_TASK, task, -1);
+}
+
+static void
+cache_task_changed_cb (MilkCache     *cache,
+                       RtmTask       *task,
+                       MilkTaskModel *model)
+{
+        MilkTaskModelPrivate *priv;
+        GtkTreeIter iter;
+        const char *id;
+        gboolean task_in_store;
+        RtmTask *old_task;
+        GtkTreePath *path;
 
         priv = MILK_TASK_MODEL_PRIVATE (model);
 
-        if (priv->auth) {
-                g_object_unref (priv->auth);
+        id = rtm_task_get_id (task);
+        g_hash_table_insert (priv->tasks, g_strdup (id),
+                        g_object_ref (task));
+
+        /* try to find a local-only version of this task first, to upgrade it to
+         * remote status */
+        task_in_store = model_store_find_local_only_task (model, task, &iter);
+        if (!task_in_store) {
+                /* FIXME: cut this */
+                g_debug ("task (supposedly) was already known remotely");
+
+                task_in_store = model_store_find_task (model, task, &iter);
+        } else {
+                /* FIXME: cut this */
+                g_debug ("task was *NOT* known remotely");
         }
-        priv->auth = g_object_ref (auth);
 
-        if (priv->update_id) {
-                g_source_remove (priv->update_id);
+        /* rtm-glib doesn't re-use task structs when they're updated, so we have
+         * to replace the changed */
+        gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+                        MILK_TASK_MODEL_COLUMN_TASK, &old_task, -1);
+
+        gtk_list_store_set (priv->store, &iter,
+                        MILK_TASK_MODEL_COLUMN_TASK, task, -1);
+
+        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 (old_task);
+}
+
+static void
+cache_task_finished_cb (MilkCache     *cache,
+                        RtmTask       *task,
+                        MilkTaskModel *model)
+{
+        MilkTaskModelPrivate *priv;
+        GtkTreeIter iter;
+        const char *id;
+        gboolean task_in_store;
+
+        priv = MILK_TASK_MODEL_PRIVATE (model);
+
+        id = rtm_task_get_id (task);
+        g_hash_table_insert (priv->tasks, g_strdup (id),
+                        g_object_ref (task));
+
+        task_in_store = model_store_find_task (model, task, &iter);
+
+        if (task_in_store)
+                gtk_list_store_remove (priv->store, &iter);
+}
+
+static void
+set_cache (MilkTaskModel *model,
+           MilkCache     *cache)
+{
+        MilkTaskModelPrivate *priv;
+        GList *tasks, *l;
+
+        g_return_if_fail (MILK_IS_TASK_MODEL (model));
+        g_return_if_fail (MILK_IS_CACHE (cache));
+
+        priv = MILK_TASK_MODEL_PRIVATE (model);
+
+        if (priv->cache) {
+                g_object_unref (priv->cache);
         }
-        priv->update_id = g_timeout_add (MODEL_UPDATE_PERIOD,
-                        (GSourceFunc) update_model, model);
+        priv->cache = g_object_ref (cache);
 
         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);
+        g_signal_connect (cache, "cleared", G_CALLBACK (cache_cleared_cb),
+                        model);
+        g_signal_connect (cache, "task-added", G_CALLBACK (cache_task_added_cb),
+                        model);
+        g_signal_connect (cache, "task-changed",
+                        G_CALLBACK (cache_task_changed_cb), model);
+        g_signal_connect (cache, "task-finished",
+                        G_CALLBACK (cache_task_finished_cb), model);
+
+        /* do the initial fill from the cache */
+        tasks = milk_cache_get_active_tasks (cache);
+        for (l = tasks; l; l = l->next) {
+                cache_task_added_cb (cache, l->data, model);
+        }
+        g_list_free (tasks);
 }
 
 static void
@@ -451,8 +481,8 @@ milk_task_model_get_property (GObject    *object,
 
         switch (property_id)
         {
-                case PROP_AUTH:
-                        g_value_set_object (value, priv->auth);
+                case PROP_CACHE:
+                        g_value_set_object (value, priv->cache);
                 break;
 
                 default:
@@ -469,8 +499,8 @@ milk_task_model_set_property (GObject      *object,
 {
         switch (property_id)
         {
-                case PROP_AUTH:
-                        milk_task_model_set_auth (MILK_TASK_MODEL (object),
+                case PROP_CACHE:
+                        set_cache (MILK_TASK_MODEL (object),
                                         g_value_get_object (value));
                 break;
 
@@ -485,9 +515,18 @@ milk_task_model_dispose (GObject *object)
 {
         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
 
-        if (priv->auth) {
-                g_object_unref (priv->auth);
-                priv->auth = NULL;
+        g_signal_handlers_disconnect_by_func (priv->cache, cache_cleared_cb,
+                        object);
+        g_signal_handlers_disconnect_by_func (priv->cache, cache_task_added_cb,
+                        object);
+        g_signal_handlers_disconnect_by_func (priv->cache,
+                        cache_task_changed_cb, object);
+        g_signal_handlers_disconnect_by_func (priv->cache,
+                        cache_task_finished_cb, object);
+
+        if (priv->cache) {
+                g_object_unref (priv->cache);
+                priv->cache = NULL;
         }
 
         g_signal_handlers_disconnect_by_func (priv->store, row_changed_cb,
@@ -504,11 +543,6 @@ 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;
@@ -518,16 +552,6 @@ milk_task_model_dispose (GObject *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);
@@ -537,16 +561,15 @@ 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,
-                 PROP_AUTH,
+                 PROP_CACHE,
                  g_param_spec_object
-                         ("auth",
-                          "Authentication proxy",
-                          "Remember The Milk authentication proxy.",
-                          MILK_TYPE_AUTH,
+                         ("cache",
+                          "Cache of tasks",
+                          "Remember The Milk tasks cache.",
+                          MILK_TYPE_CACHE,
                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                           G_PARAM_STATIC_STRINGS));
 }
@@ -559,8 +582,6 @@ 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);
 
@@ -598,7 +619,8 @@ milk_task_model_tree_model_init (GtkTreeModelIface *iface)
 }
 
 MilkTaskModel*
-milk_task_model_new (MilkAuth *auth)
+milk_task_model_new ()
 {
-        return g_object_new (MILK_TYPE_TASK_MODEL, "auth", auth, NULL);
+        return g_object_new (MILK_TYPE_TASK_MODEL,
+                        "cache", milk_cache_get_default (), NULL);
 }