Add a basic task cache
[milk] / src / milk-task-model.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License as
4  * published by the Free Software Foundation; either version 2 of the
5  * License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  * General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public
13  * License along with this program; if not, write to the
14  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
15  * Boston, MA  02110-1301  USA
16  *
17  * Authors: Travis Reitter <treitter@gmail.com>
18  */
19
20 #include <config.h>
21
22 #include <glib.h>
23 #include <glib/gi18n.h>
24 #include <gtk/gtk.h>
25 #include <hildon/hildon.h>
26 #include <rtm-glib/rtm-glib.h>
27
28 #include "milk-task-model.h"
29 #include "milk-auth.h"
30 #include "milk-cache.h"
31
32 static void
33 milk_task_model_tree_model_init (GtkTreeModelIface *iface);
34
35 G_DEFINE_TYPE_EXTENDED (MilkTaskModel,
36                         milk_task_model,
37                         G_TYPE_OBJECT,
38                         0,
39                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
40                                                milk_task_model_tree_model_init));
41
42 /* less expensive than G_TYPE_INSTANCE_GET_PRIVATE */
43 #define MILK_TASK_MODEL_PRIVATE(o) ((MILK_TASK_MODEL ((o)))->priv)
44
45 struct _MilkTaskModelPrivate
46 {
47         GHashTable *tasks;
48         GtkListStore *store;
49         MilkCache *cache;
50 };
51
52 enum {
53         PROP_0,
54         PROP_CACHE,
55 };
56
57 static GtkTreeModelFlags
58 milk_task_model_get_flags (GtkTreeModel *model)
59 {
60         return GTK_TREE_MODEL_LIST_ONLY;
61 }
62
63 static gint
64 milk_task_model_get_n_columns (GtkTreeModel *model)
65 {
66         return MILK_TASK_MODEL_N_COLUMNS;
67 }
68
69 static GType
70 milk_task_model_get_column_type (GtkTreeModel *model,
71                                  gint          column)
72 {
73         switch (column) {
74                 case MILK_TASK_MODEL_COLUMN_TASK:
75                         return RTM_TYPE_TASK;
76
77                 default:
78                         g_warning (G_STRLOC ": invalid column: %d", column);
79                         return G_TYPE_INVALID;
80         }
81 }
82
83 static gboolean
84 milk_task_model_get_iter (GtkTreeModel *model,
85                           GtkTreeIter  *iter,
86                           GtkTreePath  *path)
87 {
88         MilkTaskModelPrivate *priv;
89
90         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
91         g_return_val_if_fail (iter, FALSE);
92         g_return_val_if_fail (gtk_tree_path_get_depth (path) == 1, FALSE);
93
94         priv = MILK_TASK_MODEL_PRIVATE (model);
95
96         return gtk_tree_model_get_iter (
97                         GTK_TREE_MODEL (priv->store), iter, path);
98 }
99
100 static GtkTreePath*
101 milk_task_model_get_path (GtkTreeModel *model,
102                           GtkTreeIter  *iter)
103 {
104         MilkTaskModelPrivate *priv;
105
106         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), NULL);
107         g_return_val_if_fail (iter, NULL);
108
109         priv = MILK_TASK_MODEL_PRIVATE (model);
110
111         return gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), iter);
112 }
113
114 static void
115 milk_task_model_get_value (GtkTreeModel *model,
116                            GtkTreeIter  *iter,
117                            gint          column,
118                            GValue       *value)
119 {
120         MilkTaskModelPrivate *priv;
121
122         g_return_if_fail (MILK_IS_TASK_MODEL (model));
123         g_return_if_fail (iter);
124         g_return_if_fail (value);
125
126         priv = MILK_TASK_MODEL_PRIVATE (model);
127
128         gtk_tree_model_get_value (
129                         GTK_TREE_MODEL (priv->store), iter, column, value);
130 }
131
132 static gboolean
133 milk_task_model_iter_next (GtkTreeModel *model,
134                            GtkTreeIter  *iter)
135 {
136         MilkTaskModelPrivate *priv;
137
138         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
139         g_return_val_if_fail (iter, FALSE);
140
141         priv = MILK_TASK_MODEL_PRIVATE (model);
142
143         return gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), iter);
144 }
145
146 static gboolean
147 milk_task_model_iter_nth_child (GtkTreeModel *model,
148                                 GtkTreeIter  *iter,
149                                 GtkTreeIter  *parent,
150                                 gint          index)
151 {
152         MilkTaskModelPrivate *priv;
153
154         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
155         g_return_val_if_fail (iter, FALSE);
156         /* we're one-dimensional */
157         g_return_val_if_fail (!parent, FALSE);
158         g_return_val_if_fail (index >= 0, FALSE);
159
160         priv = MILK_TASK_MODEL_PRIVATE (model);
161
162         return gtk_tree_model_iter_nth_child (
163                         GTK_TREE_MODEL (priv->store), iter, parent, index);
164 }
165
166 static gboolean
167 milk_task_model_iter_children (GtkTreeModel *model,
168                                GtkTreeIter  *iter,
169                                GtkTreeIter  *parent)
170 {
171         MilkTaskModelPrivate *priv;
172
173         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
174         g_return_val_if_fail (iter, FALSE);
175         /* we're one-dimensional */
176         g_return_val_if_fail (!parent, FALSE);
177
178         priv = MILK_TASK_MODEL_PRIVATE (model);
179
180         return gtk_tree_model_iter_children (
181                         GTK_TREE_MODEL (priv->store), iter, parent);
182 }
183
184 static gboolean
185 milk_task_model_iter_has_child (GtkTreeModel *model,
186                                 GtkTreeIter  *iter)
187 {
188         MilkTaskModelPrivate *priv;
189
190         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
191         g_return_val_if_fail (iter, FALSE);
192
193         priv = MILK_TASK_MODEL_PRIVATE (model);
194
195         return gtk_tree_model_iter_has_child (
196                         GTK_TREE_MODEL (priv->store), iter);
197 }
198
199 static gint
200 milk_task_model_iter_n_children (GtkTreeModel *model,
201                                  GtkTreeIter  *iter)
202 {
203         MilkTaskModelPrivate *priv;
204
205         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), -1);
206         g_return_val_if_fail (iter, -1);
207
208         priv = MILK_TASK_MODEL_PRIVATE (model);
209
210         /* we're one-dimensional */
211         if (iter)
212                 return 0;
213
214         return gtk_tree_model_iter_n_children (
215                         GTK_TREE_MODEL (priv->store), iter);
216 }
217
218 static gboolean
219 milk_task_model_iter_parent (GtkTreeModel *model,
220                              GtkTreeIter  *iter,
221                              GtkTreeIter  *child)
222 {
223         MilkTaskModelPrivate *priv;
224
225         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
226         g_return_val_if_fail (iter, FALSE);
227         g_return_val_if_fail (child, FALSE);
228
229         priv = MILK_TASK_MODEL_PRIVATE (model);
230
231         return gtk_tree_model_iter_parent (
232                         GTK_TREE_MODEL (priv->store), iter, child);
233 }
234
235 typedef gchar* (*RtmTaskAttrFunc) (RtmTask*);
236
237 static gboolean
238 model_store_find_task_by_attr (MilkTaskModel   *model,
239                                RtmTask         *task_in,
240                                RtmTaskAttrFunc  attr_func,
241                                GtkTreeIter     *iter_in)
242 {
243         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
244         gboolean valid;
245         GtkTreeIter iter;
246         gboolean found = FALSE;
247
248         valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store),
249                         &iter);
250         while (valid && !found) {
251                 RtmTask *task;
252
253                 gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
254                                         MILK_TASK_MODEL_COLUMN_TASK, &task,
255                                         -1);
256
257                 if (!g_strcmp0 (attr_func (task_in),
258                                 attr_func (task))) {
259                         *iter_in = iter;
260                         found = TRUE;
261                 }
262
263                 g_object_unref (task);
264
265                 valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store),
266                                 &iter);
267         }
268
269         return found;
270 }
271
272 static gboolean
273 model_store_find_task (MilkTaskModel *model,
274                        RtmTask       *task_in,
275                        GtkTreeIter   *iter_in)
276 {
277         return model_store_find_task_by_attr (model, task_in, rtm_task_get_id,
278                         iter_in);
279 }
280
281 static gboolean
282 model_store_find_local_only_task (MilkTaskModel *model,
283                                   RtmTask       *task_in,
284                                   GtkTreeIter   *iter_in)
285 {
286         return model_store_find_task_by_attr (model, task_in,rtm_task_get_name, 
287                         iter_in);
288 }
289
290 static void
291 row_changed_cb (GtkTreeModel  *model,
292                 GtkTreePath   *path,
293                 GtkTreeIter   *iter,
294                 MilkTaskModel *self)
295 {
296         gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, iter);
297 }
298
299 static void
300 row_deleted_cb (GtkTreeModel  *model,
301                 GtkTreePath   *path,
302                 MilkTaskModel *self)
303 {
304         gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
305 }
306
307 static void
308 row_inserted_cb (GtkTreeModel  *model,
309                  GtkTreePath   *path,
310                  GtkTreeIter   *iter,
311                  MilkTaskModel *self)
312 {
313         gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, iter);
314 }
315
316 static void
317 rows_reordered_cb (GtkTreeModel  *model,
318                    GtkTreePath   *path,
319                    GtkTreeIter   *iter,
320                    gint          *new_order,
321                    MilkTaskModel *self)
322 {
323         gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, NULL,
324                         new_order);
325 }
326
327 static void
328 cache_cleared_cb (MilkCache     *cache,
329                   MilkTaskModel *model)
330 {
331         MilkTaskModelPrivate *priv;
332         priv = MILK_TASK_MODEL_PRIVATE (model);
333
334         gtk_list_store_clear (priv->store);
335 }
336
337 static void
338 cache_task_added_cb (MilkCache     *cache,
339                      RtmTask       *task,
340                      MilkTaskModel *model)
341 {
342         MilkTaskModelPrivate *priv;
343         GtkTreeIter iter;
344         const char *id;
345         gboolean task_in_store;
346
347         priv = MILK_TASK_MODEL_PRIVATE (model);
348
349         /* local-only tasks don't have a set task ID */
350         id = rtm_task_get_id (task);
351         if (id) {
352                 /* clear out any entries for the task created before we knew its
353                  * server-side ID */
354                 g_hash_table_remove (priv->tasks, rtm_task_get_name (task));
355         } else {
356                 id = rtm_task_get_name (task);
357         }
358         g_return_if_fail (id);
359
360         g_hash_table_insert (priv->tasks, g_strdup (id),
361                         g_object_ref (task));
362
363         task_in_store = model_store_find_task (model, task, &iter);
364
365         gtk_list_store_append (priv->store, &iter);
366         gtk_list_store_set (priv->store, &iter,
367                         MILK_TASK_MODEL_COLUMN_TASK, task, -1);
368 }
369
370 static void
371 cache_task_changed_cb (MilkCache     *cache,
372                        RtmTask       *task,
373                        MilkTaskModel *model)
374 {
375         MilkTaskModelPrivate *priv;
376         GtkTreeIter iter;
377         const char *id;
378         gboolean task_in_store;
379         RtmTask *old_task;
380         GtkTreePath *path;
381
382         priv = MILK_TASK_MODEL_PRIVATE (model);
383
384         id = rtm_task_get_id (task);
385         g_hash_table_insert (priv->tasks, g_strdup (id),
386                         g_object_ref (task));
387
388         /* try to find a local-only version of this task first, to upgrade it to
389          * remote status */
390         task_in_store = model_store_find_local_only_task (model, task, &iter);
391         if (!task_in_store) {
392                 /* FIXME: cut this */
393                 g_debug ("task (supposedly) was already known remotely");
394
395                 task_in_store = model_store_find_task (model, task, &iter);
396         } else {
397                 /* FIXME: cut this */
398                 g_debug ("task was *NOT* known remotely");
399         }
400
401         /* rtm-glib doesn't re-use task structs when they're updated, so we have
402          * to replace the changed */
403         gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
404                         MILK_TASK_MODEL_COLUMN_TASK, &old_task, -1);
405
406         gtk_list_store_set (priv->store, &iter,
407                         MILK_TASK_MODEL_COLUMN_TASK, task, -1);
408
409         path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), &iter);
410         gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store), path, &iter);
411         gtk_tree_path_free (path);
412
413         g_object_unref (old_task);
414 }
415
416 static void
417 cache_task_finished_cb (MilkCache     *cache,
418                         RtmTask       *task,
419                         MilkTaskModel *model)
420 {
421         MilkTaskModelPrivate *priv;
422         GtkTreeIter iter;
423         const char *id;
424         gboolean task_in_store;
425
426         priv = MILK_TASK_MODEL_PRIVATE (model);
427
428         id = rtm_task_get_id (task);
429         g_hash_table_insert (priv->tasks, g_strdup (id),
430                         g_object_ref (task));
431
432         task_in_store = model_store_find_task (model, task, &iter);
433
434         if (task_in_store)
435                 gtk_list_store_remove (priv->store, &iter);
436 }
437
438 static void
439 set_cache (MilkTaskModel *model,
440            MilkCache     *cache)
441 {
442         MilkTaskModelPrivate *priv;
443         GList *tasks, *l;
444
445         g_return_if_fail (MILK_IS_TASK_MODEL (model));
446         g_return_if_fail (MILK_IS_CACHE (cache));
447
448         priv = MILK_TASK_MODEL_PRIVATE (model);
449
450         if (priv->cache) {
451                 g_object_unref (priv->cache);
452         }
453         priv->cache = g_object_ref (cache);
454
455         gtk_list_store_clear (priv->store);
456
457         g_signal_connect (cache, "cleared", G_CALLBACK (cache_cleared_cb),
458                         model);
459         g_signal_connect (cache, "task-added", G_CALLBACK (cache_task_added_cb),
460                         model);
461         g_signal_connect (cache, "task-changed",
462                         G_CALLBACK (cache_task_changed_cb), model);
463         g_signal_connect (cache, "task-finished",
464                         G_CALLBACK (cache_task_finished_cb), model);
465
466         /* do the initial fill from the cache */
467         tasks = milk_cache_get_active_tasks (cache);
468         for (l = tasks; l; l = l->next) {
469                 cache_task_added_cb (cache, l->data, model);
470         }
471         g_list_free (tasks);
472 }
473
474 static void
475 milk_task_model_get_property (GObject    *object,
476                               guint       property_id,
477                               GValue     *value,
478                               GParamSpec *pspec)
479 {
480         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
481
482         switch (property_id)
483         {
484                 case PROP_CACHE:
485                         g_value_set_object (value, priv->cache);
486                 break;
487
488                 default:
489                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
490                                         pspec);
491         }
492 }
493
494 static void
495 milk_task_model_set_property (GObject      *object,
496                               guint         property_id,
497                               const GValue *value,
498                               GParamSpec   *pspec)
499 {
500         switch (property_id)
501         {
502                 case PROP_CACHE:
503                         set_cache (MILK_TASK_MODEL (object),
504                                         g_value_get_object (value));
505                 break;
506
507                 default:
508                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
509                                         pspec);
510         }
511 }
512
513 static void
514 milk_task_model_dispose (GObject *object)
515 {
516         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
517
518         g_signal_handlers_disconnect_by_func (priv->cache, cache_cleared_cb,
519                         object);
520         g_signal_handlers_disconnect_by_func (priv->cache, cache_task_added_cb,
521                         object);
522         g_signal_handlers_disconnect_by_func (priv->cache,
523                         cache_task_changed_cb, object);
524         g_signal_handlers_disconnect_by_func (priv->cache,
525                         cache_task_finished_cb, object);
526
527         if (priv->cache) {
528                 g_object_unref (priv->cache);
529                 priv->cache = NULL;
530         }
531
532         g_signal_handlers_disconnect_by_func (priv->store, row_changed_cb,
533                         object);
534         g_signal_handlers_disconnect_by_func (priv->store, row_deleted_cb,
535                         object);
536         g_signal_handlers_disconnect_by_func (priv->store, row_inserted_cb,
537                         object);
538         g_signal_handlers_disconnect_by_func (priv->store, rows_reordered_cb,
539                         object);
540
541         if (priv->store) {
542                 g_object_unref (priv->store);
543                 priv->store = NULL;
544         }
545
546         if (priv->tasks) {
547                 g_hash_table_destroy (priv->tasks);
548                 priv->tasks = NULL;
549         }
550
551         G_OBJECT_CLASS (milk_task_model_parent_class)->dispose (object);
552 }
553
554 static void
555 milk_task_model_class_init (MilkTaskModelClass *klass)
556 {
557         GObjectClass *object_class = G_OBJECT_CLASS (klass);
558
559         g_type_class_add_private (klass, sizeof (MilkTaskModelPrivate));
560
561         object_class->get_property = milk_task_model_get_property;
562         object_class->set_property = milk_task_model_set_property;
563         object_class->dispose = milk_task_model_dispose;
564
565         g_object_class_install_property
566                 (object_class,
567                  PROP_CACHE,
568                  g_param_spec_object
569                          ("cache",
570                           "Cache of tasks",
571                           "Remember The Milk tasks cache.",
572                           MILK_TYPE_CACHE,
573                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
574                           G_PARAM_STATIC_STRINGS));
575 }
576
577 static void
578 milk_task_model_init (MilkTaskModel *self)
579 {
580         MilkTaskModelPrivate *priv;
581
582         self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (
583                         self, MILK_TYPE_TASK_MODEL, MilkTaskModelPrivate);
584
585         priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
586                         g_free, g_object_unref);
587
588         priv->store = gtk_list_store_new (
589                         MILK_TASK_MODEL_N_COLUMNS, RTM_TYPE_TASK);
590
591         g_signal_connect (priv->store, "row-changed",
592                         G_CALLBACK (row_changed_cb), self);
593
594         g_signal_connect (priv->store, "row-deleted",
595                         G_CALLBACK (row_deleted_cb), self);
596
597         g_signal_connect (priv->store, "row-inserted",
598                         G_CALLBACK (row_inserted_cb), self);
599
600         g_signal_connect (priv->store, "rows-reordered",
601                         G_CALLBACK (rows_reordered_cb), self);
602 }
603
604 static void
605 milk_task_model_tree_model_init (GtkTreeModelIface *iface)
606 {
607         iface->get_flags       = milk_task_model_get_flags;
608         iface->get_n_columns   = milk_task_model_get_n_columns;
609         iface->get_column_type = milk_task_model_get_column_type;
610         iface->get_iter        = milk_task_model_get_iter;
611         iface->get_path        = milk_task_model_get_path;
612         iface->get_value       = milk_task_model_get_value;
613         iface->iter_next       = milk_task_model_iter_next;
614         iface->iter_children   = milk_task_model_iter_children;
615         iface->iter_has_child  = milk_task_model_iter_has_child;
616         iface->iter_n_children = milk_task_model_iter_n_children;
617         iface->iter_nth_child  = milk_task_model_iter_nth_child;
618         iface->iter_parent     = milk_task_model_iter_parent;
619 }
620
621 MilkTaskModel*
622 milk_task_model_new ()
623 {
624         return g_object_new (MILK_TYPE_TASK_MODEL,
625                         "cache", milk_cache_get_default (), NULL);
626 }