From a0963c0d36266ceedbbe86e8ff94aa14e81e681a Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Fri, 14 Oct 2022 00:01:31 -0300 Subject: [PATCH] calendar-monitor: Properly update and delete recurrent events When we receive the 'objects-modified' signal, we get components that may represent main events. We also may receive components that have no recurrency because they were just changed from being recurrent to non-recurrent. We need to handle all these cases properly. If the event has no recurrency set, remove any potential old recurrence instance lingering around. If we're changing an event with recurrency, make sure the recurrency instances are in sync with the new recurrency. Fixes https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/881 (or so I hope.) --- src/core/gcal-calendar-monitor.c | 159 +++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/core/gcal-calendar-monitor.c b/src/core/gcal-calendar-monitor.c index 0377dd0a..0c82d40b 100644 --- a/src/core/gcal-calendar-monitor.c +++ b/src/core/gcal-calendar-monitor.c @@ -473,7 +473,13 @@ on_client_view_objects_modified_cb (ECalClientView *view, const GSList *objects, GcalCalendarMonitor *self) { + g_autoptr (GRWLockReaderLocker) reader_locker = NULL; + g_autoptr (GHashTable) events_to_remove = NULL; + g_autoptr (GPtrArray) components_to_expand = NULL; + g_autoptr (GcalRange) range = NULL; + GHashTableIter iter; const GSList *l; + const gchar *aux; GCAL_ENTRY; @@ -483,12 +489,20 @@ on_client_view_objects_modified_cb (ECalClientView *view, return; } + reader_locker = g_rw_lock_reader_locker_new (&self->shared.lock); + components_to_expand = g_ptr_array_new (); + events_to_remove = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + range = gcal_range_copy (self->shared.range); + for (l = objects; l; l = l->next) { g_autoptr (GcalEvent) event = NULL; g_autoptr (GError) error = NULL; + g_autofree gchar *event_id = NULL; + ECalComponentId *component_id; ICalComponent *icomponent; ECalComponent *ecomponent; + gboolean recurrence_main; icomponent = l->data; @@ -500,6 +514,45 @@ on_client_view_objects_modified_cb (ECalClientView *view, if (!ecomponent) continue; + recurrence_main = e_cal_util_component_has_recurrences (icomponent) && + !e_cal_util_component_is_instance (icomponent); + + /* + * If the event has no recurrence id, it is either a main event, or a + * regular non-recurring event. If we're receiving the 'objects-modified' + * signal because this event went from recurring to non-recurring, the + * instances of the old recurrency are still here. They need to be removed. + */ + component_id = e_cal_component_get_id (ecomponent); + + event_id = g_strdup_printf ("%s:%s", + gcal_calendar_get_id (self->calendar), + e_cal_component_id_get_uid (component_id)); + + if (!e_cal_component_id_get_rid (component_id)) + { + g_hash_table_iter_init (&iter, self->shared.events); + while (g_hash_table_iter_next (&iter, (gpointer*) &aux, NULL)) + { + if (!g_str_has_prefix (aux, event_id)) + continue; + + if (recurrence_main || !g_str_equal (aux, event_id)) + g_hash_table_add (events_to_remove, g_strdup (aux)); + } + } + g_clear_pointer (&component_id, e_cal_component_id_free); + + /* Recurrent events will be processed later */ + if (recurrence_main) + { + GCAL_TRACE_MSG ("Component %s (%s) needs to be expanded", + i_cal_component_get_summary (icomponent), + i_cal_component_get_uid (icomponent)); + g_ptr_array_add (components_to_expand, icomponent); + continue; + } + event = gcal_event_new (self->calendar, ecomponent, &error); g_clear_object (&ecomponent); @@ -512,6 +565,95 @@ on_client_view_objects_modified_cb (ECalClientView *view, update_event_in_idle (self, event); } + /* Recurrent events */ + if (components_to_expand->len > 0) + { + g_autoptr (GPtrArray) expanded_events = NULL; + g_autoptr (GDateTime) range_start = NULL; + g_autoptr (GDateTime) range_end = NULL; + ECalClient *client; + time_t range_start_time; + time_t range_end_time; + + GCAL_TRACE_MSG ("Expanding recurrencies of %d events", components_to_expand->len); + + client = gcal_calendar_get_client (self->calendar); + + range_start = gcal_range_get_start (range); + range_end = gcal_range_get_end (range); + range_start_time = g_date_time_to_unix (range_start); + range_end_time = g_date_time_to_unix (range_end) - 1; + + expanded_events = g_ptr_array_new_with_free_func (g_object_unref); + + /* Generate the instances */ + for (guint i = 0; i < components_to_expand->len; i++) + { + GenerateRecurrencesData recurrences_data; + ICalComponent *icomponent; +#if GCAL_ENABLE_TRACE + gint old_size; +#endif + + if (g_cancellable_is_cancelled (self->cancellable)) + return; + + icomponent = g_ptr_array_index (components_to_expand, i); + + recurrences_data.monitor = self; + recurrences_data.expanded_events = expanded_events; + +#if GCAL_ENABLE_TRACE + old_size = expanded_events->len; +#endif + + e_cal_client_generate_instances_for_object_sync (client, + icomponent, + range_start_time, + range_end_time, + self->cancellable, + client_instance_generated_cb, + &recurrences_data); + +#if GCAL_ENABLE_TRACE + { + g_autofree gchar *range_str = gcal_range_to_string (range); + + GCAL_TRACE_MSG ("Component %s (%s) added %d instance(s) between %s", + i_cal_component_get_summary (icomponent), + i_cal_component_get_uid (icomponent), + expanded_events->len - old_size, + range_str); + } +#endif + } + + for (guint i = 0; i < expanded_events->len; i++) + { + GcalEvent *event = g_ptr_array_index (expanded_events, i); + GcalEvent *old_event; + + if (g_cancellable_is_cancelled (self->cancellable)) + return; + + old_event = g_hash_table_lookup (self->shared.events, gcal_event_get_uid (event)); + if (old_event) + { + update_event_in_idle (self, event); + g_hash_table_remove (events_to_remove, gcal_event_get_uid (event)); + } + else + { + add_event_in_idle (self, event); + } + } + } + + /* Now remove lingering events */ + g_hash_table_iter_init (&iter, events_to_remove); + while (g_hash_table_iter_next (&iter, (gpointer*) &aux, NULL)) + remove_event_in_idle (self, aux); + GCAL_EXIT; } @@ -543,9 +685,26 @@ on_client_view_objects_removed_cb (ECalClientView *view, } else { + g_autoptr (GRWLockReaderLocker) reader_locker = NULL; + GHashTableIter iter; + const gchar *aux; + event_id = g_strdup_printf ("%s:%s", gcal_calendar_get_id (self->calendar), e_cal_component_id_get_uid (component_id)); + + /* + * If this is the main component, remove the expanded recurrency instances + * as well. + */ + reader_locker = g_rw_lock_reader_locker_new (&self->shared.lock); + + g_hash_table_iter_init (&iter, self->shared.events); + while (g_hash_table_iter_next (&iter, (gpointer*) &aux, NULL)) + { + if (!g_str_equal (aux, event_id) && g_str_has_prefix (aux, event_id)) + remove_event_in_idle (self, aux); + } } remove_event_in_idle (self, event_id);