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