core: Add "expand-recurrences" property to GcalCalendarMonitor and GcalTimeline

Add an "expand-recurrences" boolean property (defaulting to TRUE) to both
GcalCalendarMonitor and GcalTimeline. When set to FALSE:
- Recurring master components are kept as single events without expanding
  each recurrence instance.
- Detached recurrence instances are ignored.
- Pruning outside range retains recurring master events so they are not
  prematurely removed.
- Subscriber filtering permits recurring master events within the
  subscriber model.

This allows consumers like search providers to avoid expanding all recurrence
instances when searching, improving performance and avoiding duplicate hits.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/808>
This commit is contained in:
John Cardullo
2026-10-27 11:10:58 -07:00
parent 4defb0019b
commit 9d29a7a34e
4 changed files with 102 additions and 9 deletions
+58 -8
View File
@@ -81,6 +81,7 @@ struct _GcalCalendarMonitor
GHashTable *events; /* gchar* -> GcalEvent* */
GcalRange *range;
gchar *filter;
gboolean expand_recurrences;
} shared;
};
@@ -98,6 +99,7 @@ enum
PROP_0,
PROP_CALENDAR,
PROP_COMPLETE,
PROP_EXPAND_RECURRENCES,
N_PROPS
};
@@ -383,14 +385,26 @@ on_client_view_objects_added_cb (ECalClientView *view,
if (!icomponent || !i_cal_component_get_uid (icomponent))
continue;
/* Recurrent events will be processed later */
if (e_cal_util_component_has_recurrences (icomponent) &&
!e_cal_util_component_is_instance (icomponent))
if (self->shared.expand_recurrences)
{
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);
/* 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;
}
}
else if (e_cal_util_component_is_instance (icomponent))
{
/*
* When recurrence expansion is disabled, ignore detached recurrence
* instances (components with a RECURRENCE-ID) returned by EDS so
* only the master recurring component is processed.
*/
continue;
}
@@ -528,12 +542,17 @@ on_client_view_objects_modified_cb (ECalClientView *view,
if (!icomponent || !i_cal_component_get_uid (icomponent))
continue;
/* Ignore modified detached recurrence instances when expansion is disabled */
if (!self->shared.expand_recurrences && e_cal_util_component_is_instance (icomponent))
continue;
ecomponent = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomponent));
if (!ecomponent)
continue;
recurrence_main = e_cal_util_component_has_recurrences (icomponent) &&
recurrence_main = self->shared.expand_recurrences &&
e_cal_util_component_has_recurrences (icomponent) &&
!e_cal_util_component_is_instance (icomponent);
/*
@@ -1047,6 +1066,9 @@ remove_events_outside_range (GcalCalendarMonitor *self,
{
GcalRange *event_range = gcal_event_get_range (event);
if (!self->shared.expand_recurrences && gcal_event_has_recurrence (event))
continue;
if (gcal_range_calculate_overlap (range, event_range, NULL) != GCAL_RANGE_NO_OVERLAP)
continue;
@@ -1326,6 +1348,10 @@ gcal_calendar_monitor_get_property (GObject *object,
g_value_set_boolean (value, self->complete);
break;
case PROP_EXPAND_RECURRENCES:
g_value_set_boolean (value, gcal_calendar_monitor_get_expand_recurrences (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -1350,6 +1376,10 @@ gcal_calendar_monitor_set_property (GObject *object,
self);
break;
case PROP_EXPAND_RECURRENCES:
self->shared.expand_recurrences = g_value_get_boolean (value);
break;
case PROP_COMPLETE:
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1374,12 +1404,24 @@ gcal_calendar_monitor_class_init (GcalCalendarMonitorClass *klass)
FALSE,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GcalCalendarMonitor:expand-recurrences:
*
* Whether to expand recurrences into multiple instances.
*/
properties[PROP_EXPAND_RECURRENCES] = g_param_spec_boolean ("expand-recurrences",
NULL,
NULL,
TRUE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
gcal_calendar_monitor_init (GcalCalendarMonitor *self)
{
self->shared.expand_recurrences = TRUE;
self->shared.events = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
self->thread_context = g_main_context_new ();
self->main_context = g_main_context_ref_thread_default ();
@@ -1502,3 +1544,11 @@ gcal_calendar_monitor_is_complete (GcalCalendarMonitor *self)
return self->complete;
}
gboolean
gcal_calendar_monitor_get_expand_recurrences (GcalCalendarMonitor *self)
{
g_return_val_if_fail (GCAL_IS_CALENDAR_MONITOR (self), TRUE);
return self->shared.expand_recurrences;
}
+2
View File
@@ -43,4 +43,6 @@ void gcal_calendar_monitor_set_filter (GcalCalendarMo
gboolean gcal_calendar_monitor_is_complete (GcalCalendarMonitor *self);
gboolean gcal_calendar_monitor_get_expand_recurrences (GcalCalendarMonitor *self);
G_END_DECLS
+40 -1
View File
@@ -46,6 +46,7 @@ struct _GcalTimeline
GHashTable *calendars; /* GcalCalendar* -> GcalCalendarMonitor* */
gboolean complete;
gboolean expand_recurrences;
GListStore *calendar_monitors;
GListModel *events_model;
@@ -61,6 +62,7 @@ enum
{
PROP_0,
PROP_COMPLETE,
PROP_EXPAND_RECURRENCES,
PROP_FILTER,
N_PROPS
};
@@ -74,6 +76,7 @@ static GParamSpec *properties [N_PROPS] = { NULL, };
typedef struct
{
GcalTimeline *timeline;
GcalRange *range;
GtkFilterListModel *events;
GtkSortListModel *sorted_events;
@@ -103,6 +106,9 @@ event_in_subscriber_range_func (gpointer item,
if (!data->range)
return FALSE;
if (!data->timeline->expand_recurrences && gcal_event_has_recurrence (item))
return TRUE;
return gcal_event_overlaps (item, data->range);
}
@@ -159,6 +165,7 @@ subscriber_data_new (GcalTimeline *self,
g_autoptr (SubscriberData) data = NULL;
data = g_new0 (SubscriberData, 1);
data->timeline = self;
data->range = gcal_timeline_subscriber_get_range (subscriber);
filter = gtk_custom_filter_new (event_in_subscriber_range_func, data, NULL);
@@ -502,6 +509,10 @@ gcal_timeline_get_property (GObject *object,
g_value_set_boolean (value, self->complete);
break;
case PROP_EXPAND_RECURRENCES:
g_value_set_boolean (value, self->expand_recurrences);
break;
case PROP_FILTER:
g_value_set_string (value, self->filter);
break;
@@ -521,6 +532,10 @@ gcal_timeline_set_property (GObject *object,
switch (prop_id)
{
case PROP_EXPAND_RECURRENCES:
self->expand_recurrences = g_value_get_boolean (value);
break;
case PROP_FILTER:
gcal_timeline_set_filter (self, g_value_get_string (value));
break;
@@ -550,6 +565,17 @@ gcal_timeline_class_init (GcalTimelineClass *klass)
FALSE,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GcalTimeline:expand-recurrences:
*
* Whether to expand recurrences into multiple instances.
*/
properties[PROP_EXPAND_RECURRENCES] = g_param_spec_boolean ("expand-recurrences",
"Expand recurrences",
"Expand recurrences",
TRUE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
/**
* GcalTimeline::filter:
*
@@ -568,6 +594,7 @@ static void
gcal_timeline_init (GcalTimeline *self)
{
self->augmentation_factor = 1.0;
self->expand_recurrences = TRUE;
self->cancellable = g_cancellable_new ();
self->calendars = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
@@ -635,7 +662,10 @@ gcal_timeline_add_calendar (GcalTimeline *self,
GCAL_TRACE_MSG ("Adding calendar '%s' to timeline %p", gcal_calendar_get_name (calendar), self);
monitor = gcal_calendar_monitor_new (calendar);
monitor = g_object_new (GCAL_TYPE_CALENDAR_MONITOR,
"calendar", calendar,
"expand-recurrences", self->expand_recurrences,
NULL);
g_signal_connect (monitor, "notify::complete", G_CALLBACK (on_calendar_monitor_completed_cb), self);
g_hash_table_insert (self->calendars, calendar, g_object_ref (monitor));
g_list_store_append (self->calendar_monitors, monitor);
@@ -768,3 +798,12 @@ gcal_timeline_is_complete (GcalTimeline *self)
return self->complete;
}
gboolean
gcal_timeline_get_expand_recurrences (GcalTimeline *self)
{
g_return_val_if_fail (GCAL_IS_TIMELINE (self), TRUE);
return self->expand_recurrences;
}
+2
View File
@@ -56,4 +56,6 @@ void gcal_timeline_set_filter (GcalTimeline
gboolean gcal_timeline_is_complete (GcalTimeline *self);
gboolean gcal_timeline_get_expand_recurrences (GcalTimeline *self);
G_END_DECLS