Cut unnecessary MilkTask class.
[milk] / src / milk-task-model.c
index 8e6a13d..a1d6594 100644 (file)
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 #include <hildon/hildon.h>
+#include <rtm-glib/rtm-glib.h>
 
 #include "milk-task-model.h"
+#include "milk-auth.h"
 
 static void
 milk_task_model_tree_model_init (GtkTreeModelIface *iface);
@@ -39,27 +41,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;
 };
 
-/* FIXME: relocate this to its own class */
-typedef struct {
-        gint id;
-        gint priority;
-        char *title;
-} MilkTask;
-
-/* FIXME: don't hard-code this */
-static MilkTask tasks[] = {
-        { 0, 1, "Remember the milk"},
-        { 2, 1, "Make a Maemo 5 RTM client"},
-        { 6, 3, "Get a haircut"},
-        { 9, 2, "Keep it real"},
-        { 5, 3, "Invent smellovision"},
+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)
 {
@@ -77,14 +82,8 @@ milk_task_model_get_column_type (GtkTreeModel *model,
                                  gint          column)
 {
         switch (column) {
-                case MILK_TASK_MODEL_COLUMN_ID:
-                        return G_TYPE_INT;
-
-                case MILK_TASK_MODEL_COLUMN_PRIORITY:
-                        return G_TYPE_UINT;
-
-                case MILK_TASK_MODEL_COLUMN_TITLE:
-                        return G_TYPE_STRING;
+                case MILK_TASK_MODEL_COLUMN_TASK:
+                        return RTM_TYPE_TASK;
 
                 default:
                         g_warning (G_STRLOC ": invalid column: %d", column);
@@ -99,7 +98,7 @@ milk_task_model_get_iter (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_val_if_fail (MILK_IS_LIST_STORE (model), FALSE);
+        g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
         g_return_val_if_fail (iter, FALSE);
         g_return_val_if_fail (gtk_tree_path_get_depth (path) == 1, FALSE);
 
@@ -115,7 +114,7 @@ milk_task_model_get_path (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_val_if_fail (MILK_IS_LIST_STORE (model), NULL);
+        g_return_val_if_fail (MILK_IS_TASK_MODEL (model), NULL);
         g_return_val_if_fail (iter, NULL);
 
         priv = MILK_TASK_MODEL_PRIVATE (model);
@@ -131,9 +130,8 @@ milk_task_model_get_value (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_if_fail (MILK_IS_LIST_STORE (model));
+        g_return_if_fail (MILK_IS_TASK_MODEL (model));
         g_return_if_fail (iter);
-        g_return_if_fail (column >= 0);
         g_return_if_fail (value);
 
         priv = MILK_TASK_MODEL_PRIVATE (model);
@@ -148,7 +146,7 @@ milk_task_model_iter_next (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_val_if_fail (MILK_IS_LIST_STORE (model), FALSE);
+        g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
         g_return_val_if_fail (iter, FALSE);
 
         priv = MILK_TASK_MODEL_PRIVATE (model);
@@ -164,7 +162,7 @@ milk_task_model_iter_nth_child (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_val_if_fail (MILK_IS_LIST_STORE (model), FALSE);
+        g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
         g_return_val_if_fail (iter, FALSE);
         /* we're one-dimensional */
         g_return_val_if_fail (!parent, FALSE);
@@ -183,7 +181,7 @@ milk_task_model_iter_children (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_val_if_fail (MILK_IS_LIST_STORE (model), FALSE);
+        g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
         g_return_val_if_fail (iter, FALSE);
         /* we're one-dimensional */
         g_return_val_if_fail (!parent, FALSE);
@@ -200,7 +198,7 @@ milk_task_model_iter_has_child (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_val_if_fail (MILK_IS_LIST_STORE (model), FALSE);
+        g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
         g_return_val_if_fail (iter, FALSE);
 
         priv = MILK_TASK_MODEL_PRIVATE (model);
@@ -215,7 +213,7 @@ milk_task_model_iter_n_children (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_val_if_fail (MILK_IS_LIST_STORE (model), -1);
+        g_return_val_if_fail (MILK_IS_TASK_MODEL (model), -1);
         g_return_val_if_fail (iter, -1);
 
         priv = MILK_TASK_MODEL_PRIVATE (model);
@@ -235,7 +233,7 @@ milk_task_model_iter_parent (GtkTreeModel *model,
 {
         MilkTaskModelPrivate *priv;
 
-        g_return_val_if_fail (MILK_IS_LIST_STORE (model), FALSE);
+        g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
         g_return_val_if_fail (iter, FALSE);
         g_return_val_if_fail (child, FALSE);
 
@@ -245,14 +243,218 @@ milk_task_model_iter_parent (GtkTreeModel *model,
                         GTK_TREE_MODEL (priv->store), iter, child);
 }
 
+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) {
+                RtmTask *task;
+
+                gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+                                        MILK_TASK_MODEL_COLUMN_TASK, &task,
+                                        -1);
+
+                if (!g_strcmp0 (rtm_task_get_id (task_in),
+                                rtm_task_get_id (task))) {
+                        *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;
+        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;
+}
+
+static void
+auth_notify_cb (MilkAuth      *auth,
+                GParamSpec    *spec,
+                MilkTaskModel *model)
+{
+        if (milk_auth_get_state (auth) == MILK_AUTH_STATE_CONNECTED) {
+                update_model (model);
+        }
+}
+
+static void
+row_changed_cb (GtkTreeModel  *model,
+                GtkTreePath   *path,
+                GtkTreeIter   *iter,
+                MilkTaskModel *self)
+{
+        gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, iter);
+}
+
+static void
+row_deleted_cb (GtkTreeModel  *model,
+                GtkTreePath   *path,
+                MilkTaskModel *self)
+{
+        gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
+}
+
+static void
+row_inserted_cb (GtkTreeModel  *model,
+                 GtkTreePath   *path,
+                 GtkTreeIter   *iter,
+                 MilkTaskModel *self)
+{
+        gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, iter);
+}
+
+static void
+rows_reordered_cb (GtkTreeModel  *model,
+                   GtkTreePath   *path,
+                   GtkTreeIter   *iter,
+                   gint          *new_order,
+                   MilkTaskModel *self)
+{
+        gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, NULL,
+                        new_order);
+}
+
+void
+milk_task_model_set_auth (MilkTaskModel *model,
+                          MilkAuth      *auth)
+{
+        MilkTaskModelPrivate *priv;
+
+        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));
+
+        priv = MILK_TASK_MODEL_PRIVATE (model);
+
+        if (priv->auth) {
+                g_object_unref (priv->auth);
+        }
+        priv->auth = g_object_ref (auth);
+
+        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
 milk_task_model_get_property (GObject    *object,
-                               guint       property_id,
-                               GValue     *value,
-                               GParamSpec *pspec)
+                              guint       property_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
 {
+        MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
+
         switch (property_id)
         {
+                case PROP_AUTH:
+                        g_value_set_object (value, priv->auth);
+                break;
+
                 default:
                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
                                         pspec);
@@ -261,12 +463,17 @@ milk_task_model_get_property (GObject    *object,
 
 static void
 milk_task_model_set_property (GObject      *object,
-                               guint         property_id,
-                               const GValue *value,
-                               GParamSpec   *pspec)
+                              guint         property_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
 {
         switch (property_id)
         {
+                case PROP_AUTH:
+                        milk_task_model_set_auth (MILK_TASK_MODEL (object),
+                                        g_value_get_object (value));
+                break;
+
                 default:
                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
                                         pspec);
@@ -278,49 +485,46 @@ 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->store, row_changed_cb,
+                        object);
+        g_signal_handlers_disconnect_by_func (priv->store, row_deleted_cb,
+                        object);
+        g_signal_handlers_disconnect_by_func (priv->store, row_inserted_cb,
+                        object);
+        g_signal_handlers_disconnect_by_func (priv->store, rows_reordered_cb,
+                        object);
+
         if (priv->store) {
                 g_object_unref (priv->store);
                 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_constructed (GObject* object)
+milk_task_model_finalize (GObject *object)
 {
-        typedef struct {
-                const char *id;
-                const char *title;
-                gint priority;
-        } MilkTask_args;
-
-        /* FIXME: don't hard-code this */
-        static MilkTask_args tasks[] = {
-                { "0", "Walk the dog", 1},
-                { "2", "Make a Maemo 5 RTM client", 1},
-                { "6", "Stand on one foot", 3},
-                { "9", "Pick up some DVX ('cause it's so crisp)", 2},
-                { "5", "Finalize Halloween costume", 3},
-        };
-
         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
-        GtkTreeIter iter;
-        gint i;
 
-        /* Populate model */
-        for (i = 0; i < G_N_ELEMENTS (tasks); i++) {
-                gtk_list_store_append (priv->store, &iter);
-                gtk_list_store_set (
-                                priv->store, &iter,
-                                MILK_TASK_MODEL_COLUMN_ID,
-                                tasks[i].id,
-                                MILK_TASK_MODEL_COLUMN_PRIORITY,
-                                tasks[i].priority,
-                                MILK_TASK_MODEL_COLUMN_TITLE,
-                                tasks[i].title,
-                                -1);
-        }
+        g_free (priv->last_sync);
+
+        G_OBJECT_CLASS (milk_task_model_parent_class)->finalize (object);
 }
 
 static void
@@ -332,8 +536,19 @@ 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->constructed = milk_task_model_constructed;
         object_class->dispose = milk_task_model_dispose;
+        object_class->finalize = milk_task_model_finalize;
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_AUTH,
+                 g_param_spec_object
+                         ("auth",
+                          "Authentication proxy",
+                          "Remember The Milk authentication proxy.",
+                          MILK_TYPE_AUTH,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -342,11 +557,27 @@ milk_task_model_init (MilkTaskModel *self)
         MilkTaskModelPrivate *priv;
 
         self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (
-                        self, MILK_TYPE_LIST_STORE, MilkTaskModelPrivate);
+                        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, G_TYPE_INT, G_TYPE_UINT,
-                        G_TYPE_STRING);
+                        MILK_TASK_MODEL_N_COLUMNS, RTM_TYPE_TASK);
+
+        g_signal_connect (priv->store, "row-changed",
+                        G_CALLBACK (row_changed_cb), self);
+
+        g_signal_connect (priv->store, "row-deleted",
+                        G_CALLBACK (row_deleted_cb), self);
+
+        g_signal_connect (priv->store, "row-inserted",
+                        G_CALLBACK (row_inserted_cb), self);
+
+        g_signal_connect (priv->store, "rows-reordered",
+                        G_CALLBACK (rows_reordered_cb), self);
 }
 
 static void
@@ -367,8 +598,7 @@ milk_task_model_tree_model_init (GtkTreeModelIface *iface)
 }
 
 MilkTaskModel*
-milk_task_model_new ()
+milk_task_model_new (MilkAuth *auth)
 {
-        return g_object_new (MILK_TYPE_LIST_STORE,
-                             NULL);
+        return g_object_new (MILK_TYPE_TASK_MODEL, "auth", auth, NULL);
 }