1 /* gtd-task-list-eds.c
2  *
3  * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #define G_LOG_DOMAIN "GtdTaskListEds"
20 
21 #include "e-source-gnome-todo.h"
22 #include "gtd-debug.h"
23 #include "gtd-eds-autoptr.h"
24 #include "gtd-provider-eds.h"
25 #include "gtd-task-eds.h"
26 #include "gtd-task-list-eds.h"
27 
28 #include <glib/gi18n.h>
29 
30 struct _GtdTaskListEds
31 {
32   GtdTaskList         parent;
33 
34   ECalClient         *client;
35   ECalClientView     *client_view;
36   ESource            *source;
37 
38   GCancellable       *cancellable;
39 };
40 
41 typedef struct
42 {
43   GtdProvider        *provider;
44   ESource            *source;
45   ECalClient         *client;
46 } NewTaskListData;
47 
48 static void          on_client_objects_modified_for_migration_cb (GObject            *object,
49                                                                   GAsyncResult       *result,
50                                                                   gpointer            user_data);
51 
52 G_DEFINE_TYPE (GtdTaskListEds, gtd_task_list_eds, GTD_TYPE_TASK_LIST)
53 
54 enum
55 {
56   PROP_0,
57   PROP_CLIENT,
58   PROP_SOURCE,
59   N_PROPS
60 };
61 
62 
63 /*
64  * Auxiliary methods
65  */
66 
67 static void
new_task_list_data_free(gpointer data)68 new_task_list_data_free (gpointer data)
69 {
70   NewTaskListData *list_data = data;
71 
72   if (!list_data)
73     return;
74 
75   g_clear_object (&list_data->provider);
76   g_clear_object (&list_data->source);
77   g_clear_object (&list_data->client);
78   g_free (list_data);
79 }
80 
81 static void
update_changed_tasks(GtdTaskListEds * self,GHashTable * changed_tasks)82 update_changed_tasks (GtdTaskListEds *self,
83                       GHashTable     *changed_tasks)
84 {
85   g_autoptr (GSList) components = NULL;
86   GHashTableIter iter;
87   GtdProvider *provider;
88   GtdTask *task;
89   guint n_changed_tasks;
90 
91   GTD_ENTRY;
92 
93   n_changed_tasks = g_hash_table_size (changed_tasks);
94   provider = gtd_task_list_get_provider (GTD_TASK_LIST (self));
95 
96   /* Nothing changed, list is ready */
97   if (n_changed_tasks == 0)
98     {
99       gtd_object_pop_loading (GTD_OBJECT (provider));
100       g_signal_emit_by_name (provider, "list-added", self);
101       GTD_RETURN ();
102     }
103 
104   GTD_TRACE_MSG ("%u task(s) changed", n_changed_tasks);
105 
106   g_hash_table_iter_init (&iter, changed_tasks);
107   while (g_hash_table_iter_next (&iter, (gpointer *) &task, NULL))
108     {
109       ICalComponent *ical_comp;
110       ECalComponent *comp;
111 
112       comp = gtd_task_eds_get_component (GTD_TASK_EDS (task));
113       ical_comp = e_cal_component_get_icalcomponent (comp);
114 
115       components = g_slist_prepend (components, ical_comp);
116     }
117 
118   e_cal_client_modify_objects (self->client,
119                                components,
120                                E_CAL_OBJ_MOD_THIS,
121                                E_CAL_OPERATION_FLAG_NONE,
122                                self->cancellable,
123                                on_client_objects_modified_for_migration_cb,
124                                self);
125 
126   GTD_EXIT;
127 }
128 
129 static void
migrate_to_v1(GtdTaskListEds * self,GHashTable * changed_tasks)130 migrate_to_v1 (GtdTaskListEds *self,
131                GHashTable     *changed_tasks)
132 {
133   GListModel *model;
134   guint n_tasks;
135   guint i;
136 
137   model = G_LIST_MODEL (self);
138   n_tasks = g_list_model_get_n_items (model);
139 
140   for (i = 0; i < n_tasks; i++)
141     {
142       g_autoptr (GtdTask) task = NULL;
143 
144       task = g_list_model_get_item (model, i);
145 
146       /* Don't notify to avoid carpet-bombing GtdTaskList */
147       g_object_freeze_notify (G_OBJECT (task));
148 
149       gtd_task_set_position (task, i);
150 
151       g_hash_table_add (changed_tasks, task);
152     }
153 
154   for (i = 0; i < n_tasks; i++)
155     {
156       g_autoptr (GtdTask) task = NULL;
157 
158       task = g_list_model_get_item (model, i);
159       g_object_thaw_notify (G_OBJECT (task));
160     }
161 }
162 
163 struct
164 {
165   guint api_version;
166   void  (* migrate) (GtdTaskListEds *self,
167                      GHashTable     *changed_tasks);
168 }
169 migration_vtable[] =
170 {
171   { 0, migrate_to_v1 },
172 };
173 
174 static void
maybe_migrate_todo_api_version(GtdTaskListEds * self)175 maybe_migrate_todo_api_version (GtdTaskListEds *self)
176 {
177   g_autoptr (GHashTable) changed_tasks = NULL;
178   ESourceGnomeTodo *gnome_todo_extension;
179   gboolean api_version_changed;
180   guint api_version;
181   guint i;
182 
183   GTD_ENTRY;
184 
185   /*
186    * Ensure the type so that it is available for introspection when
187    * calling e_source_get_extension().
188    */
189   g_type_ensure (E_TYPE_SOURCE_GNOME_TODO);
190 
191   api_version_changed = FALSE;
192   gnome_todo_extension = e_source_get_extension (self->source, E_SOURCE_EXTENSION_GNOME_TODO);
193   api_version = e_source_gnome_todo_get_api_version (gnome_todo_extension);
194   changed_tasks = g_hash_table_new (g_direct_hash, g_direct_equal);
195 
196   g_debug ("%s: GNOME To Do API version %u",
197            gtd_task_list_get_name (GTD_TASK_LIST (self)),
198            api_version);
199 
200   for (i = 0; i < G_N_ELEMENTS (migration_vtable); i++)
201     {
202       guint new_api_version = i + 1;
203 
204       if (api_version > migration_vtable[i].api_version)
205         continue;
206 
207       g_debug ("  Migrating task list to GNOME To Do API v%u", new_api_version);
208 
209       migration_vtable[i].migrate (self, changed_tasks);
210 
211       e_source_gnome_todo_set_api_version (gnome_todo_extension, new_api_version);
212       api_version_changed = TRUE;
213     }
214 
215   if (api_version_changed)
216     {
217       g_debug ("Saving new API version");
218 
219       e_source_write (self->source, NULL, NULL, NULL);
220     }
221 
222   update_changed_tasks (self, changed_tasks);
223 
224   GTD_EXIT;
225 }
226 
227 
228 /*
229  * Callbacks
230  */
231 
232 static gboolean
new_task_list_in_idle_cb(gpointer data)233 new_task_list_in_idle_cb (gpointer data)
234 {
235   g_autoptr (GtdTaskListEds) list_eds = NULL;
236   g_autoptr (GTask) task = data;
237   NewTaskListData *list_data;
238 
239   list_data = g_task_get_task_data (task);
240   list_eds = g_object_new (GTD_TYPE_TASK_LIST_EDS,
241                            "provider", list_data->provider,
242                            "source", list_data->source,
243                            "client", list_data->client,
244                            NULL);
245 
246   g_task_return_pointer (task, g_steal_pointer (&list_eds), NULL);
247 
248   return G_SOURCE_REMOVE;
249 }
250 
251 static void
on_client_objects_modified_for_migration_cb(GObject * object,GAsyncResult * result,gpointer user_data)252 on_client_objects_modified_for_migration_cb (GObject      *object,
253                                              GAsyncResult *result,
254                                              gpointer      user_data)
255 {
256   g_autoptr (GError) error = NULL;
257   GtdProvider *provider;
258   GtdTaskListEds *self;
259 
260   GTD_ENTRY;
261 
262   self = GTD_TASK_LIST_EDS (user_data);
263   provider = gtd_task_list_get_provider (GTD_TASK_LIST (self));
264 
265   e_cal_client_modify_objects_finish (self->client, result, &error);
266 
267   if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
268     g_warning ("Error migrating tasks to new API version: %s", error->message);
269 
270   gtd_object_pop_loading (GTD_OBJECT (provider));
271   g_signal_emit_by_name (provider, "list-added", self);
272 
273   GTD_EXIT;
274 }
275 
276 static void
on_view_objects_added_cb(ECalClientView * view,const GSList * objects,GtdTaskList * self)277 on_view_objects_added_cb (ECalClientView *view,
278                           const GSList   *objects,
279                           GtdTaskList    *self)
280 {
281   g_autoptr (ECalClient) client = NULL;
282   GSList *l;
283 
284   GTD_ENTRY;
285 
286   client = e_cal_client_view_ref_client (view);
287 
288   for (l = (GSList*) objects; l; l = l->next)
289     {
290       g_autoptr (ECalComponent) component = NULL;
291       GtdTask *task;
292       const gchar *uid;
293 
294       component = e_cal_component_new_from_icalcomponent (i_cal_component_clone (l->data));
295       uid = e_cal_component_get_uid (component);
296 
297       task = gtd_task_list_get_task_by_id (self, uid);
298 
299       /* If the task already exists, we must instead update its component */
300       if (task)
301         {
302           gtd_task_eds_set_component (GTD_TASK_EDS (task), component);
303 
304           gtd_task_list_update_task (self, task);
305 
306           GTD_TRACE_MSG ("Updated task '%s' to tasklist '%s'",
307                          gtd_task_get_title (task),
308                          gtd_task_list_get_name (self));
309 
310           continue;
311         }
312 
313       /* Add the new task */
314       task = gtd_task_eds_new (component);
315       gtd_task_set_list (task, self);
316 
317       gtd_task_list_add_task (self, task);
318 
319       GTD_TRACE_MSG ("Added task '%s' (%s) to tasklist '%s'",
320                      gtd_task_get_title (task),
321                      gtd_object_get_uid (GTD_OBJECT (task)),
322                      gtd_task_list_get_name (self));
323     }
324 
325   GTD_EXIT;
326 }
327 
328 static void
on_view_objects_modified_cb(ECalClientView * view,const GSList * objects,GtdTaskList * self)329 on_view_objects_modified_cb (ECalClientView *view,
330                              const GSList   *objects,
331                              GtdTaskList    *self)
332 {
333   g_autoptr (ECalClient) client = NULL;
334   GSList *l;
335 
336   GTD_ENTRY;
337 
338   client = e_cal_client_view_ref_client (view);
339 
340   for (l = (GSList*) objects; l; l = l->next)
341     {
342       g_autoptr (ECalComponent) component = NULL;
343       GtdTask *task;
344       const gchar *uid;
345 
346       component = e_cal_component_new_from_icalcomponent (i_cal_component_clone (l->data));
347       uid = e_cal_component_get_uid (component);
348 
349       task = gtd_task_list_get_task_by_id (self, uid);
350 
351       if (!task)
352         continue;
353 
354       gtd_task_eds_set_component (GTD_TASK_EDS (task), component);
355 
356       GTD_TRACE_MSG ("Updated task '%s' from tasklist '%s'",
357                      gtd_task_get_title (GTD_TASK (task)),
358                      gtd_task_list_get_name (self));
359     }
360 
361   GTD_EXIT;
362 }
363 
364 static void
on_view_objects_removed_cb(ECalClientView * view,const GSList * uids,GtdTaskList * self)365 on_view_objects_removed_cb (ECalClientView *view,
366                             const GSList   *uids,
367                             GtdTaskList    *self)
368 {
369   GSList *l;
370 
371   GTD_ENTRY;
372 
373   for (l = (GSList*) uids; l; l = l->next)
374     {
375       ECalComponentId *id;
376       GtdTask *task;
377 
378       id = l->data;
379       task = gtd_task_list_get_task_by_id (self, e_cal_component_id_get_uid (id));
380 
381       if (!task)
382         continue;
383 
384       gtd_task_list_remove_task (self, task);
385 
386       GTD_TRACE_MSG ("Removed task '%s' from tasklist '%s'",
387                      gtd_task_get_title (task),
388                      gtd_task_list_get_name (self));
389     }
390 
391   GTD_EXIT;
392 }
393 
394 static void
on_view_completed_cb(ECalClientView * view,const GError * error,GtdTaskList * self)395 on_view_completed_cb (ECalClientView *view,
396                       const GError   *error,
397                       GtdTaskList    *self)
398 {
399   gtd_object_pop_loading (GTD_OBJECT (gtd_manager_get_default ()));
400   gtd_object_pop_loading (GTD_OBJECT (self));
401 
402   if (error)
403     {
404       g_warning ("Error fetching tasks from list: %s", error->message);
405 
406       gtd_manager_emit_error_message (gtd_manager_get_default (),
407                                       _("Error fetching tasks from list"),
408                                       error->message,
409                                       NULL,
410                                       NULL);
411       return;
412     }
413 
414   maybe_migrate_todo_api_version (GTD_TASK_LIST_EDS (self));
415 }
416 
417 static void
on_client_view_acquired_cb(GObject * client,GAsyncResult * result,gpointer user_data)418 on_client_view_acquired_cb (GObject      *client,
419                             GAsyncResult *result,
420                             gpointer      user_data)
421 {
422   g_autoptr (GError) error = NULL;
423   GtdTaskListEds *self;
424 
425   self = GTD_TASK_LIST_EDS (user_data);
426 
427   e_cal_client_get_view_finish (E_CAL_CLIENT (client), result, &self->client_view, &error);
428 
429   if (error)
430     {
431       g_warning ("Error fetching tasks from list: %s", error->message);
432 
433       gtd_manager_emit_error_message (gtd_manager_get_default (),
434                                       _("Error fetching tasks from list"),
435                                       error->message,
436                                       NULL,
437                                       NULL);
438       return;
439     }
440 
441   g_debug ("ECalClientView for tasklist '%s' successfully acquired",
442            gtd_task_list_get_name (GTD_TASK_LIST (self)));
443 
444   g_signal_connect (self->client_view, "objects-added", G_CALLBACK (on_view_objects_added_cb), self);
445   g_signal_connect (self->client_view, "objects-removed", G_CALLBACK (on_view_objects_removed_cb), self);
446   g_signal_connect (self->client_view, "objects-modified", G_CALLBACK (on_view_objects_modified_cb), self);
447   g_signal_connect (self->client_view, "complete", G_CALLBACK (on_view_completed_cb), self);
448 
449   gtd_object_push_loading (GTD_OBJECT (self));
450 
451   e_cal_client_view_start (self->client_view, &error);
452 
453   if (error)
454     {
455       g_warning ("Error starting view: %s", error->message);
456 
457       gtd_manager_emit_error_message (gtd_manager_get_default (),
458                                       _("Error fetching tasks from list"),
459                                       error->message,
460                                       NULL,
461                                       NULL);
462     }
463 }
464 
465 static void
on_source_removable_changed_cb(GtdTaskListEds * list)466 on_source_removable_changed_cb (GtdTaskListEds *list)
467 {
468   gtd_task_list_set_is_removable (GTD_TASK_LIST (list),
469                                   e_source_get_removable (list->source) ||
470                                   e_source_get_remote_deletable (list->source));
471 }
472 
473 static void
on_source_selectable_selected_changed_cb(ESourceSelectable * selectable,GParamSpec * pspec,GtdTaskListEds * self)474 on_source_selectable_selected_changed_cb (ESourceSelectable *selectable,
475                                           GParamSpec        *pspec,
476                                           GtdTaskListEds    *self)
477 {
478   g_debug ("%s (%s): ESourceSelectable:selected changed, notifying...",
479            e_source_get_uid (self->source),
480            e_source_get_display_name (self->source));
481 
482   g_object_notify (G_OBJECT (self), "archived");
483 }
484 
485 static void
on_save_task_list_finished_cb(GObject * source,GAsyncResult * result,gpointer user_data)486 on_save_task_list_finished_cb (GObject      *source,
487                                GAsyncResult *result,
488                                gpointer      user_data)
489 {
490   GtdTaskListEds *list;
491   GError *error;
492 
493   list = user_data;
494   error = NULL;
495 
496   gtd_object_pop_loading (GTD_OBJECT (list));
497 
498   e_source_write_finish (E_SOURCE (source), result, &error);
499 
500   if (error)
501     {
502       g_warning ("%s: %s: %s",
503                  G_STRFUNC,
504                  "Error saving task list",
505                  error->message);
506       g_clear_error (&error);
507     }
508 }
509 
510 static void
save_task_list(GtdTaskListEds * list)511 save_task_list (GtdTaskListEds *list)
512 {
513   if (e_source_get_writable (list->source))
514     {
515       if (!list->cancellable)
516         list->cancellable = g_cancellable_new ();
517 
518       gtd_object_push_loading (GTD_OBJECT (list));
519 
520       e_source_write (list->source,
521                       list->cancellable,
522                       on_save_task_list_finished_cb,
523                       list);
524     }
525 }
526 
527 static gboolean
color_to_string(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer user_data)528 color_to_string (GBinding     *binding,
529                  const GValue *from_value,
530                  GValue       *to_value,
531                  gpointer      user_data)
532 {
533   GdkRGBA *color;
534   gchar *color_str;
535 
536   color = g_value_get_boxed (from_value);
537   color_str = gdk_rgba_to_string (color);
538 
539   g_value_set_string (to_value, color_str);
540 
541   g_free (color_str);
542 
543   return TRUE;
544 }
545 
546 static gboolean
string_to_color(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer user_data)547 string_to_color (GBinding     *binding,
548                  const GValue *from_value,
549                  GValue       *to_value,
550                  gpointer      user_data)
551 {
552   GdkRGBA color;
553 
554   if (!gdk_rgba_parse (&color, g_value_get_string (from_value)))
555     gdk_rgba_parse (&color, "#ffffff"); /* calendar default colour */
556 
557   g_value_set_boxed (to_value, &color);
558 
559   return TRUE;
560 }
561 
562 
563 /*
564  * GtdTaskList overrides
565  */
566 
567 static gboolean
gtd_task_list_eds_get_archived(GtdTaskList * list)568 gtd_task_list_eds_get_archived (GtdTaskList *list)
569 {
570   ESourceSelectable *selectable;
571   GtdTaskListEds *self;
572 
573   self = GTD_TASK_LIST_EDS (list);
574   selectable = e_source_get_extension (self->source, E_SOURCE_EXTENSION_TASK_LIST);
575 
576   return !e_source_selectable_get_selected (selectable);
577 }
578 
579 static void
gtd_task_list_eds_set_archived(GtdTaskList * list,gboolean archived)580 gtd_task_list_eds_set_archived (GtdTaskList *list,
581                                 gboolean     archived)
582 {
583   ESourceSelectable *selectable;
584   GtdTaskListEds *self;
585 
586   GTD_ENTRY;
587 
588   self = GTD_TASK_LIST_EDS (list);
589   selectable = e_source_get_extension (self->source, E_SOURCE_EXTENSION_TASK_LIST);
590 
591   g_signal_handlers_block_by_func (selectable, on_source_selectable_selected_changed_cb, self);
592 
593   e_source_selectable_set_selected (selectable, !archived);
594 
595   g_signal_handlers_unblock_by_func (selectable, on_source_selectable_selected_changed_cb, self);
596 
597   GTD_EXIT;
598 }
599 
600 
601 /*
602  * GtdObject overrides
603  */
604 
605 static const gchar*
gtd_task_list_eds_get_uid(GtdObject * object)606 gtd_task_list_eds_get_uid (GtdObject *object)
607 {
608   GtdTaskListEds *self = GTD_TASK_LIST_EDS (object);
609 
610   return e_source_get_uid (self->source);
611 }
612 
613 static void
gtd_task_list_eds_set_uid(GtdObject * object,const gchar * uid)614 gtd_task_list_eds_set_uid (GtdObject   *object,
615                            const gchar *uid)
616 {
617   g_assert_not_reached ();
618 }
619 
620 
621 /*
622  * GObject overrides
623  */
624 
625 static void
gtd_task_list_eds_finalize(GObject * object)626 gtd_task_list_eds_finalize (GObject *object)
627 {
628   GtdTaskListEds *self = GTD_TASK_LIST_EDS (object);
629 
630   g_cancellable_cancel (self->cancellable);
631 
632   g_clear_object (&self->cancellable);
633   g_clear_object (&self->client);
634   g_clear_object (&self->client_view);
635   g_clear_object (&self->source);
636 
637   G_OBJECT_CLASS (gtd_task_list_eds_parent_class)->finalize (object);
638 }
639 
640 static void
gtd_task_list_eds_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)641 gtd_task_list_eds_get_property (GObject    *object,
642                                 guint       prop_id,
643                                 GValue     *value,
644                                 GParamSpec *pspec)
645 {
646   GtdTaskListEds *self = GTD_TASK_LIST_EDS (object);
647 
648   switch (prop_id)
649     {
650     case PROP_CLIENT:
651       g_value_set_object (value, self->client);
652       break;
653 
654     case PROP_SOURCE:
655       g_value_set_object (value, self->source);
656       break;
657 
658     default:
659       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
660     }
661 }
662 
663 static void
gtd_task_list_eds_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)664 gtd_task_list_eds_set_property (GObject      *object,
665                                 guint         prop_id,
666                                 const GValue *value,
667                                 GParamSpec   *pspec)
668 {
669   GtdTaskListEds *self = GTD_TASK_LIST_EDS (object);
670 
671   switch (prop_id)
672     {
673     case PROP_CLIENT:
674       if (!g_set_object (&self->client, g_value_get_object (value)) || !self->client)
675         return;
676 
677       e_cal_client_get_view (self->client,
678                              "#t",
679                              self->cancellable,
680                              on_client_view_acquired_cb,
681                              self);
682 
683       g_object_notify (object, "client");
684       break;
685 
686     case PROP_SOURCE:
687       gtd_task_list_eds_set_source (self, g_value_get_object (value));
688       break;
689 
690     default:
691       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
692     }
693 }
694 
695 static void
gtd_task_list_eds_class_init(GtdTaskListEdsClass * klass)696 gtd_task_list_eds_class_init (GtdTaskListEdsClass *klass)
697 {
698   GObjectClass *object_class = G_OBJECT_CLASS (klass);
699   GtdObjectClass *gtd_object_class = GTD_OBJECT_CLASS (klass);
700   GtdTaskListClass *task_list_class = GTD_TASK_LIST_CLASS (klass);
701 
702   task_list_class->get_archived = gtd_task_list_eds_get_archived;
703   task_list_class->set_archived = gtd_task_list_eds_set_archived;
704 
705   gtd_object_class->get_uid = gtd_task_list_eds_get_uid;
706   gtd_object_class->set_uid = gtd_task_list_eds_set_uid;
707 
708   object_class->finalize = gtd_task_list_eds_finalize;
709   object_class->get_property = gtd_task_list_eds_get_property;
710   object_class->set_property = gtd_task_list_eds_set_property;
711 
712 
713   /**
714    * GtdTaskListEds::client:
715    *
716    * The #ECalClient of this #GtdTaskListEds
717    */
718   g_object_class_install_property (object_class,
719                                    PROP_CLIENT,
720                                    g_param_spec_object ("client",
721                                                         "ECalClient of this list",
722                                                         "The ECalClient of this list",
723                                                         E_TYPE_CAL_CLIENT,
724                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
725 
726   /**
727    * GtdTaskListEds::source:
728    *
729    * The #ESource of this #GtdTaskListEds
730    */
731   g_object_class_install_property (object_class,
732                                    PROP_SOURCE,
733                                    g_param_spec_object ("source",
734                                                         "ESource of this list",
735                                                         "The ESource of this list",
736                                                         E_TYPE_SOURCE,
737                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
738 }
739 
740 static void
gtd_task_list_eds_init(GtdTaskListEds * self)741 gtd_task_list_eds_init (GtdTaskListEds *self)
742 {
743 }
744 
745 void
gtd_task_list_eds_new(GtdProvider * provider,ESource * source,ECalClient * client,GAsyncReadyCallback callback,GCancellable * cancellable,gpointer user_data)746 gtd_task_list_eds_new (GtdProvider         *provider,
747                        ESource             *source,
748                        ECalClient          *client,
749                        GAsyncReadyCallback  callback,
750                        GCancellable        *cancellable,
751                        gpointer             user_data)
752 {
753   g_autoptr (GTask) task = NULL;
754   NewTaskListData *data;
755 
756   data = g_new (NewTaskListData, 1);
757   data->provider = g_object_ref (provider);
758   data->source = g_object_ref (source);
759   data->client = g_object_ref (client);
760 
761   task = g_task_new (NULL, cancellable, callback, user_data);
762   g_task_set_task_data (task, data, new_task_list_data_free);
763   g_task_set_source_tag (task, gtd_task_list_eds_new);
764 
765   g_idle_add (new_task_list_in_idle_cb, g_steal_pointer (&task));
766 }
767 
768 GtdTaskListEds*
gtd_task_list_eds_new_finish(GAsyncResult * result,GError ** error)769 gtd_task_list_eds_new_finish (GAsyncResult  *result,
770                               GError       **error)
771 {
772   g_return_val_if_fail (G_IS_TASK (result), NULL);
773   g_return_val_if_fail (!error || !*error, NULL);
774 
775   return g_task_propagate_pointer (G_TASK (result), error);
776 }
777 
778 ESource*
gtd_task_list_eds_get_source(GtdTaskListEds * list)779 gtd_task_list_eds_get_source (GtdTaskListEds *list)
780 {
781   g_return_val_if_fail (GTD_IS_TASK_LIST_EDS (list), NULL);
782 
783   return list->source;
784 }
785 
786 void
gtd_task_list_eds_set_source(GtdTaskListEds * self,ESource * source)787 gtd_task_list_eds_set_source (GtdTaskListEds *self,
788                               ESource        *source)
789 {
790   ESourceSelectable *selectable;
791   ESourceRefresh *refresh;
792   GdkRGBA color;
793   gboolean is_inbox;
794 
795   g_return_if_fail (GTD_IS_TASK_LIST_EDS (self));
796 
797   if (!g_set_object (&self->source, source))
798     return;
799 
800   is_inbox = g_str_equal (e_source_get_uid (source), GTD_PROVIDER_EDS_INBOX_ID);
801 
802   /* Setup color */
803   selectable = E_SOURCE_SELECTABLE (e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST));
804 
805   if (!gdk_rgba_parse (&color, e_source_selectable_get_color (selectable)))
806     gdk_rgba_parse (&color, "#ffffff"); /* calendar default color */
807 
808   gtd_task_list_set_color (GTD_TASK_LIST (self), &color);
809 
810   g_object_bind_property_full (self,
811                                "color",
812                                selectable,
813                                "color",
814                                G_BINDING_BIDIRECTIONAL,
815                                color_to_string,
816                                string_to_color,
817                                self,
818                                NULL);
819 
820   g_signal_connect_object (selectable,
821                            "notify::selected",
822                            G_CALLBACK (on_source_selectable_selected_changed_cb),
823                            self,
824                            0);
825 
826   /* Setup tasklist name */
827   if (is_inbox)
828     gtd_task_list_set_name (GTD_TASK_LIST (self), _("Inbox"));
829   else
830     gtd_task_list_set_name (GTD_TASK_LIST (self), e_source_get_display_name (source));
831 
832   g_object_bind_property (source,
833                           "display-name",
834                           self,
835                           "name",
836                           G_BINDING_BIDIRECTIONAL);
837 
838   /* Save the task list every time something changes */
839   g_signal_connect_swapped (source,
840                             "notify",
841                             G_CALLBACK (save_task_list),
842                             self);
843 
844   /* Update ::is-removable property */
845   gtd_task_list_set_is_removable (GTD_TASK_LIST (self),
846                                   e_source_get_removable (source) ||
847                                   e_source_get_remote_deletable (source));
848 
849   g_signal_connect_swapped (source,
850                             "notify::removable",
851                             G_CALLBACK (on_source_removable_changed_cb),
852                             self);
853 
854   g_signal_connect_swapped (source,
855                             "notify::remote-deletable",
856                             G_CALLBACK (on_source_removable_changed_cb),
857                             self);
858 
859   /* Refresh timeout */
860   refresh = e_source_get_extension (source, E_SOURCE_EXTENSION_REFRESH);
861   e_source_refresh_set_enabled (refresh, TRUE);
862   e_source_refresh_set_interval_minutes (refresh, 5);
863 
864   e_source_write (source, NULL, NULL, NULL);
865 
866   g_object_notify (G_OBJECT (self), "source");
867 }
868 
869 ECalClient*
gtd_task_list_eds_get_client(GtdTaskListEds * self)870 gtd_task_list_eds_get_client (GtdTaskListEds *self)
871 {
872   g_return_val_if_fail (GTD_IS_TASK_LIST_EDS (self), NULL);
873 
874   return self->client;
875 }
876