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