[gnome-calendar/gbsneto/recurring-event-editing-fixes: 6/18] calendar-monitor: Properly update and delete recurrent events




commit f44aba35d3a90cd210bdb1e19d4ee2f0d5a4af43
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Oct 14 00:01:31 2022 -0300

    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 | 153 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 153 insertions(+)
---
diff --git a/src/core/gcal-calendar-monitor.c b/src/core/gcal-calendar-monitor.c
index 0377dd0a..ea6c8e91 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,10 +489,16 @@ 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;
+      ECalComponentId *component_id;
       ICalComponent *icomponent;
       ECalComponent *ecomponent;
 
@@ -500,6 +512,41 @@ on_client_view_objects_modified_cb (ECalClientView      *view,
       if (!ecomponent)
         continue;
 
+      /*
+       * 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);
+      if (!e_cal_component_id_get_rid (component_id))
+        {
+          g_autofree gchar *event_id = NULL;
+
+          event_id = g_strdup_printf ("%s:%s",
+                                      gcal_calendar_get_id (self->calendar),
+                                      e_cal_component_id_get_uid (component_id));
+
+          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))
+                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 (e_cal_util_component_has_recurrences (icomponent) &&
+          !e_cal_util_component_is_instance (icomponent))
+        {
+          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 +559,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 +679,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);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]