Files
federated-calendar/src/core/gcal-timeline.c
T
Georges Basile Stavracas Neto da84b727b3 core/subscriber: Pass old event on update
Now that we keep precise control over GcalEvent instances, and
can use simple pointer comparisons everywhere, GcalMonthView
implemented its update function as a simple remove + add pair.

The problem is that we're not passing the old event instance,
so removals never actually happen, since the new instance is
not yet available to subscribers.

Pass old event in additio to the new one, and use it where it
makes sense.

Closes https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/1073
2023-08-18 10:41:41 -03:00

1115 lines
32 KiB
C

/* gcal-timeline.c
*
* Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "GcalTimeline"
#include "gcal-calendar.h"
#include "gcal-calendar-monitor.h"
#include "gcal-context.h"
#include "gcal-date-time-utils.h"
#include "gcal-debug.h"
#include "gcal-event.h"
#include "gcal-range-tree.h"
#include "gcal-timeline.h"
#include "gcal-timeline-subscriber.h"
#include <libedataserver/libedataserver.h>
#define BATCH_SIZE 5
typedef enum
{
ADD_EVENT,
UPDATE_EVENT,
REMOVE_EVENT,
} QueueEvent;
typedef struct
{
GcalTimeline *timeline;
QueueEvent queue_event;
GcalTimelineSubscriber *subscriber;
GcalEvent *event;
GcalEvent *old_event;
gboolean update_range_tree;
} QueueData;
typedef struct
{
GSource parent;
GcalTimeline *timeline;
} TimelineSource;
struct _GcalTimeline
{
GObject parent_instance;
gulong update_range_idle_id;
GcalRange *range;
GcalRangeTree *events;
gchar *filter;
GHashTable *calendars; /* GcalCalendar* -> GcalCalendarMonitor* */
guint completed_calendars;
GHashTable *subscribers; /* GcalTimelineSubscriber* -> SubscriberData* */
GcalRangeTree *subscriber_ranges;
GCancellable *cancellable;
GHashTable *queued_adds;
GQueue *event_queue;
GSource *timeline_source;
GcalContext *context;
};
G_DEFINE_TYPE (GcalTimeline, gcal_timeline, G_TYPE_OBJECT)
enum
{
PROP_0,
PROP_COMPLETE,
PROP_CONTEXT,
PROP_FILTER,
N_PROPS
};
static GParamSpec *properties [N_PROPS] = { NULL, };
/*
* Auxiliary methods
*/
static gchar*
format_subscriber_event_id (GcalTimelineSubscriber *subscriber,
GcalEvent *event)
{
return g_strdup_printf ("%s:%s", G_OBJECT_TYPE_NAME (subscriber), gcal_event_get_uid (event));
}
static void
queue_data_free (QueueData *queue_data)
{
g_clear_object (&queue_data->subscriber);
g_clear_object (&queue_data->old_event);
g_clear_object (&queue_data->event);
g_free (queue_data);
}
static void
queue_event_data (GcalTimeline *self,
QueueEvent queue_event,
GcalTimelineSubscriber *subscriber,
GcalEvent *event,
GcalEvent *old_event,
gboolean update_range_tree)
{
g_autofree gchar *subscriber_event_id = NULL;
QueueData *queue_data;
queue_data = g_new0 (QueueData, 1);
queue_data->timeline = self;
queue_data->queue_event = queue_event;
queue_data->subscriber = subscriber ? g_object_ref (subscriber) : NULL;
queue_data->event = event ? g_object_ref (event) : NULL;
queue_data->old_event = old_event ? g_object_ref (old_event) : NULL;
queue_data->update_range_tree = update_range_tree;
if (subscriber)
{
subscriber_event_id = format_subscriber_event_id (subscriber, event);
switch (queue_event)
{
case ADD_EVENT:
case UPDATE_EVENT:
break;
case REMOVE_EVENT:
{
GList *queued_add_link;
queued_add_link = g_hash_table_lookup (self->queued_adds, subscriber_event_id);
if (queued_add_link)
{
QueueData *queued_add = queued_add_link->data;
GCAL_TRACE_MSG ("Removing ADD_EVENT for event '%s' (%s) from event queue",
gcal_event_get_summary (event),
subscriber_event_id);
g_hash_table_remove (self->queued_adds, subscriber_event_id);
g_queue_delete_link (self->event_queue, queued_add_link);
queue_data_free (queued_add);
return;
}
}
break;
}
}
g_queue_push_tail (self->event_queue, queue_data);
if (subscriber)
{
switch (queue_event)
{
case ADD_EVENT:
g_hash_table_insert (self->queued_adds,
g_steal_pointer (&subscriber_event_id),
g_queue_peek_tail_link (self->event_queue));
break;
case UPDATE_EVENT:
case REMOVE_EVENT:
break;
}
}
}
static inline gboolean
is_timeline_complete (GcalTimeline *self)
{
return self->completed_calendars == g_hash_table_size (self->calendars);
}
static void
add_event_to_subscriber (GcalTimelineSubscriber *subscriber,
GcalEvent *event)
{
GCAL_TRACE_MSG ("Adding event %s to subscriber %s",
gcal_event_get_uid (event),
G_OBJECT_TYPE_NAME (subscriber));
GCAL_TIMELINE_SUBSCRIBER_GET_IFACE (subscriber)->add_event (subscriber, event);
}
static void
update_subscriber_event (GcalTimelineSubscriber *subscriber,
GcalEvent *old_event,
GcalEvent *event)
{
GCAL_TRACE_MSG ("Updating event '%s' (%s) at subscriber %s",
gcal_event_get_summary (event),
gcal_event_get_uid (event),
G_OBJECT_TYPE_NAME (subscriber));
GCAL_TIMELINE_SUBSCRIBER_GET_IFACE (subscriber)->update_event (subscriber, old_event, event);
}
static void
remove_event_from_subscriber (GcalTimelineSubscriber *subscriber,
GcalEvent *event)
{
GCAL_TRACE_MSG ("Removing event '%s' (%s) from subscriber %s",
gcal_event_get_summary (event),
gcal_event_get_uid (event),
G_OBJECT_TYPE_NAME (subscriber));
GCAL_TIMELINE_SUBSCRIBER_GET_IFACE (subscriber)->remove_event (subscriber, event);
}
static void
update_range (GcalTimeline *self)
{
GcalTimelineSubscriber *subscriber;
GHashTableIter iter;
gboolean has_subscribers;
gboolean range_changed;
GCAL_ENTRY;
range_changed = FALSE;
has_subscribers = g_hash_table_size (self->subscribers) > 0;
if (has_subscribers)
{
g_autoptr (GcalRange) new_range = NULL;
g_hash_table_iter_init (&iter, self->subscribers);
while (g_hash_table_iter_next (&iter, (gpointer*) &subscriber, NULL))
{
g_autoptr (GcalRange) subscriber_range = NULL;
g_autoptr (GcalRange) union_range = NULL;
subscriber_range = gcal_timeline_subscriber_get_range (subscriber);
if (new_range)
{
union_range = gcal_range_union (subscriber_range, new_range);
g_clear_pointer (&new_range, gcal_range_unref);
new_range = g_steal_pointer (&union_range);
}
else
{
new_range = g_steal_pointer (&subscriber_range);
}
}
if (!self->range || gcal_range_compare (self->range, new_range) != 0)
{
g_clear_pointer (&self->range, gcal_range_unref);
self->range = g_steal_pointer (&new_range);
range_changed = TRUE;
}
}
else if (self->range)
{
g_clear_pointer (&self->range, gcal_range_unref);
range_changed = TRUE;
}
if (range_changed)
{
GcalCalendarMonitor *monitor;
g_hash_table_iter_init (&iter, self->calendars);
while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &monitor))
gcal_calendar_monitor_set_range (monitor, self->range);
}
GCAL_EXIT;
}
static void
calculate_changed_events (GcalTimeline *self,
GcalTimelineSubscriber *subscriber,
GcalRange *old_range,
GcalRange *new_range)
{
g_autoptr (GPtrArray) events_to_remove = NULL;
g_autoptr (GPtrArray) events_to_add = NULL;
GcalRangeOverlap overlap;
gint range_diff;
gint i;
overlap = gcal_range_calculate_overlap (new_range, old_range, NULL);
if (overlap == GCAL_RANGE_NO_OVERLAP)
{
GCAL_TRACE_MSG ("Ranges don't overlap, doing a full cleanup");
events_to_remove = gcal_range_tree_get_data_at_range (self->events, old_range);
events_to_add = gcal_range_tree_get_data_at_range (self->events, new_range);
}
else
{
g_autoptr (GDateTime) old_range_start = NULL;
g_autoptr (GDateTime) old_range_end = NULL;
g_autoptr (GDateTime) new_range_start = NULL;
g_autoptr (GDateTime) new_range_end = NULL;
GCAL_TRACE_MSG ("Ranges overlap, doing a diff");
events_to_add = g_ptr_array_new ();
events_to_remove = g_ptr_array_new ();
old_range_start = gcal_range_get_start (old_range);
old_range_end = gcal_range_get_end (old_range);
new_range_start = gcal_range_get_start (new_range);
new_range_end = gcal_range_get_end (new_range);
/* Start ranges diff */
range_diff = g_date_time_compare (old_range_start, new_range_start);
if (range_diff != 0)
{
g_autoptr (GPtrArray) events = NULL;
if (range_diff < 0)
{
g_autoptr (GcalRange) range = gcal_range_new (old_range_start, new_range_start, GCAL_RANGE_DEFAULT);
/* Removed */
events = gcal_range_tree_get_data_at_range (self->events, range);
if (events)
g_ptr_array_extend (events_to_remove, events, NULL, NULL);
}
else if (range_diff > 0)
{
g_autoptr (GcalRange) range = gcal_range_new (new_range_start, old_range_start, GCAL_RANGE_DEFAULT);
/* Added */
events = gcal_range_tree_get_data_at_range (self->events, range);
if (events)
g_ptr_array_extend (events_to_add, events, NULL, NULL);
}
}
/* End ranges diff */
range_diff = g_date_time_compare (old_range_end, new_range_end);
if (range_diff != 0)
{
g_autoptr (GPtrArray) events = NULL;
if (range_diff < 0)
{
g_autoptr (GcalRange) range = gcal_range_new (old_range_end, new_range_end, GCAL_RANGE_DEFAULT);
events = gcal_range_tree_get_data_at_range (self->events, range);
if (events)
g_ptr_array_extend (events_to_add, events, NULL, NULL);
}
else if (range_diff > 0)
{
g_autoptr (GcalRange) range = gcal_range_new (new_range_end, old_range_end, GCAL_RANGE_DEFAULT);
events = gcal_range_tree_get_data_at_range (self->events, range);
if (events)
g_ptr_array_extend (events_to_remove, events, NULL, NULL);
}
}
}
for (i = 0; events_to_remove && i < events_to_remove->len; i++)
{
GcalEvent *event = g_ptr_array_index (events_to_remove, i);
GCAL_TRACE_MSG ("Removing event from subscriber %s due to time range change (event: '%s' (%s))",
G_OBJECT_TYPE_NAME (subscriber),
gcal_event_get_summary (event),
gcal_event_get_uid (event));
queue_event_data (self, REMOVE_EVENT, subscriber, event, NULL, FALSE);
}
for (i = 0; events_to_add && i < events_to_add->len; i++)
{
GcalEvent *event = g_ptr_array_index (events_to_add, i);
GCAL_TRACE_MSG ("Queueing event addition for subscriber %s (event: '%s' (%s))",
G_OBJECT_TYPE_NAME (subscriber),
gcal_event_get_summary (event),
gcal_event_get_uid (event));
queue_event_data (self, ADD_EVENT, subscriber, event, NULL, FALSE);
}
}
static void
add_cached_events_to_subscriber (GcalTimeline *self,
GcalTimelineSubscriber *subscriber)
{
g_autoptr (GcalRange) subscriber_range = NULL;
g_autoptr (GPtrArray) events_to_add = NULL;
gint i;
GCAL_ENTRY;
subscriber_range = gcal_timeline_subscriber_get_range (subscriber);
events_to_add = gcal_range_tree_get_data_at_range (self->events, subscriber_range);
for (i = 0; events_to_add && i < events_to_add->len; i++)
{
GcalEvent *event = g_ptr_array_index (events_to_add, i);
GCAL_TRACE_MSG ("Queueing event addition for subscriber %s (event: '%s' (%s))",
G_OBJECT_TYPE_NAME (subscriber),
gcal_event_get_summary (event),
gcal_event_get_uid (event));
queue_event_data (self, ADD_EVENT, subscriber, event, NULL, FALSE);
}
GCAL_EXIT;
}
static void
update_subscriber_range (GcalTimeline *self,
GcalTimelineSubscriber *subscriber)
{
g_autoptr (GcalRange) old_range = NULL;
g_autoptr (GcalRange) new_range = NULL;
GCAL_ENTRY;
old_range = g_hash_table_lookup (self->subscribers, subscriber);
g_assert (old_range != NULL);
new_range = gcal_timeline_subscriber_get_range (subscriber);
/* Diff new and old event ranges */
calculate_changed_events (self, subscriber, old_range, new_range);
/* Update the subscriber range */
gcal_range_tree_remove_data (self->subscriber_ranges, subscriber);
gcal_range_tree_add_range (self->subscriber_ranges, new_range, subscriber);
g_hash_table_insert (self->subscribers, g_object_ref (subscriber), g_steal_pointer (&new_range));
g_steal_pointer (&old_range);
GCAL_EXIT;
}
static void
update_calendar_monitor_filters (GcalTimeline *self)
{
GcalCalendarMonitor *monitor;
GHashTableIter iter;
g_hash_table_iter_init (&iter, self->calendars);
while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &monitor))
gcal_calendar_monitor_set_filter (monitor, self->filter);
}
/*
* GcalCalendarMonitorListener events
*/
static void
calendar_monitor_add_events_cb (GcalCalendarMonitor *monitor,
GPtrArray *events,
gpointer user_data)
{
GcalTimeline *self;
GCAL_ENTRY;
self = GCAL_TIMELINE (user_data);
for (guint i = 0; i < events->len; i++)
{
g_autoptr (GPtrArray) subscribers_at_range = NULL;
GcalRange *event_range;
GcalEvent *event;
event = g_ptr_array_index (events, i);
event_range = gcal_event_get_range (event);
/* Add to all subscribers within the event range */
subscribers_at_range = gcal_range_tree_get_data_at_range (self->subscriber_ranges, event_range);
queue_event_data (self, ADD_EVENT, NULL, event, NULL, TRUE);
for (guint j = 0; subscribers_at_range && j < subscribers_at_range->len; j++)
{
GcalTimelineSubscriber *subscriber = g_ptr_array_index (subscribers_at_range, j);
queue_event_data (self, ADD_EVENT, subscriber, event, NULL, FALSE);
}
}
GCAL_EXIT;
}
static void
calendar_monitor_update_events_cb (GcalCalendarMonitor *monitor,
GPtrArray *old_events,
GPtrArray *events,
gpointer user_data)
{
GcalTimeline *self;
GCAL_ENTRY;
g_assert (old_events->len == events->len);
self = GCAL_TIMELINE (user_data);
for (guint i = 0; i < events->len; i++)
{
g_autoptr (GPtrArray) subscribers_at_range = NULL;
GcalRange *event_range;
GcalEvent *old_event;
GcalEvent *event;
event = g_ptr_array_index (events, i);
old_event = g_ptr_array_index (old_events, i);
event_range = gcal_event_get_range (event);
/* Add to all subscribers within the event range */
subscribers_at_range = gcal_range_tree_get_data_at_range (self->subscriber_ranges, event_range);
queue_event_data (self, UPDATE_EVENT, NULL, event, old_event, TRUE);
for (guint j = 0; subscribers_at_range && j < subscribers_at_range->len; j++)
{
GcalTimelineSubscriber *subscriber = g_ptr_array_index (subscribers_at_range, j);
queue_event_data (self, UPDATE_EVENT, subscriber, event, old_event, FALSE);
}
}
GCAL_EXIT;
}
static void
calendar_monitor_remove_events_cb (GcalCalendarMonitor *monitor,
GPtrArray *events,
gpointer user_data)
{
GcalTimeline *self;
GCAL_ENTRY;
self = GCAL_TIMELINE (user_data);
for (guint i = 0; i < events->len; i++)
{
g_autoptr (GPtrArray) subscribers_at_range = NULL;
GcalRange *event_range;
GcalEvent *event;
event = g_ptr_array_index (events, i);
event_range = gcal_event_get_range (event);
/* Add to all subscribers within the event range */
subscribers_at_range = gcal_range_tree_get_data_at_range (self->subscriber_ranges, event_range);
for (guint j = 0; subscribers_at_range && j < subscribers_at_range->len; j++)
{
GcalTimelineSubscriber *subscriber = g_ptr_array_index (subscribers_at_range, j);
queue_event_data (self, REMOVE_EVENT, subscriber, event, NULL, FALSE);
}
queue_event_data (self, REMOVE_EVENT, NULL, event, NULL, TRUE);
}
GCAL_EXIT;
}
static const GcalCalendarMonitorListener monitor_listener = {
calendar_monitor_add_events_cb,
calendar_monitor_update_events_cb,
calendar_monitor_remove_events_cb,
};
/*
* Callbacks
*/
static void
on_calendar_monitor_completed_cb (GcalCalendarMonitor *monitor,
GParamSpec *pspec,
GcalTimeline *self)
{
GCAL_ENTRY;
if (gcal_calendar_monitor_is_complete (monitor))
self->completed_calendars++;
else
self->completed_calendars--;
g_assert (self->completed_calendars <= g_hash_table_size (self->calendars));
if (is_timeline_complete (self))
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COMPLETE]);
GCAL_EXIT;
}
static gboolean
update_timeline_range_in_idle_cb (gpointer user_data)
{
GcalTimeline *self;
GCAL_ENTRY;
self = GCAL_TIMELINE (user_data);
update_range (self);
self->update_range_idle_id = 0;
GCAL_RETURN (G_SOURCE_REMOVE);
}
static void
on_subscriber_range_changed_cb (GcalTimelineSubscriber *subscriber,
GcalTimeline *self)
{
update_subscriber_range (self, subscriber);
if (self->update_range_idle_id)
return;
self->update_range_idle_id = g_idle_add (update_timeline_range_in_idle_cb, self);
}
static gboolean
timeline_source_prepare (GSource *source,
gint *timeout)
{
TimelineSource *timeline_source;
GcalTimeline *self;
timeline_source = (TimelineSource*) source;
self = timeline_source->timeline;
return self->event_queue->length > 0;
}
static gboolean
timeline_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
TimelineSource *timeline_source;
GcalTimeline *self;
gint processed_events;
GCAL_ENTRY;
processed_events = 0;
timeline_source = (TimelineSource*) source;
self = timeline_source->timeline;
while (processed_events < BATCH_SIZE && !g_queue_is_empty (self->event_queue))
{
GcalTimelineSubscriber *subscriber;
g_autofree gchar *subscriber_event_id = NULL;
GcalRange *event_range;
QueueData *queue_data;
GcalEvent *event;
queue_data = g_queue_pop_head (self->event_queue);
event = queue_data->event;
subscriber = queue_data->subscriber;
event_range = gcal_event_get_range (event);
if (subscriber)
subscriber_event_id = format_subscriber_event_id (subscriber, event);
/* The subscriber may have been removed already */
if (subscriber && !g_hash_table_contains (self->subscribers, subscriber))
{
g_hash_table_remove (self->queued_adds, subscriber_event_id);
queue_data_free (queue_data);
continue;
}
switch (queue_data->queue_event)
{
case ADD_EVENT:
GCAL_TRACE_MSG ("Processing ADD_EVENT for event '%s' (%s) (in queued_adds: %d, update range tree: %d)",
gcal_event_get_summary (event),
gcal_event_get_uid (event),
subscriber_event_id && g_hash_table_contains (self->queued_adds, subscriber_event_id),
queue_data->update_range_tree);
if (queue_data->update_range_tree)
gcal_range_tree_add_range (self->events, event_range, g_object_ref (event));
if (subscriber)
{
add_event_to_subscriber (subscriber, event);
g_hash_table_remove (self->queued_adds, subscriber_event_id);
}
break;
case UPDATE_EVENT:
{
GCAL_TRACE_MSG ("Processing UPDATE_EVENT for event '%s' (%s) (update range tree: %d)",
gcal_event_get_summary (event),
gcal_event_get_uid (event),
queue_data->update_range_tree);
if (queue_data->update_range_tree)
{
GcalRange *old_event_range;
/* Remove the old event */
old_event_range = gcal_event_get_range (queue_data->old_event);
gcal_range_tree_remove_range (self->events, old_event_range, queue_data->old_event);
gcal_range_tree_add_range (self->events, event_range, g_object_ref (event));
}
if (subscriber)
update_subscriber_event (subscriber, queue_data->old_event, event);
}
break;
case REMOVE_EVENT:
GCAL_TRACE_MSG ("Processing REMOVE_EVENT for event '%s' (%s) (update range tree: %d)",
gcal_event_get_summary (event),
gcal_event_get_uid (event),
queue_data->update_range_tree);
if (subscriber)
remove_event_from_subscriber (subscriber, event);
if (queue_data->update_range_tree)
gcal_range_tree_remove_range (self->events, event_range, event);
break;
}
queue_data_free (queue_data);
processed_events++;
}
GCAL_RETURN (G_SOURCE_CONTINUE);
}
static GSourceFuncs timeline_source_funcs =
{
timeline_source_prepare,
NULL,
timeline_source_dispatch,
NULL,
};
/*
* GObject overrides
*/
static void
gcal_timeline_finalize (GObject *object)
{
GcalTimeline *self = (GcalTimeline *)object;
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
if (self->update_range_idle_id > 0)
{
g_source_remove (self->update_range_idle_id);
self->update_range_idle_id = 0;
}
g_clear_pointer (&self->events, gcal_range_tree_unref);
g_clear_pointer (&self->calendars, g_hash_table_destroy);
g_clear_pointer (&self->subscribers, g_hash_table_destroy);
g_clear_pointer (&self->queued_adds, g_hash_table_destroy);
g_clear_pointer (&self->subscriber_ranges, gcal_range_tree_unref);
g_source_destroy (self->timeline_source);
g_clear_pointer (&self->timeline_source, g_source_unref);
if (self->event_queue)
{
g_queue_free_full (self->event_queue, (GDestroyNotify) queue_data_free);
self->event_queue = NULL;
}
G_OBJECT_CLASS (gcal_timeline_parent_class)->finalize (object);
}
static void
gcal_timeline_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GcalTimeline *self = GCAL_TIMELINE (object);
switch (prop_id)
{
case PROP_COMPLETE:
g_value_set_boolean (value, is_timeline_complete (self));
break;
case PROP_CONTEXT:
g_value_set_object (value, self->context);
break;
case PROP_FILTER:
g_value_set_string (value, self->filter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gcal_timeline_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GcalTimeline *self = GCAL_TIMELINE (object);
switch (prop_id)
{
case PROP_CONTEXT:
g_assert (self->context == NULL);
self->context = g_value_get_object (value);
break;
case PROP_FILTER:
gcal_timeline_set_filter (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gcal_timeline_class_init (GcalTimelineClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gcal_timeline_finalize;
object_class->get_property = gcal_timeline_get_property;
object_class->set_property = gcal_timeline_set_property;
/**
* GcalTimeline::complete:
*
* Whether all calendars in this timeline are completed.
*/
properties[PROP_COMPLETE] = g_param_spec_boolean ("complete",
"Complete",
"Complete",
FALSE,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GcalSearchEngine::context:
*
* The #GcalContext of the application.
*/
properties[PROP_CONTEXT] = g_param_spec_object ("context",
"Data context",
"Data context",
GCAL_TYPE_CONTEXT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GcalTimeline::filter:
*
* The search filter.
*/
properties[PROP_FILTER] = g_param_spec_string ("filter",
"Filter",
"Filter",
NULL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
gcal_timeline_init (GcalTimeline *self)
{
TimelineSource *timeline_source;
self->cancellable = g_cancellable_new ();
self->events = gcal_range_tree_new_with_free_func (g_object_unref);
self->calendars = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
self->subscribers = g_hash_table_new_full (NULL, NULL, g_object_unref, (GDestroyNotify) gcal_range_unref);
self->subscriber_ranges = gcal_range_tree_new ();
self->event_queue = g_queue_new ();
self->queued_adds = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
/* Timeline source */
timeline_source = (TimelineSource*) g_source_new (&timeline_source_funcs, sizeof (TimelineSource));
timeline_source->timeline = self;
self->timeline_source = (GSource*) timeline_source;
g_source_set_name (self->timeline_source, "Timeline Source");
g_source_set_priority (self->timeline_source, G_PRIORITY_DEFAULT_IDLE);
g_source_attach (self->timeline_source, g_main_context_default ());
}
/**
* gcal_timeline_new:
* @context: a #GcalContext
*
* Creates a new #GcalTimeline.
*
* Returns: (transfer full): a #GcalTimeline
*/
GcalTimeline*
gcal_timeline_new (GcalContext *context)
{
return g_object_new (GCAL_TYPE_TIMELINE,
"context", context,
NULL);
}
/**
* gcal_timeline_add_calendar:
* @self: a #GcalTimeline
* @calendar: a #GcalCalendar
*
* Adds @calendar to @self. The events in @calendar will be added
* respectively to
*
*/
void
gcal_timeline_add_calendar (GcalTimeline *self,
GcalCalendar *calendar)
{
g_autoptr (GcalCalendarMonitor) monitor = NULL;
g_return_if_fail (GCAL_IS_TIMELINE (self));
g_return_if_fail (GCAL_IS_CALENDAR (calendar));
GCAL_ENTRY;
if (g_hash_table_contains (self->calendars, calendar))
return;
GCAL_TRACE_MSG ("Adding calendar '%s' to timeline %p", gcal_calendar_get_name (calendar), self);
monitor = gcal_calendar_monitor_new (calendar, &monitor_listener, self);
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));
if (self->range)
gcal_calendar_monitor_set_range (monitor, self->range);
GCAL_EXIT;
}
void
gcal_timeline_remove_calendar (GcalTimeline *self,
GcalCalendar *calendar)
{
g_return_if_fail (GCAL_IS_TIMELINE (self));
g_return_if_fail (GCAL_IS_CALENDAR (calendar));
GCAL_ENTRY;
g_object_ref (calendar);
if (g_hash_table_remove (self->calendars, calendar))
GCAL_TRACE_MSG ("Removing calendar '%s' from timeline %p", gcal_calendar_get_name (calendar), self);
g_object_unref (calendar);
GCAL_EXIT;
}
void
gcal_timeline_add_subscriber (GcalTimeline *self,
GcalTimelineSubscriber *subscriber)
{
g_autoptr (GcalRange) subscriber_range = NULL;
g_return_if_fail (GCAL_IS_TIMELINE (self));
g_return_if_fail (GCAL_IS_TIMELINE_SUBSCRIBER (subscriber));
GCAL_ENTRY;
if (g_hash_table_contains (self->subscribers, subscriber))
GCAL_RETURN ();
g_debug ("Adding subscriber %s to timeline %p", G_OBJECT_TYPE_NAME (subscriber), self);
subscriber_range = gcal_timeline_subscriber_get_range (subscriber);
g_hash_table_insert (self->subscribers, g_object_ref (subscriber), gcal_range_ref (subscriber_range));
g_signal_connect_object (subscriber,
"range-changed",
G_CALLBACK (on_subscriber_range_changed_cb),
self,
0);
add_cached_events_to_subscriber (self, subscriber);
update_subscriber_range (self, subscriber);
update_range (self);
GCAL_EXIT;
}
void
gcal_timeline_remove_subscriber (GcalTimeline *self,
GcalTimelineSubscriber *subscriber)
{
g_return_if_fail (GCAL_IS_TIMELINE (self));
GCAL_ENTRY;
if (!g_hash_table_contains (self->subscribers, subscriber))
GCAL_RETURN ();
g_debug ("Removing subscriber %s from timeline %p", G_OBJECT_TYPE_NAME (subscriber), self);
g_signal_handlers_disconnect_by_func (subscriber, on_subscriber_range_changed_cb, self);
g_hash_table_remove (self->subscribers, subscriber);
if (g_hash_table_size (self->subscribers) == 0)
g_queue_clear_full (self->event_queue, (GDestroyNotify) queue_data_free);
gcal_range_tree_remove_data (self->subscriber_ranges, subscriber);
update_range (self);
GCAL_EXIT;
}
GPtrArray*
gcal_timeline_get_events_at_range (GcalTimeline *self,
GDateTime *range_start,
GDateTime *range_end)
{
g_autoptr (GPtrArray) events_at_range = NULL;
g_autoptr (GcalRange) range = NULL;
g_return_val_if_fail (GCAL_IS_TIMELINE (self), NULL);
g_return_val_if_fail (range_start != NULL, NULL);
g_return_val_if_fail (range_end != NULL, NULL);
range = gcal_range_new (range_start, range_end, GCAL_RANGE_DEFAULT);
events_at_range = gcal_range_tree_get_data_at_range (self->events, range);
return g_steal_pointer (&events_at_range);
}
const gchar*
gcal_timeline_get_filter (GcalTimeline *self)
{
g_return_val_if_fail (GCAL_IS_TIMELINE (self), NULL);
return self->filter;
}
void
gcal_timeline_set_filter (GcalTimeline *self,
const gchar *filter)
{
g_return_if_fail (GCAL_IS_TIMELINE (self));
GCAL_ENTRY;
if (g_strcmp0 (self->filter, filter) == 0)
GCAL_RETURN ();
g_debug ("Setting timeline filter to \"%s\"", filter);
g_clear_pointer (&self->filter, g_free);
self->filter = g_strdup (filter);
update_calendar_monitor_filters (self);
GCAL_EXIT;
}
gboolean
gcal_timeline_is_complete (GcalTimeline *self)
{
g_return_val_if_fail (GCAL_IS_TIMELINE (self), FALSE);
return is_timeline_complete (self);
}