1 /* gcal-calendar-monitor.c
2  *
3  * Copyright 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  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "GcalCalendarMonitor"
22 
23 #include "gcal-calendar-monitor.h"
24 #include "gcal-date-time-utils.h"
25 #include "gcal-debug.h"
26 #include "gcal-event.h"
27 
28 #include <gio/gio.h>
29 #include <libecal/libecal.h>
30 
31 typedef struct
32 {
33   GcalCalendarMonitor *monitor;
34   GcalEvent           *event;
35   gchar               *event_id;
36   gboolean             complete;
37 } IdleData;
38 
39 typedef enum
40 {
41   INVALID_EVENT,
42   CREATE_VIEW,
43   REMOVE_VIEW,
44   RANGE_UPDATED,
45   FILTER_UPDATED,
46   QUIT,
47 } MonitorThreadEvent;
48 
49 struct _GcalCalendarMonitor
50 {
51   GObject             parent;
52 
53   GThread            *thread;
54   GCancellable       *cancellable;
55   GMainContext       *thread_context;
56   GMainContext       *main_context;
57 
58   GHashTable         *events; /* gchar* -> GcalEvent* */
59 
60   GAsyncQueue        *messages;
61   GcalCalendar       *calendar;
62   gboolean            complete;
63 
64   /*
65    * These fields are only accessed on the monitor thread, and
66    * never on the main thread.
67    */
68   struct {
69     gboolean          populated;
70     GHashTable       *events_to_add;
71     ECalClientView   *view;
72   } monitor_thread;
73 
74   /*
75    * Accessing any of the fields below must be happen inside
76    * a locked muxed.
77    */
78   struct {
79     GMutex            mutex;
80     GcalRange        *range;
81     gchar            *filter;
82   } shared;
83 };
84 
85 static gboolean      add_event_to_timeline_in_idle_cb            (gpointer           user_data);
86 static gboolean      update_event_in_idle_cb                     (gpointer           user_data);
87 static gboolean      remove_event_from_timeline_in_idle_cb       (gpointer           user_data);
88 static gboolean      complete_in_idle_cb                         (gpointer           user_data);
89 
90 G_DEFINE_TYPE (GcalCalendarMonitor, gcal_calendar_monitor, G_TYPE_OBJECT)
91 
92 enum
93 {
94   EVENT_ADDED,
95   EVENT_UPDATED,
96   EVENT_REMOVED,
97   N_SIGNALS,
98 };
99 
100 enum
101 {
102   PROP_0,
103   PROP_CALENDAR,
104   PROP_COMPLETE,
105   N_PROPS
106 };
107 
108 static gulong signals[N_SIGNALS] = { 0, };
109 static GParamSpec *properties [N_PROPS] = { NULL, };
110 
111 /*
112  * Threads
113  *
114  * These methods must *never* be executed in the main thread.
115  */
116 
117 static gchar*
build_subscriber_filter(GcalRange * range,const gchar * filter)118 build_subscriber_filter (GcalRange   *range,
119                          const gchar *filter)
120 {
121   g_autoptr (GDateTime) inclusive_range_end = NULL;
122   g_autoptr (GDateTime) utc_range_start = NULL;
123   g_autoptr (GDateTime) utc_range_end = NULL;
124   g_autoptr (GDateTime) range_start = NULL;
125   g_autoptr (GDateTime) range_end = NULL;
126   g_autofree gchar *start_str = NULL;
127   g_autofree gchar *end_str = NULL;
128   g_autofree gchar *result = NULL;
129 
130   /*
131    * XXX: E-D-S ISO8601 parser is incomplete and doesn't accept the output of
132    * g_date_time_format_iso8601().
133    */
134 
135   range_start = gcal_range_get_start (range);
136   utc_range_start = g_date_time_to_utc (range_start);
137   start_str = g_date_time_format (utc_range_start, "%Y%m%dT%H%M%SZ");
138 
139   range_end = gcal_range_get_end (range);
140   inclusive_range_end = g_date_time_add_seconds (range_end, -1);
141   utc_range_end = g_date_time_to_utc (inclusive_range_end);
142   end_str = g_date_time_format (utc_range_end, "%Y%m%dT%H%M%SZ");
143 
144   if (filter)
145     {
146       result = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") (make-time \"%s\")) %s)",
147                                 start_str,
148                                 end_str,
149                                 filter);
150     }
151   else
152     {
153       result = g_strdup_printf ("(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))",
154                                 start_str,
155                                 end_str);
156     }
157 
158   return g_steal_pointer (&result);
159 }
160 
161 static void
maybe_init_event_arrays(GcalCalendarMonitor * self)162 maybe_init_event_arrays (GcalCalendarMonitor *self)
163 {
164   if (self->monitor_thread.populated)
165     return;
166 
167   if (!self->monitor_thread.events_to_add)
168     {
169       self->monitor_thread.events_to_add = g_hash_table_new_full (g_str_hash,
170                                                                   g_str_equal,
171                                                                   g_free,
172                                                                   g_object_unref);
173     }
174 }
175 
176 static GcalRange*
get_monitor_ranges(GcalCalendarMonitor * self)177 get_monitor_ranges (GcalCalendarMonitor  *self)
178 {
179   g_autoptr (GcalRange) range = NULL;
180 
181   g_mutex_lock (&self->shared.mutex);
182   range = gcal_range_copy (self->shared.range);
183   g_mutex_unlock (&self->shared.mutex);
184 
185   return g_steal_pointer (&range);
186 }
187 
188 static void
idle_data_free(IdleData * data)189 idle_data_free (IdleData *data)
190 {
191   g_clear_object (&data->monitor);
192   g_clear_object (&data->event);
193   g_clear_pointer (&data->event_id, g_free);
194   g_free (data);
195 }
196 
197 static void
add_event_in_idle(GcalCalendarMonitor * self,GcalEvent * event)198 add_event_in_idle (GcalCalendarMonitor *self,
199                    GcalEvent        *event)
200 {
201   IdleData *idle_data;
202 
203   idle_data = g_new0 (IdleData, 1);
204   idle_data->monitor = g_object_ref (self);
205   idle_data->event = g_object_ref (event);
206 
207   g_main_context_invoke_full (self->main_context,
208                               G_PRIORITY_DEFAULT_IDLE,
209                               add_event_to_timeline_in_idle_cb,
210                               idle_data,
211                               (GDestroyNotify) idle_data_free);
212 }
213 
214 static void
update_event_in_idle(GcalCalendarMonitor * self,GcalEvent * event)215 update_event_in_idle (GcalCalendarMonitor *self,
216                       GcalEvent           *event)
217 {
218   IdleData *idle_data;
219 
220   idle_data = g_new0 (IdleData, 1);
221   idle_data->monitor = g_object_ref (self);
222   idle_data->event = g_object_ref (event);
223 
224   g_main_context_invoke_full (self->main_context,
225                               G_PRIORITY_DEFAULT_IDLE,
226                               update_event_in_idle_cb,
227                               idle_data,
228                               (GDestroyNotify) idle_data_free);
229 }
230 
231 static void
remove_event_in_idle(GcalCalendarMonitor * self,const gchar * event_id)232 remove_event_in_idle (GcalCalendarMonitor *self,
233                       const gchar         *event_id)
234 {
235   IdleData *idle_data;
236 
237   idle_data = g_new0 (IdleData, 1);
238   idle_data->monitor = g_object_ref (self);
239   idle_data->event_id = g_strdup (event_id);
240 
241   g_main_context_invoke_full (self->main_context,
242                               G_PRIORITY_DEFAULT_IDLE,
243                               remove_event_from_timeline_in_idle_cb,
244                               idle_data,
245                               (GDestroyNotify) idle_data_free);
246 }
247 
248 static void
set_complete_in_idle(GcalCalendarMonitor * self,gboolean complete)249 set_complete_in_idle (GcalCalendarMonitor *self,
250                       gboolean             complete)
251 {
252   IdleData *idle_data;
253 
254   idle_data = g_new0 (IdleData, 1);
255   idle_data->monitor = g_object_ref (self);
256   idle_data->complete = complete;
257 
258   g_main_context_invoke_full (self->main_context,
259                               G_PRIORITY_DEFAULT_IDLE,
260                               complete_in_idle_cb,
261                               idle_data,
262                               (GDestroyNotify) idle_data_free);
263 }
264 
265 typedef struct
266 {
267   GcalCalendarMonitor *monitor;
268   GPtrArray           *expanded_events;
269 } GenerateRecurrencesData;
270 
271 static gboolean
client_instance_generated_cb(ICalComponent * icomponent,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)272 client_instance_generated_cb (ICalComponent  *icomponent,
273                               ICalTime       *instance_start,
274                               ICalTime       *instance_end,
275                               gpointer        user_data,
276                               GCancellable   *cancellable,
277                               GError        **error)
278 {
279   g_autoptr (GcalEvent) event = NULL;
280   g_autoptr (GError) local_error = NULL;
281   GenerateRecurrencesData *data;
282   GcalCalendarMonitor *self;
283   ECalComponent *ecomponent;
284 
285   data = user_data;
286   self = data->monitor;
287 
288   if (g_cancellable_set_error_if_cancelled (cancellable, error))
289     return FALSE;
290 
291   ecomponent = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomponent));
292   if (!ecomponent)
293     return TRUE;
294 
295   event = gcal_event_new (self->calendar, ecomponent, &local_error);
296   g_clear_object (&ecomponent);
297   if (local_error)
298     {
299       g_propagate_error (error, local_error);
300       return TRUE;
301     }
302 
303   g_ptr_array_add (data->expanded_events, g_steal_pointer (&event));
304 
305   return TRUE;
306 }
307 
308 static void
on_client_view_objects_added_cb(ECalClientView * view,const GSList * objects,GcalCalendarMonitor * self)309 on_client_view_objects_added_cb (ECalClientView      *view,
310                                  const GSList        *objects,
311                                  GcalCalendarMonitor *self)
312 {
313   g_autoptr (GPtrArray) components_to_expand = NULL;
314   g_autoptr (GcalRange) range = NULL;
315   const GSList *l;
316   gint i;
317 
318   GCAL_ENTRY;
319 
320   range = get_monitor_ranges (self);
321 
322   maybe_init_event_arrays (self);
323   components_to_expand = g_ptr_array_new ();
324 
325   for (l = objects; l; l = l->next)
326     {
327       g_autoptr (GcalEvent) event = NULL;
328       g_autoptr (GError) error = NULL;
329       g_autofree gchar *event_id = NULL;
330       ICalComponent *icomponent;
331       ECalComponent *ecomponent;
332 
333       icomponent = l->data;
334 
335       if (g_cancellable_is_cancelled (self->cancellable))
336         return;
337 
338       if (!icomponent || !i_cal_component_get_uid (icomponent))
339         continue;
340 
341       /* Recurrent events will be processed later */
342       if (e_cal_util_component_has_recurrences (icomponent) &&
343           !e_cal_util_component_is_instance (icomponent))
344         {
345           GCAL_TRACE_MSG ("Component %s (%s) needs to be expanded",
346                           i_cal_component_get_summary (icomponent),
347                           i_cal_component_get_uid (icomponent));
348           g_ptr_array_add (components_to_expand, icomponent);
349           continue;
350         }
351 
352       ecomponent = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomponent));
353 
354       if (!ecomponent)
355         continue;
356 
357       event = gcal_event_new (self->calendar, ecomponent, &error);
358       g_clear_object (&ecomponent);
359 
360       if (error)
361         {
362           g_warning ("Error creating event: %s", error->message);
363           continue;
364         }
365 
366       event_id = g_strdup (gcal_event_get_uid (event));
367 
368       if (!self->monitor_thread.populated)
369         {
370           g_hash_table_insert (self->monitor_thread.events_to_add,
371                                g_steal_pointer (&event_id),
372                                g_object_ref (event));
373         }
374       else
375         {
376           add_event_in_idle (self, event);
377         }
378     }
379 
380   /* Recurrent events */
381   if (components_to_expand->len > 0)
382     {
383       g_autoptr (GPtrArray) expanded_events = NULL;
384       g_autoptr (GDateTime) range_start = NULL;
385       g_autoptr (GDateTime) range_end = NULL;
386       ECalClient *client;
387       time_t range_start_time;
388       time_t range_end_time;
389 
390       GCAL_TRACE_MSG ("Expanding recurrencies of %d events", components_to_expand->len);
391 
392       client = gcal_calendar_get_client (self->calendar);
393 
394       range_start = gcal_range_get_start (range);
395       range_end = gcal_range_get_end (range);
396       range_start_time = g_date_time_to_unix (range_start);
397       range_end_time = g_date_time_to_unix (range_end) - 1;
398 
399       expanded_events = g_ptr_array_new_with_free_func (g_object_unref);
400 
401       /* Generate the instances */
402       for (i = 0; i < components_to_expand->len; i++)
403         {
404           GenerateRecurrencesData recurrences_data;
405           ICalComponent *icomponent;
406 #if GCAL_ENABLE_TRACE
407           gint old_size;
408 #endif
409 
410           if (g_cancellable_is_cancelled (self->cancellable))
411             return;
412 
413           icomponent = g_ptr_array_index (components_to_expand, i);
414 
415           recurrences_data.monitor = self;
416           recurrences_data.expanded_events = expanded_events;
417 
418 #if GCAL_ENABLE_TRACE
419           old_size = expanded_events->len;
420 #endif
421 
422           e_cal_client_generate_instances_for_object_sync (client,
423                                                            icomponent,
424                                                            range_start_time,
425                                                            range_end_time,
426                                                            self->cancellable,
427                                                            client_instance_generated_cb,
428                                                            &recurrences_data);
429 
430 #if GCAL_ENABLE_TRACE
431             {
432               g_autofree gchar *range_str = gcal_range_to_string (range);
433 
434               GCAL_TRACE_MSG ("Component %s (%s) added %d instance(s) between %s",
435                               i_cal_component_get_summary (icomponent),
436                               i_cal_component_get_uid (icomponent),
437                               expanded_events->len - old_size,
438                               range_str);
439             }
440 #endif
441         }
442 
443       /* Process all these instances */
444       for (i = 0; i < expanded_events->len; i++)
445         {
446           g_autofree gchar *event_id = NULL;
447           GcalEvent *event = g_ptr_array_index (expanded_events, i);
448 
449           if (g_cancellable_is_cancelled (self->cancellable))
450             return;
451 
452           event_id = g_strdup (gcal_event_get_uid (event));
453 
454           if (!self->monitor_thread.populated)
455             {
456               g_hash_table_insert (self->monitor_thread.events_to_add,
457                                    g_steal_pointer (&event_id),
458                                    g_object_ref (event));
459             }
460           else
461             {
462               add_event_in_idle (self, event);
463             }
464         }
465     }
466 
467   GCAL_EXIT;
468 }
469 
470 static void
on_client_view_objects_modified_cb(ECalClientView * view,const GSList * objects,GcalCalendarMonitor * self)471 on_client_view_objects_modified_cb (ECalClientView      *view,
472                                     const GSList        *objects,
473                                     GcalCalendarMonitor *self)
474 {
475   const GSList *l;
476 
477   GCAL_ENTRY;
478 
479   if (!self->monitor_thread.populated && self->monitor_thread.events_to_add)
480     {
481       g_clear_pointer (&self->monitor_thread.events_to_add, g_hash_table_destroy);
482       return;
483     }
484 
485   for (l = objects; l; l = l->next)
486     {
487       g_autoptr (GcalEvent) event = NULL;
488       g_autoptr (GError) error = NULL;
489       ICalComponent *icomponent;
490       ECalComponent *ecomponent;
491 
492       icomponent = l->data;
493 
494       if (!icomponent || !i_cal_component_get_uid (icomponent))
495         continue;
496 
497       ecomponent = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomponent));
498 
499       if (!ecomponent)
500         continue;
501 
502       event = gcal_event_new (self->calendar, ecomponent, &error);
503       g_clear_object (&ecomponent);
504 
505       if (error)
506         {
507           g_warning ("Error creating event: %s", error->message);
508           continue;
509         }
510 
511       update_event_in_idle (self, event);
512     }
513 
514   GCAL_EXIT;
515 }
516 
517 static void
on_client_view_objects_removed_cb(ECalClientView * view,const GSList * objects,GcalCalendarMonitor * self)518 on_client_view_objects_removed_cb (ECalClientView      *view,
519                                    const GSList        *objects,
520                                    GcalCalendarMonitor *self)
521 {
522   const GSList *l;
523 
524   GCAL_ENTRY;
525 
526   for (l = objects; l; l = l->next)
527     {
528       g_autofree gchar *event_id = NULL;
529       ECalComponentId *component_id;
530 
531       if (g_cancellable_is_cancelled (self->cancellable))
532         return;
533 
534       component_id = l->data;
535 
536       if (e_cal_component_id_get_rid (component_id))
537         {
538           event_id = g_strdup_printf ("%s:%s:%s",
539                                       gcal_calendar_get_id (self->calendar),
540                                       e_cal_component_id_get_uid (component_id),
541                                       e_cal_component_id_get_rid (component_id));
542         }
543       else
544         {
545           event_id = g_strdup_printf ("%s:%s",
546                                       gcal_calendar_get_id (self->calendar),
547                                       e_cal_component_id_get_uid (component_id));
548         }
549 
550       remove_event_in_idle (self, event_id);
551     }
552 
553   GCAL_EXIT;
554 }
555 
556 static void
on_client_view_complete_cb(ECalClientView * view,const GError * error,GcalCalendarMonitor * self)557 on_client_view_complete_cb (ECalClientView      *view,
558                             const GError        *error,
559                             GcalCalendarMonitor *self)
560 {
561   g_autoptr (GHashTable) events_to_add = NULL;
562 
563   GCAL_ENTRY;
564 
565   g_assert (!self->monitor_thread.populated);
566 
567   events_to_add = g_steal_pointer (&self->monitor_thread.events_to_add);
568 
569   /* Process all these instances */
570   if (events_to_add)
571     {
572       GHashTableIter iter;
573       GcalEvent *event;
574 
575       g_hash_table_iter_init (&iter, events_to_add);
576       while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &event))
577         {
578           if (g_cancellable_is_cancelled (self->cancellable))
579             return;
580 
581           GCAL_TRACE_MSG ("Sending event '%s' (%s) to main thead's idle",
582                           gcal_event_get_summary (event),
583                           gcal_event_get_uid (event));
584 
585           add_event_in_idle (self, event);
586         }
587     }
588 
589   self->monitor_thread.populated = TRUE;
590 
591   set_complete_in_idle (self, TRUE);
592 
593   g_debug ("Finished initial loading of calendar '%s'", gcal_calendar_get_name (self->calendar));
594 
595   GCAL_EXIT;
596 }
597 
598 static void
create_view(GcalCalendarMonitor * self)599 create_view (GcalCalendarMonitor *self)
600 {
601   g_autofree gchar *filter = NULL;
602   g_autoptr (GError) error = NULL;
603   ECalClientView *view;
604   ECalClient *client;
605 
606   GCAL_ENTRY;
607 
608   g_mutex_lock (&self->shared.mutex);
609 
610   g_assert (self->cancellable == NULL);
611   self->cancellable = g_cancellable_new ();
612 
613   if (!self->shared.range)
614     {
615       g_mutex_unlock (&self->shared.mutex);
616       GCAL_RETURN ();
617     }
618 
619   filter = build_subscriber_filter (self->shared.range, self->shared.filter);
620 
621   g_mutex_unlock (&self->shared.mutex);
622 
623   if (!gcal_calendar_get_visible (self->calendar))
624     GCAL_RETURN ();
625 
626   client = gcal_calendar_get_client (self->calendar);
627   e_cal_client_get_view_sync (client, filter, &view, self->cancellable, &error);
628 
629   if (error)
630     {
631       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
632         g_warning ("Error creating view: %s", error->message);
633       GCAL_RETURN ();
634     }
635 
636   GCAL_TRACE_MSG ("Initialized ECalClientView with query \"%s\"", filter);
637 
638   g_signal_connect (view, "objects-added", G_CALLBACK (on_client_view_objects_added_cb), self);
639   g_signal_connect (view, "objects-modified", G_CALLBACK (on_client_view_objects_modified_cb), self);
640   g_signal_connect (view, "objects-removed", G_CALLBACK (on_client_view_objects_removed_cb), self);
641   g_signal_connect (view, "complete", G_CALLBACK (on_client_view_complete_cb), self);
642 
643   if (g_cancellable_is_cancelled (self->cancellable))
644     GCAL_RETURN ();
645 
646   g_debug ("Starting ECalClientView for calendar '%s'", gcal_calendar_get_name (self->calendar));
647 
648   e_cal_client_view_start (view, &error);
649 
650   if (error)
651     {
652       g_warning ("Error starting up view: %s", error->message);
653       g_clear_object (&view);
654       GCAL_RETURN ();
655     }
656 
657   self->monitor_thread.view = g_steal_pointer (&view);
658 
659   set_complete_in_idle (self, FALSE);
660 
661   GCAL_EXIT;
662 }
663 
664 static void
remove_view(GcalCalendarMonitor * self)665 remove_view (GcalCalendarMonitor *self)
666 {
667   g_autoptr (GError) error = NULL;
668 
669   GCAL_ENTRY;
670 
671   g_clear_object (&self->cancellable);
672 
673   if (!self->monitor_thread.view)
674     GCAL_RETURN ();
675 
676   g_debug ("Tearing down view for calendar %s", gcal_calendar_get_id (self->calendar));
677 
678   e_cal_client_view_stop (self->monitor_thread.view, &error);
679 
680   if (error)
681     {
682       g_warning ("Error stopping view: %s", error->message);
683       GCAL_RETURN ();
684     }
685 
686   g_clear_object (&self->monitor_thread.view);
687   self->monitor_thread.populated = FALSE;
688 
689   GCAL_EXIT;
690 }
691 
692 typedef struct
693 {
694   GSource              parent;
695   GcalCalendarMonitor *monitor;
696   GMainLoop           *mainloop;
697 } MessageQueueSource;
698 
699 static gboolean
message_queue_source_prepare(GSource * source,gint * timeout)700 message_queue_source_prepare (GSource *source,
701                               gint    *timeout)
702 {
703   GcalCalendarMonitor *self;
704   MessageQueueSource *queue_source;
705 
706   queue_source = (MessageQueueSource*) source;
707   self = queue_source->monitor;
708 
709   return g_async_queue_length (self->messages) > 0;
710 }
711 
712 static gboolean
message_queue_source_dispatch(GSource * source,GSourceFunc callback,gpointer user_data)713 message_queue_source_dispatch (GSource     *source,
714                                GSourceFunc  callback,
715                                gpointer     user_data)
716 {
717   GcalCalendarMonitor *self;
718   MessageQueueSource *queue_source;
719   MonitorThreadEvent event;
720 
721   GCAL_ENTRY;
722 
723   queue_source = (MessageQueueSource*) source;
724   self = queue_source->monitor;
725   event = GPOINTER_TO_INT (g_async_queue_pop (self->messages));
726 
727   switch (event)
728     {
729     case FILTER_UPDATED:
730     case RANGE_UPDATED:
731       remove_view (self);
732       create_view (self);
733       break;
734 
735     case CREATE_VIEW:
736       create_view (self);
737       break;
738 
739     case REMOVE_VIEW:
740       remove_view (self);
741       break;
742 
743     case INVALID_EVENT:
744     case QUIT:
745       remove_view (self);
746       g_main_loop_quit (queue_source->mainloop);
747       GCAL_RETURN (G_SOURCE_REMOVE);
748     }
749 
750   GCAL_RETURN (G_SOURCE_CONTINUE);
751 }
752 
753 static GSourceFuncs monitor_queue_source_funcs =
754 {
755   message_queue_source_prepare,
756   NULL,
757   message_queue_source_dispatch,
758   NULL,
759 };
760 
761 static MessageQueueSource*
views_monitor_source_new(GcalCalendarMonitor * self,GMainLoop * mainloop)762 views_monitor_source_new (GcalCalendarMonitor *self,
763                           GMainLoop           *mainloop)
764 {
765   MessageQueueSource *queue_source;
766   GSource *source;
767 
768   /* Prepare the message queue source */
769   source = g_source_new (&monitor_queue_source_funcs, sizeof (MessageQueueSource));
770   queue_source = (MessageQueueSource*) source;
771   queue_source->monitor = self;
772   queue_source->mainloop = mainloop;
773   g_source_set_name (source, "Message Queue Source");
774 
775   return queue_source;
776 }
777 
778 static gpointer
calendar_view_thread_func(gpointer data)779 calendar_view_thread_func (gpointer data)
780 {
781   g_autoptr (GMainLoop) mainloop = NULL;
782   MessageQueueSource *queue_source;
783   GcalCalendarMonitor *self;
784 
785   GCAL_ENTRY;
786 
787   self = data;
788   mainloop = g_main_loop_new (self->thread_context, FALSE);
789   g_main_context_push_thread_default (self->thread_context);
790 
791   /* Events source */
792   queue_source = views_monitor_source_new (self, mainloop);
793   g_source_attach ((GSource*) queue_source, self->thread_context);
794 
795   g_main_loop_run (mainloop);
796   g_main_loop_quit (mainloop);
797 
798   g_main_context_pop_thread_default (self->thread_context);
799 
800   GCAL_RETURN (NULL);
801 }
802 
803 /*
804  * Auxiliary methods
805  */
806 
807 static void
notify_view_thread(GcalCalendarMonitor * self,MonitorThreadEvent event)808 notify_view_thread (GcalCalendarMonitor *self,
809                     MonitorThreadEvent   event)
810 {
811   g_async_queue_push (self->messages, GINT_TO_POINTER (event));
812   g_main_context_wakeup (self->thread_context);
813 }
814 
815 static void
maybe_spawn_view_thread(GcalCalendarMonitor * self)816 maybe_spawn_view_thread (GcalCalendarMonitor *self)
817 {
818   g_autofree gchar *thread_name = NULL;
819 
820   if (self->thread || !self->shared.range)
821     return;
822 
823   thread_name = g_strdup_printf ("GcalCalendarMonitor (%s)", gcal_calendar_get_id (self->calendar));
824   self->thread = g_thread_new (thread_name, calendar_view_thread_func, self);
825 
826   g_debug ("Spawning thread %s", thread_name);
827 }
828 
829 static void
remove_events_outside_range(GcalCalendarMonitor * self,GcalRange * range)830 remove_events_outside_range (GcalCalendarMonitor *self,
831                              GcalRange           *range)
832 {
833   GHashTableIter iter;
834   GcalEvent *event;
835 
836   GCAL_TRACE_MSG ("Removing events outside range from monitor");
837 
838   g_hash_table_iter_init (&iter, self->events);
839   while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &event))
840     {
841       g_object_ref (event);
842 
843       g_hash_table_iter_remove (&iter);
844       g_signal_emit (self, signals[EVENT_REMOVED], 0, event);
845 
846       g_object_unref (event);
847     }
848 }
849 
850 static void
remove_all_events(GcalCalendarMonitor * self)851 remove_all_events (GcalCalendarMonitor *self)
852 {
853   GHashTableIter iter;
854   GcalEvent *event;
855 
856   GCAL_TRACE_MSG ("Removing all events from view");
857 
858   g_hash_table_iter_init (&iter, self->events);
859   while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &event))
860     {
861       g_hash_table_iter_remove (&iter);
862       g_signal_emit (self, signals[EVENT_REMOVED], 0, event);
863     }
864 }
865 
866 static void
set_complete(GcalCalendarMonitor * self,gboolean complete)867 set_complete (GcalCalendarMonitor *self,
868               gboolean             complete)
869 {
870   if (self->complete == complete)
871     return;
872 
873   GCAL_TRACE_MSG ("Setting complete to %s", complete ? "TRUE" : "FALSE");
874 
875   self->complete = complete;
876   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COMPLETE]);
877 }
878 
879 
880 /*
881  * Callbacks
882  */
883 
884 static gboolean
add_event_to_timeline_in_idle_cb(gpointer user_data)885 add_event_to_timeline_in_idle_cb (gpointer user_data)
886 {
887   GcalCalendarMonitor *self;
888   GcalEvent *event;
889   IdleData *idle_data;
890   const gchar *event_id;
891 
892   GCAL_ENTRY;
893 
894   idle_data = user_data;
895   self = idle_data->monitor;
896   event = idle_data->event;
897   g_assert (idle_data->event_id == NULL);
898 
899   event_id = gcal_event_get_uid (event);
900   if (g_hash_table_insert (self->events, g_strdup (event_id), g_object_ref (event)))
901     g_signal_emit (self, signals[EVENT_ADDED], 0, event);
902 
903   GCAL_RETURN (G_SOURCE_REMOVE);
904 }
905 
906 static gboolean
update_event_in_idle_cb(gpointer user_data)907 update_event_in_idle_cb (gpointer user_data)
908 {
909   GcalCalendarMonitor *self;
910   GcalEvent *old_event;
911   GcalEvent *event;
912   IdleData *idle_data;
913   const gchar *event_id;
914 
915   GCAL_ENTRY;
916 
917   idle_data = user_data;
918   self = idle_data->monitor;
919   event = idle_data->event;
920   g_assert (idle_data->event_id == NULL);
921 
922   event_id = gcal_event_get_uid (event);
923   old_event = g_hash_table_lookup (self->events, event_id);
924   if (old_event)
925     {
926       g_hash_table_insert (self->events, g_strdup (event_id), g_object_ref (event));
927       g_signal_emit (self, signals[EVENT_UPDATED], 0, old_event, event);
928     }
929 
930   GCAL_RETURN (G_SOURCE_REMOVE);
931 }
932 
933 static gboolean
remove_event_from_timeline_in_idle_cb(gpointer user_data)934 remove_event_from_timeline_in_idle_cb (gpointer user_data)
935 {
936   g_autoptr (GcalEvent) event = NULL;
937   GcalCalendarMonitor *self;
938   IdleData *idle_data;
939   gchar *event_id;
940 
941   GCAL_ENTRY;
942 
943   idle_data = user_data;
944   self = idle_data->monitor;
945   g_assert (idle_data->event == NULL);
946   event_id = idle_data->event_id;
947 
948   event = g_hash_table_lookup (self->events, event_id);
949 
950   if (event)
951     {
952       /* Keep the event alive until the signal is emitted */
953       g_object_ref (event);
954 
955       g_hash_table_remove (self->events, event_id);
956       g_signal_emit (self, signals[EVENT_REMOVED], 0, event);
957     }
958 
959   GCAL_RETURN (G_SOURCE_REMOVE);
960 }
961 
962 static void
on_calendar_visible_changed_cb(GcalCalendar * calendar,GParamSpec * pspec,GcalCalendarMonitor * self)963 on_calendar_visible_changed_cb (GcalCalendar        *calendar,
964                                 GParamSpec          *pspec,
965                                 GcalCalendarMonitor *self)
966 {
967   if (gcal_calendar_get_visible (calendar))
968     {
969       notify_view_thread (self, CREATE_VIEW);
970     }
971   else
972     {
973       remove_all_events (self);
974       notify_view_thread (self, REMOVE_VIEW);
975     }
976 }
977 
978 static gboolean
complete_in_idle_cb(gpointer user_data)979 complete_in_idle_cb (gpointer user_data)
980 {
981   GcalCalendarMonitor *self;
982   IdleData *idle_data;
983 
984   GCAL_ENTRY;
985 
986   idle_data = user_data;
987   self = idle_data->monitor;
988   g_assert (idle_data->event == NULL);
989   g_assert (idle_data->event_id == NULL);
990 
991   set_complete (self, idle_data->complete);
992 
993   GCAL_RETURN (G_SOURCE_REMOVE);
994 }
995 
996 
997 /*
998  * GObject overrides
999  */
1000 
1001 static void
gcal_calendar_monitor_finalize(GObject * object)1002 gcal_calendar_monitor_finalize (GObject *object)
1003 {
1004   GcalCalendarMonitor *self = (GcalCalendarMonitor *)object;
1005 
1006   g_cancellable_cancel (self->cancellable);
1007   notify_view_thread (self, QUIT);
1008   g_clear_pointer (&self->thread, g_thread_join);
1009 
1010   remove_all_events (self);
1011 
1012   g_clear_object (&self->calendar);
1013   g_clear_pointer (&self->events, g_hash_table_destroy);
1014   g_clear_pointer (&self->thread_context, g_main_context_unref);
1015   g_clear_pointer (&self->main_context, g_main_context_unref);
1016   g_clear_pointer (&self->messages, g_async_queue_unref);
1017   g_clear_pointer (&self->shared.filter, g_free);
1018   g_clear_pointer (&self->shared.range, gcal_range_unref);
1019 
1020   G_OBJECT_CLASS (gcal_calendar_monitor_parent_class)->finalize (object);
1021 }
1022 
1023 static void
gcal_calendar_monitor_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1024 gcal_calendar_monitor_get_property (GObject    *object,
1025                                     guint       prop_id,
1026                                     GValue     *value,
1027                                     GParamSpec *pspec)
1028 {
1029   GcalCalendarMonitor *self = GCAL_CALENDAR_MONITOR (object);
1030 
1031   switch (prop_id)
1032     {
1033     case PROP_CALENDAR:
1034       g_value_set_object (value, self->calendar);
1035       break;
1036 
1037     case PROP_COMPLETE:
1038       g_value_set_boolean (value, self->complete);
1039       break;
1040 
1041     default:
1042       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1043     }
1044 }
1045 
1046 static void
gcal_calendar_monitor_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1047 gcal_calendar_monitor_set_property (GObject      *object,
1048                                     guint         prop_id,
1049                                     const GValue *value,
1050                                     GParamSpec   *pspec)
1051 {
1052   GcalCalendarMonitor *self = GCAL_CALENDAR_MONITOR (object);
1053 
1054   switch (prop_id)
1055     {
1056     case PROP_CALENDAR:
1057       g_assert (self->calendar == NULL);
1058       self->calendar = g_value_dup_object (value);
1059       g_signal_connect (self->calendar,
1060                         "notify::visible",
1061                         G_CALLBACK (on_calendar_visible_changed_cb),
1062                         self);
1063       break;
1064 
1065     case PROP_COMPLETE:
1066     default:
1067       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1068     }
1069 }
1070 
1071 static void
gcal_calendar_monitor_class_init(GcalCalendarMonitorClass * klass)1072 gcal_calendar_monitor_class_init (GcalCalendarMonitorClass *klass)
1073 {
1074   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1075 
1076   object_class->finalize = gcal_calendar_monitor_finalize;
1077   object_class->get_property = gcal_calendar_monitor_get_property;
1078   object_class->set_property = gcal_calendar_monitor_set_property;
1079 
1080   signals[EVENT_ADDED] = g_signal_new ("event-added",
1081                                        GCAL_TYPE_CALENDAR_MONITOR,
1082                                        G_SIGNAL_RUN_FIRST,
1083                                        0, NULL, NULL, NULL,
1084                                        G_TYPE_NONE,
1085                                        1,
1086                                        GCAL_TYPE_EVENT);
1087 
1088   signals[EVENT_UPDATED] = g_signal_new ("event-updated",
1089                                          GCAL_TYPE_CALENDAR_MONITOR,
1090                                          G_SIGNAL_RUN_FIRST,
1091                                          0, NULL, NULL, NULL,
1092                                          G_TYPE_NONE,
1093                                          2,
1094                                          GCAL_TYPE_EVENT,
1095                                          GCAL_TYPE_EVENT);
1096 
1097   signals[EVENT_REMOVED] = g_signal_new ("event-removed",
1098                                          GCAL_TYPE_CALENDAR_MONITOR,
1099                                          G_SIGNAL_RUN_FIRST,
1100                                          0, NULL, NULL, NULL,
1101                                          G_TYPE_NONE,
1102                                          1,
1103                                          GCAL_TYPE_EVENT);
1104 
1105   properties[PROP_CALENDAR] = g_param_spec_object ("calendar",
1106                                                    "Calendar",
1107                                                    "Calendar to be monitored",
1108                                                    GCAL_TYPE_CALENDAR,
1109                                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
1110 
1111 
1112   properties[PROP_COMPLETE] = g_param_spec_boolean ("complete",
1113                                                     "Complete",
1114                                                     "Whether",
1115                                                     FALSE,
1116                                                     G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
1117 
1118   g_object_class_install_properties (object_class, N_PROPS, properties);
1119 }
1120 
1121 static void
gcal_calendar_monitor_init(GcalCalendarMonitor * self)1122 gcal_calendar_monitor_init (GcalCalendarMonitor *self)
1123 {
1124   self->events = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
1125   self->thread_context = g_main_context_new ();
1126   self->main_context = g_main_context_ref_thread_default ();
1127   self->messages = g_async_queue_new ();
1128   self->complete = FALSE;
1129 }
1130 
1131 GcalCalendarMonitor*
gcal_calendar_monitor_new(GcalCalendar * calendar)1132 gcal_calendar_monitor_new (GcalCalendar *calendar)
1133 {
1134   return g_object_new (GCAL_TYPE_CALENDAR_MONITOR,
1135                        "calendar", calendar,
1136                        NULL);
1137 }
1138 
1139 /**
1140  * gcal_calendar_monitor_set_range:
1141  * @self: a #GcalCalendarMonitor
1142  * @range: a #GcalRange
1143  *
1144  * Updates the range of @self. This usually will result in
1145  * @self creating a new view, and gathering the events from
1146  * the events server.
1147  */
1148 void
gcal_calendar_monitor_set_range(GcalCalendarMonitor * self,GcalRange * range)1149 gcal_calendar_monitor_set_range (GcalCalendarMonitor *self,
1150                                  GcalRange           *range)
1151 {
1152   gboolean range_changed;
1153 
1154   g_return_if_fail (GCAL_IS_CALENDAR_MONITOR (self));
1155 
1156   GCAL_ENTRY;
1157 
1158   g_mutex_lock (&self->shared.mutex);
1159 
1160   range_changed =
1161     !self->shared.range ||
1162     !range ||
1163     gcal_range_calculate_overlap (self->shared.range, range, NULL) != GCAL_RANGE_EQUAL;
1164 
1165   if (!range_changed)
1166     {
1167       g_mutex_unlock (&self->shared.mutex);
1168       GCAL_RETURN ();
1169     }
1170 
1171   g_clear_pointer (&self->shared.range, gcal_range_unref);
1172   self->shared.range = range ? gcal_range_copy (range) : NULL;
1173 
1174   g_mutex_unlock (&self->shared.mutex);
1175 
1176   maybe_spawn_view_thread (self);
1177   remove_events_outside_range (self, range);
1178 
1179   g_cancellable_cancel (self->cancellable);
1180 
1181   if (gcal_calendar_get_visible (self->calendar))
1182     notify_view_thread (self, RANGE_UPDATED);
1183 
1184   GCAL_EXIT;
1185 }
1186 
1187 /**
1188  * gcal_calendar_monitor_get_cached_event:
1189  * @self: a #GcalCalendarMonitor
1190  * @event_id: the id of the #GcalEvent
1191  *
1192  * Returns: (transfer full)(nullable): a #GcalEvent, or %NULL
1193  */
1194 GcalEvent*
gcal_calendar_monitor_get_cached_event(GcalCalendarMonitor * self,const gchar * event_id)1195 gcal_calendar_monitor_get_cached_event (GcalCalendarMonitor *self,
1196                                         const gchar         *event_id)
1197 {
1198   GcalEvent *event;
1199 
1200   g_return_val_if_fail (GCAL_IS_CALENDAR_MONITOR (self), NULL);
1201   g_return_val_if_fail (event_id, NULL);
1202 
1203   event = g_hash_table_lookup (self->events, event_id);
1204 
1205   return event ? g_object_ref (event) : NULL;
1206 }
1207 
1208 void
gcal_calendar_monitor_set_filter(GcalCalendarMonitor * self,const gchar * filter)1209 gcal_calendar_monitor_set_filter (GcalCalendarMonitor *self,
1210                                   const gchar         *filter)
1211 {
1212   g_return_if_fail (GCAL_IS_CALENDAR_MONITOR (self));
1213 
1214   g_mutex_lock (&self->shared.mutex);
1215 
1216   g_clear_pointer (&self->shared.filter, g_free);
1217   self->shared.filter = g_strdup (filter);
1218 
1219   g_mutex_unlock (&self->shared.mutex);
1220 
1221   remove_all_events (self);
1222 
1223   if (gcal_calendar_get_visible (self->calendar))
1224     notify_view_thread (self, FILTER_UPDATED);
1225 }
1226 
1227 gboolean
gcal_calendar_monitor_is_complete(GcalCalendarMonitor * self)1228 gcal_calendar_monitor_is_complete (GcalCalendarMonitor *self)
1229 {
1230   g_return_val_if_fail (GCAL_IS_CALENDAR_MONITOR (self), FALSE);
1231 
1232   return self->complete;
1233 }
1234