Poll for task additions, removals, and changes
[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-task.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 /* FIXME: make this configurable at runtime, pref. as a gconf value */
46 /* time between syncing with the server, in ms */
47 #define MODEL_UPDATE_PERIOD 60000
48
49 struct _MilkTaskModelPrivate
50 {
51         GHashTable *tasks;
52         GtkListStore *store;
53         MilkAuth *auth;
54         guint update_id;
55         char *last_sync;
56 };
57
58 enum {
59         PROP_AUTH = 1,
60 };
61
62 static gboolean
63 task_is_finished (RtmTask *task)
64 {
65         return (rtm_task_get_completed_date (task) ||
66                 rtm_task_get_deleted_date (task));
67 }
68
69 static GtkTreeModelFlags
70 milk_task_model_get_flags (GtkTreeModel *model)
71 {
72         return GTK_TREE_MODEL_LIST_ONLY;
73 }
74
75 static gint
76 milk_task_model_get_n_columns (GtkTreeModel *model)
77 {
78         return MILK_TASK_MODEL_N_COLUMNS;
79 }
80
81 static GType
82 milk_task_model_get_column_type (GtkTreeModel *model,
83                                  gint          column)
84 {
85         switch (column) {
86                 case MILK_TASK_MODEL_COLUMN_TASK:
87                         return MILK_TYPE_TASK;
88
89                 default:
90                         g_warning (G_STRLOC ": invalid column: %d", column);
91                         return G_TYPE_INVALID;
92         }
93 }
94
95 static gboolean
96 milk_task_model_get_iter (GtkTreeModel *model,
97                           GtkTreeIter  *iter,
98                           GtkTreePath  *path)
99 {
100         MilkTaskModelPrivate *priv;
101
102         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
103         g_return_val_if_fail (iter, FALSE);
104         g_return_val_if_fail (gtk_tree_path_get_depth (path) == 1, FALSE);
105
106         priv = MILK_TASK_MODEL_PRIVATE (model);
107
108         return gtk_tree_model_get_iter (
109                         GTK_TREE_MODEL (priv->store), iter, path);
110 }
111
112 static GtkTreePath*
113 milk_task_model_get_path (GtkTreeModel *model,
114                           GtkTreeIter  *iter)
115 {
116         MilkTaskModelPrivate *priv;
117
118         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), NULL);
119         g_return_val_if_fail (iter, NULL);
120
121         priv = MILK_TASK_MODEL_PRIVATE (model);
122
123         return gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), iter);
124 }
125
126 static void
127 milk_task_model_get_value (GtkTreeModel *model,
128                            GtkTreeIter  *iter,
129                            gint          column,
130                            GValue       *value)
131 {
132         MilkTaskModelPrivate *priv;
133
134         g_return_if_fail (MILK_IS_TASK_MODEL (model));
135         g_return_if_fail (iter);
136         g_return_if_fail (value);
137
138         priv = MILK_TASK_MODEL_PRIVATE (model);
139
140         gtk_tree_model_get_value (
141                         GTK_TREE_MODEL (priv->store), iter, column, value);
142 }
143
144 static gboolean
145 milk_task_model_iter_next (GtkTreeModel *model,
146                            GtkTreeIter  *iter)
147 {
148         MilkTaskModelPrivate *priv;
149
150         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
151         g_return_val_if_fail (iter, FALSE);
152
153         priv = MILK_TASK_MODEL_PRIVATE (model);
154
155         return gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), iter);
156 }
157
158 static gboolean
159 milk_task_model_iter_nth_child (GtkTreeModel *model,
160                                 GtkTreeIter  *iter,
161                                 GtkTreeIter  *parent,
162                                 gint          index)
163 {
164         MilkTaskModelPrivate *priv;
165
166         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
167         g_return_val_if_fail (iter, FALSE);
168         /* we're one-dimensional */
169         g_return_val_if_fail (!parent, FALSE);
170         g_return_val_if_fail (index >= 0, FALSE);
171
172         priv = MILK_TASK_MODEL_PRIVATE (model);
173
174         return gtk_tree_model_iter_nth_child (
175                         GTK_TREE_MODEL (priv->store), iter, parent, index);
176 }
177
178 static gboolean
179 milk_task_model_iter_children (GtkTreeModel *model,
180                                GtkTreeIter  *iter,
181                                GtkTreeIter  *parent)
182 {
183         MilkTaskModelPrivate *priv;
184
185         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
186         g_return_val_if_fail (iter, FALSE);
187         /* we're one-dimensional */
188         g_return_val_if_fail (!parent, FALSE);
189
190         priv = MILK_TASK_MODEL_PRIVATE (model);
191
192         return gtk_tree_model_iter_children (
193                         GTK_TREE_MODEL (priv->store), iter, parent);
194 }
195
196 static gboolean
197 milk_task_model_iter_has_child (GtkTreeModel *model,
198                                 GtkTreeIter  *iter)
199 {
200         MilkTaskModelPrivate *priv;
201
202         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
203         g_return_val_if_fail (iter, FALSE);
204
205         priv = MILK_TASK_MODEL_PRIVATE (model);
206
207         return gtk_tree_model_iter_has_child (
208                         GTK_TREE_MODEL (priv->store), iter);
209 }
210
211 static gint
212 milk_task_model_iter_n_children (GtkTreeModel *model,
213                                  GtkTreeIter  *iter)
214 {
215         MilkTaskModelPrivate *priv;
216
217         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), -1);
218         g_return_val_if_fail (iter, -1);
219
220         priv = MILK_TASK_MODEL_PRIVATE (model);
221
222         /* we're one-dimensional */
223         if (iter)
224                 return 0;
225
226         return gtk_tree_model_iter_n_children (
227                         GTK_TREE_MODEL (priv->store), iter);
228 }
229
230 static gboolean
231 milk_task_model_iter_parent (GtkTreeModel *model,
232                              GtkTreeIter  *iter,
233                              GtkTreeIter  *child)
234 {
235         MilkTaskModelPrivate *priv;
236
237         g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
238         g_return_val_if_fail (iter, FALSE);
239         g_return_val_if_fail (child, FALSE);
240
241         priv = MILK_TASK_MODEL_PRIVATE (model);
242
243         return gtk_tree_model_iter_parent (
244                         GTK_TREE_MODEL (priv->store), iter, child);
245 }
246
247 static gboolean
248 model_store_find_task (MilkTaskModel *model,
249                        RtmTask       *task_in,
250                        GtkTreeIter   *iter_in)
251 {
252         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
253         gboolean valid;
254         GtkTreeIter iter;
255         gboolean found = FALSE;
256
257         valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store),
258                         &iter);
259         while (valid && !found) {
260                 MilkTask *task;
261                 char *task_id;
262
263                 gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
264                                         MILK_TASK_MODEL_COLUMN_TASK, &task,
265                                         -1);
266                 g_object_get (task, "id", &task_id, NULL);
267
268                 if (!g_strcmp0 (rtm_task_get_id (task_in), task_id)) {
269                         *iter_in = iter;
270                         found = TRUE;
271                 }
272
273                 g_object_unref (task);
274
275                 valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store),
276                                 &iter);
277         }
278
279         return found;
280 }
281
282 static gboolean
283 update_model (MilkTaskModel *model)
284 {
285         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
286         GList *rtm_tasks;
287         GList *l;
288         GTimeVal current_time;
289         char *new_sync;
290         GError *error = NULL;
291
292         if (milk_auth_get_state (priv->auth) != MILK_AUTH_STATE_CONNECTED) {
293                 return TRUE;
294         }
295
296         g_get_current_time (&current_time);
297         new_sync = g_time_val_to_iso8601 (&current_time);
298         rtm_tasks = milk_auth_get_tasks (priv->auth, priv->last_sync, &error);
299
300         if (error) {
301                 g_error (G_STRLOC ": failed to retrieve latest tasks: %s",
302                                 error->message);
303                 g_clear_error (&error);
304         } else {
305                 g_free (priv->last_sync);
306                 priv->last_sync = new_sync;
307         }
308
309         /* Populate model */
310         for (l = rtm_tasks; l; l = g_list_delete_link (l, l)) {
311                 GtkTreeIter iter;
312                 RtmTask *rtm_task;
313                 MilkTask *task;
314                 const char *id;
315                 gboolean task_in_store;
316
317                 rtm_task = RTM_TASK (l->data);
318
319                 id = rtm_task_get_id (rtm_task);
320                 g_hash_table_insert (priv->tasks, g_strdup (id),
321                                 g_object_ref (rtm_task));
322
323                 task_in_store = model_store_find_task (model, rtm_task, &iter);
324
325                 /* Task is deleted or completed */
326                 if (task_is_finished (rtm_task)) {
327                         if (task_in_store) {
328                                 gtk_list_store_remove (priv->store, &iter);
329                         }
330                 /* Task has been changed */
331                 } else if (task_in_store) {
332                         GtkTreePath *path;
333
334                         gtk_tree_model_get (
335                                         GTK_TREE_MODEL (priv->store), &iter,
336                                         MILK_TASK_MODEL_COLUMN_TASK, &task,
337                                         -1);
338
339                         milk_task_set_title (task,
340                                         rtm_task_get_name (rtm_task));
341
342                         path = gtk_tree_model_get_path (
343                                         GTK_TREE_MODEL (priv->store), &iter);
344                         gtk_tree_model_row_changed (
345                                         GTK_TREE_MODEL (priv->store),
346                                         path, &iter);
347                         gtk_tree_path_free (path);
348
349                         g_object_unref (task);
350
351                 /* Task is new */
352                 } else {
353                         /* FIXME: remove the MilkTask class; just use RtmTask
354                          * directly */
355                         task = milk_task_new (id, rtm_task_get_name (rtm_task),
356                                         /* FIXME: switch priority from int to
357                                          * string */
358                                         g_ascii_strtod (rtm_task_get_priority
359                                                         (rtm_task), NULL));
360
361                         gtk_list_store_append (priv->store, &iter);
362                         gtk_list_store_set (
363                                         priv->store, &iter,
364                                         MILK_TASK_MODEL_COLUMN_TASK, task,
365                                         -1);
366                 }
367         }
368
369         return TRUE;
370 }
371
372 static void
373 auth_notify_cb (MilkAuth      *auth,
374                 GParamSpec    *spec,
375                 MilkTaskModel *model)
376 {
377         if (milk_auth_get_state (auth) == MILK_AUTH_STATE_CONNECTED) {
378                 update_model (model);
379         }
380 }
381
382 static void
383 row_changed_cb (GtkTreeModel  *model,
384                 GtkTreePath   *path,
385                 GtkTreeIter   *iter,
386                 MilkTaskModel *self)
387 {
388         gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, iter);
389 }
390
391 static void
392 row_deleted_cb (GtkTreeModel  *model,
393                 GtkTreePath   *path,
394                 MilkTaskModel *self)
395 {
396         gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
397 }
398
399 static void
400 row_inserted_cb (GtkTreeModel  *model,
401                  GtkTreePath   *path,
402                  GtkTreeIter   *iter,
403                  MilkTaskModel *self)
404 {
405         gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, iter);
406 }
407
408 static void
409 rows_reordered_cb (GtkTreeModel  *model,
410                    GtkTreePath   *path,
411                    GtkTreeIter   *iter,
412                    gint          *new_order,
413                    MilkTaskModel *self)
414 {
415         gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, NULL,
416                         new_order);
417 }
418
419 void
420 milk_task_model_set_auth (MilkTaskModel *model,
421                           MilkAuth      *auth)
422 {
423         MilkTaskModelPrivate *priv;
424
425         g_return_if_fail (model);
426         g_return_if_fail (MILK_IS_TASK_MODEL (model));
427         g_return_if_fail (auth);
428         g_return_if_fail (MILK_IS_AUTH (auth));
429
430         priv = MILK_TASK_MODEL_PRIVATE (model);
431
432         if (priv->auth) {
433                 g_object_unref (priv->auth);
434         }
435         priv->auth = g_object_ref (auth);
436
437         if (priv->update_id) {
438                 g_source_remove (priv->update_id);
439         }
440         priv->update_id = g_timeout_add (MODEL_UPDATE_PERIOD,
441                         (GSourceFunc) update_model, model);
442
443         gtk_list_store_clear (priv->store);
444
445         g_signal_connect (priv->auth, "notify::state",
446                           G_CALLBACK (auth_notify_cb), model);
447         auth_notify_cb (priv->auth, NULL, model);
448 }
449
450 static void
451 milk_task_model_get_property (GObject    *object,
452                               guint       property_id,
453                               GValue     *value,
454                               GParamSpec *pspec)
455 {
456         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
457
458         switch (property_id)
459         {
460                 case PROP_AUTH:
461                         g_value_set_object (value, priv->auth);
462                 break;
463
464                 default:
465                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
466                                         pspec);
467         }
468 }
469
470 static void
471 milk_task_model_set_property (GObject      *object,
472                               guint         property_id,
473                               const GValue *value,
474                               GParamSpec   *pspec)
475 {
476         switch (property_id)
477         {
478                 case PROP_AUTH:
479                         milk_task_model_set_auth (MILK_TASK_MODEL (object),
480                                         g_value_get_object (value));
481                 break;
482
483                 default:
484                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
485                                         pspec);
486         }
487 }
488
489 static void
490 milk_task_model_dispose (GObject *object)
491 {
492         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
493
494         if (priv->auth) {
495                 g_object_unref (priv->auth);
496                 priv->auth = NULL;
497         }
498
499         g_signal_handlers_disconnect_by_func (priv->store, row_changed_cb,
500                         object);
501         g_signal_handlers_disconnect_by_func (priv->store, row_deleted_cb,
502                         object);
503         g_signal_handlers_disconnect_by_func (priv->store, row_inserted_cb,
504                         object);
505         g_signal_handlers_disconnect_by_func (priv->store, rows_reordered_cb,
506                         object);
507
508         if (priv->store) {
509                 g_object_unref (priv->store);
510                 priv->store = NULL;
511         }
512
513         if (priv->update_id) {
514                 g_source_remove (priv->update_id);
515                 priv->update_id = 0;
516         }
517
518         if (priv->tasks) {
519                 g_hash_table_destroy (priv->tasks);
520                 priv->tasks = NULL;
521         }
522
523         G_OBJECT_CLASS (milk_task_model_parent_class)->dispose (object);
524 }
525
526 static void
527 milk_task_model_finalize (GObject *object)
528 {
529         MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
530
531         g_free (priv->last_sync);
532
533         G_OBJECT_CLASS (milk_task_model_parent_class)->finalize (object);
534 }
535
536 static void
537 milk_task_model_class_init (MilkTaskModelClass *klass)
538 {
539         GObjectClass *object_class = G_OBJECT_CLASS (klass);
540
541         g_type_class_add_private (klass, sizeof (MilkTaskModelPrivate));
542
543         object_class->get_property = milk_task_model_get_property;
544         object_class->set_property = milk_task_model_set_property;
545         object_class->dispose = milk_task_model_dispose;
546         object_class->finalize = milk_task_model_finalize;
547
548         g_object_class_install_property
549                 (object_class,
550                  PROP_AUTH,
551                  g_param_spec_object
552                          ("auth",
553                           "Authentication proxy",
554                           "Remember The Milk authentication proxy.",
555                           MILK_TYPE_AUTH,
556                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
557                           G_PARAM_STATIC_STRINGS));
558 }
559
560 static void
561 milk_task_model_init (MilkTaskModel *self)
562 {
563         MilkTaskModelPrivate *priv;
564
565         self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (
566                         self, MILK_TYPE_TASK_MODEL, MilkTaskModelPrivate);
567
568         priv->last_sync = NULL;
569
570         priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
571                         g_free, g_object_unref);
572
573         priv->store = gtk_list_store_new (
574                         MILK_TASK_MODEL_N_COLUMNS, MILK_TYPE_TASK);
575
576         g_signal_connect (priv->store, "row-changed",
577                         G_CALLBACK (row_changed_cb), self);
578
579         g_signal_connect (priv->store, "row-deleted",
580                         G_CALLBACK (row_deleted_cb), self);
581
582         g_signal_connect (priv->store, "row-inserted",
583                         G_CALLBACK (row_inserted_cb), self);
584
585         g_signal_connect (priv->store, "rows-reordered",
586                         G_CALLBACK (rows_reordered_cb), self);
587 }
588
589 static void
590 milk_task_model_tree_model_init (GtkTreeModelIface *iface)
591 {
592         iface->get_flags       = milk_task_model_get_flags;
593         iface->get_n_columns   = milk_task_model_get_n_columns;
594         iface->get_column_type = milk_task_model_get_column_type;
595         iface->get_iter        = milk_task_model_get_iter;
596         iface->get_path        = milk_task_model_get_path;
597         iface->get_value       = milk_task_model_get_value;
598         iface->iter_next       = milk_task_model_iter_next;
599         iface->iter_children   = milk_task_model_iter_children;
600         iface->iter_has_child  = milk_task_model_iter_has_child;
601         iface->iter_n_children = milk_task_model_iter_n_children;
602         iface->iter_nth_child  = milk_task_model_iter_nth_child;
603         iface->iter_parent     = milk_task_model_iter_parent;
604 }
605
606 MilkTaskModel*
607 milk_task_model_new (MilkAuth *auth)
608 {
609         return g_object_new (MILK_TYPE_TASK_MODEL, "auth", auth, NULL);
610 }