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.)
This commit is contained in:
Georges Basile Stavracas Neto
2022-10-17 23:37:13 -03:00
parent 16b9337e3f
commit a0963c0d36
+159
View File
@@ -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);