The calendar view thread can start spinning its own mainloop before g_thread_new() returns, and in that case we have a NULL thread. Check that either the GThread is what we expect it to be, OR, the thread is NULL and there are no messages in the queue.
1464 lines
42 KiB
C
1464 lines
42 KiB
C
/* gcal-calendar-monitor.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 "GcalCalendarMonitor"
|
|
|
|
#include "gcal-calendar-monitor.h"
|
|
#include "gcal-core-macros.h"
|
|
#include "gcal-date-time-utils.h"
|
|
#include "gcal-debug.h"
|
|
#include "gcal-event.h"
|
|
|
|
#include <gio/gio.h>
|
|
#include <libecal/libecal.h>
|
|
|
|
typedef struct
|
|
{
|
|
GcalCalendarMonitor *monitor;
|
|
GPtrArray *events;
|
|
GPtrArray *event_ids;
|
|
gboolean complete;
|
|
} IdleData;
|
|
|
|
typedef enum
|
|
{
|
|
INVALID_EVENT,
|
|
CREATE_VIEW,
|
|
REMOVE_VIEW,
|
|
RANGE_UPDATED,
|
|
FILTER_UPDATED,
|
|
QUIT,
|
|
} MonitorThreadEvent;
|
|
|
|
struct _GcalCalendarMonitor
|
|
{
|
|
GObject parent;
|
|
|
|
GThread *thread;
|
|
GCancellable *cancellable;
|
|
GMainContext *thread_context;
|
|
GMainContext *main_context;
|
|
|
|
GAsyncQueue *messages;
|
|
GcalCalendar *calendar;
|
|
gboolean complete;
|
|
|
|
const GcalCalendarMonitorListener *listener;
|
|
gpointer listener_user_data;
|
|
|
|
/*
|
|
* These fields are only accessed on the monitor thread, and
|
|
* never on the main thread.
|
|
*/
|
|
struct {
|
|
gboolean populated;
|
|
GPtrArray *events_to_add;
|
|
ECalClientView *view;
|
|
} monitor_thread;
|
|
|
|
/*
|
|
* Accessing any of the fields below must be happen inside
|
|
* a locked muxed.
|
|
*/
|
|
struct {
|
|
GRWLock lock;
|
|
GHashTable *events; /* gchar* -> GcalEvent* */
|
|
GcalRange *range;
|
|
gchar *filter;
|
|
} shared;
|
|
};
|
|
|
|
static gboolean add_events_to_timeline_in_idle_cb (gpointer user_data);
|
|
static gboolean update_events_in_idle_cb (gpointer user_data);
|
|
static gboolean remove_events_from_timeline_in_idle_cb (gpointer user_data);
|
|
static gboolean complete_in_idle_cb (gpointer user_data);
|
|
|
|
G_DEFINE_TYPE (GcalCalendarMonitor, gcal_calendar_monitor, G_TYPE_OBJECT)
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CALENDAR,
|
|
PROP_COMPLETE,
|
|
N_PROPS
|
|
};
|
|
|
|
static GParamSpec *properties [N_PROPS] = { NULL, };
|
|
|
|
/*
|
|
* Threads
|
|
*
|
|
* These methods must *never* be executed in the main thread.
|
|
*/
|
|
|
|
static gchar*
|
|
build_subscriber_filter (GcalRange *range,
|
|
const gchar *filter)
|
|
{
|
|
g_autoptr (GDateTime) inclusive_range_end = NULL;
|
|
g_autoptr (GDateTime) utc_range_start = NULL;
|
|
g_autoptr (GDateTime) utc_range_end = NULL;
|
|
g_autoptr (GDateTime) range_start = NULL;
|
|
g_autoptr (GDateTime) range_end = NULL;
|
|
g_autofree gchar *start_str = NULL;
|
|
g_autofree gchar *end_str = NULL;
|
|
g_autofree gchar *result = NULL;
|
|
|
|
/*
|
|
* XXX: E-D-S ISO8601 parser is incomplete and doesn't accept the output of
|
|
* g_date_time_format_iso8601().
|
|
*/
|
|
|
|
range_start = gcal_range_get_start (range);
|
|
utc_range_start = g_date_time_to_utc (range_start);
|
|
start_str = g_date_time_format (utc_range_start, "%Y%m%dT%H%M%SZ");
|
|
|
|
range_end = gcal_range_get_end (range);
|
|
inclusive_range_end = g_date_time_add_seconds (range_end, -1);
|
|
utc_range_end = g_date_time_to_utc (inclusive_range_end);
|
|
end_str = g_date_time_format (utc_range_end, "%Y%m%dT%H%M%SZ");
|
|
|
|
if (filter)
|
|
{
|
|
result = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") (make-time \"%s\")) %s)",
|
|
start_str,
|
|
end_str,
|
|
filter);
|
|
}
|
|
else
|
|
{
|
|
result = g_strdup_printf ("(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))",
|
|
start_str,
|
|
end_str);
|
|
}
|
|
|
|
return g_steal_pointer (&result);
|
|
}
|
|
|
|
static void
|
|
maybe_init_event_arrays (GcalCalendarMonitor *self)
|
|
{
|
|
if (self->monitor_thread.populated)
|
|
return;
|
|
|
|
if (!self->monitor_thread.events_to_add)
|
|
self->monitor_thread.events_to_add = g_ptr_array_new_with_free_func (g_object_unref);
|
|
}
|
|
|
|
static GcalRange*
|
|
get_monitor_ranges (GcalCalendarMonitor *self)
|
|
{
|
|
g_autoptr (GcalRange) range = NULL;
|
|
|
|
g_rw_lock_reader_lock (&self->shared.lock);
|
|
range = gcal_range_copy (self->shared.range);
|
|
g_rw_lock_reader_unlock (&self->shared.lock);
|
|
|
|
return g_steal_pointer (&range);
|
|
}
|
|
|
|
static void
|
|
idle_data_free (IdleData *data)
|
|
{
|
|
g_clear_object (&data->monitor);
|
|
g_clear_pointer (&data->events, g_ptr_array_unref);
|
|
g_clear_pointer (&data->event_ids, g_ptr_array_unref);
|
|
g_free (data);
|
|
}
|
|
|
|
static void
|
|
add_events_in_idle (GcalCalendarMonitor *self,
|
|
GPtrArray *events)
|
|
{
|
|
IdleData *idle_data;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
idle_data = g_new0 (IdleData, 1);
|
|
idle_data->monitor = g_object_ref (self);
|
|
idle_data->events = g_ptr_array_ref (events);
|
|
|
|
g_main_context_invoke_full (self->main_context,
|
|
G_PRIORITY_DEFAULT_IDLE,
|
|
add_events_to_timeline_in_idle_cb,
|
|
idle_data,
|
|
(GDestroyNotify) idle_data_free);
|
|
}
|
|
|
|
static void
|
|
update_events_in_idle (GcalCalendarMonitor *self,
|
|
GPtrArray *events)
|
|
{
|
|
IdleData *idle_data;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
idle_data = g_new0 (IdleData, 1);
|
|
idle_data->monitor = g_object_ref (self);
|
|
idle_data->events = g_ptr_array_ref (events);
|
|
|
|
g_main_context_invoke_full (self->main_context,
|
|
G_PRIORITY_DEFAULT_IDLE,
|
|
update_events_in_idle_cb,
|
|
idle_data,
|
|
(GDestroyNotify) idle_data_free);
|
|
}
|
|
|
|
static void
|
|
remove_events_in_idle (GcalCalendarMonitor *self,
|
|
GPtrArray *events)
|
|
{
|
|
IdleData *idle_data;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
idle_data = g_new0 (IdleData, 1);
|
|
idle_data->monitor = g_object_ref (self);
|
|
idle_data->event_ids = g_ptr_array_ref (events);
|
|
|
|
g_main_context_invoke_full (self->main_context,
|
|
G_PRIORITY_DEFAULT_IDLE,
|
|
remove_events_from_timeline_in_idle_cb,
|
|
idle_data,
|
|
(GDestroyNotify) idle_data_free);
|
|
}
|
|
|
|
static void
|
|
set_complete_in_idle (GcalCalendarMonitor *self,
|
|
gboolean complete)
|
|
{
|
|
IdleData *idle_data;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
idle_data = g_new0 (IdleData, 1);
|
|
idle_data->monitor = g_object_ref (self);
|
|
idle_data->complete = complete;
|
|
|
|
g_main_context_invoke_full (self->main_context,
|
|
G_PRIORITY_DEFAULT_IDLE,
|
|
complete_in_idle_cb,
|
|
idle_data,
|
|
(GDestroyNotify) idle_data_free);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GcalCalendarMonitor *monitor;
|
|
GPtrArray *expanded_events;
|
|
} GenerateRecurrencesData;
|
|
|
|
static gboolean
|
|
client_instance_generated_cb (ICalComponent *icomponent,
|
|
ICalTime *instance_start,
|
|
ICalTime *instance_end,
|
|
gpointer user_data,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr (GcalEvent) event = NULL;
|
|
g_autoptr (GError) local_error = NULL;
|
|
GenerateRecurrencesData *data;
|
|
GcalCalendarMonitor *self;
|
|
ECalComponent *ecomponent;
|
|
|
|
data = user_data;
|
|
self = data->monitor;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
|
return FALSE;
|
|
|
|
ecomponent = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomponent));
|
|
if (!ecomponent)
|
|
return TRUE;
|
|
|
|
event = gcal_event_new (self->calendar, ecomponent, &local_error);
|
|
g_clear_object (&ecomponent);
|
|
if (local_error)
|
|
{
|
|
g_propagate_error (error, local_error);
|
|
return TRUE;
|
|
}
|
|
|
|
g_ptr_array_add (data->expanded_events, g_steal_pointer (&event));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_client_view_objects_added_cb (ECalClientView *view,
|
|
const GSList *objects,
|
|
GcalCalendarMonitor *self)
|
|
{
|
|
g_autoptr (GPtrArray) components_to_expand = NULL;
|
|
g_autoptr (GPtrArray) events_to_add = NULL;
|
|
g_autoptr (GcalRange) range = NULL;
|
|
const GSList *l;
|
|
gint i;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
range = get_monitor_ranges (self);
|
|
|
|
maybe_init_event_arrays (self);
|
|
components_to_expand = g_ptr_array_new ();
|
|
events_to_add = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
for (l = objects; l; l = l->next)
|
|
{
|
|
g_autoptr (GcalEvent) event = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
ICalComponent *icomponent;
|
|
ECalComponent *ecomponent;
|
|
|
|
icomponent = l->data;
|
|
|
|
if (g_cancellable_is_cancelled (self->cancellable))
|
|
return;
|
|
|
|
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))
|
|
{
|
|
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;
|
|
}
|
|
|
|
ecomponent = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomponent));
|
|
|
|
if (!ecomponent)
|
|
continue;
|
|
|
|
event = gcal_event_new (self->calendar, ecomponent, &error);
|
|
g_clear_object (&ecomponent);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("Error creating event: %s", error->message);
|
|
continue;
|
|
}
|
|
|
|
if (!self->monitor_thread.populated)
|
|
g_ptr_array_add (self->monitor_thread.events_to_add, g_object_ref (event));
|
|
else
|
|
g_ptr_array_add (events_to_add, g_object_ref (event));
|
|
}
|
|
|
|
/* Recurrent events */
|
|
if (components_to_expand->len > 0)
|
|
{
|
|
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;
|
|
|
|
/* Generate the instances */
|
|
for (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;
|
|
|
|
if (!self->monitor_thread.populated)
|
|
recurrences_data.expanded_events = self->monitor_thread.events_to_add;
|
|
else
|
|
recurrences_data.expanded_events = events_to_add;
|
|
|
|
#if GCAL_ENABLE_TRACE
|
|
old_size = recurrences_data.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),
|
|
recurrences_data.expanded_events->len - old_size,
|
|
range_str);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (events_to_add->len > 0)
|
|
add_events_in_idle (self, events_to_add);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
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 (GPtrArray) event_ids_to_remove = NULL;
|
|
g_autoptr (GPtrArray) events_to_update = NULL;
|
|
g_autoptr (GcalRange) range = NULL;
|
|
const GSList *l;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
if (!self->monitor_thread.populated && self->monitor_thread.events_to_add)
|
|
{
|
|
g_clear_pointer (&self->monitor_thread.events_to_add, g_ptr_array_unref);
|
|
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);
|
|
events_to_update = g_ptr_array_new_with_free_func (g_object_unref);
|
|
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;
|
|
|
|
if (!icomponent || !i_cal_component_get_uid (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) &&
|
|
!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))
|
|
{
|
|
GHashTableIter iter;
|
|
const gchar *aux;
|
|
|
|
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);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("Error creating event: %s", error->message);
|
|
continue;
|
|
}
|
|
|
|
g_ptr_array_add (events_to_update, g_steal_pointer (&event));
|
|
}
|
|
|
|
/* Recurrent events */
|
|
if (components_to_expand->len > 0)
|
|
{
|
|
g_autoptr (GPtrArray) expanded_events = NULL;
|
|
g_autoptr (GPtrArray) events_to_add = 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);
|
|
events_to_add = 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)
|
|
{
|
|
g_hash_table_remove (events_to_remove, gcal_event_get_uid (event));
|
|
g_ptr_array_add (events_to_update, g_steal_pointer (&event));
|
|
}
|
|
else
|
|
{
|
|
g_ptr_array_add (events_to_add, g_steal_pointer (&event));
|
|
}
|
|
}
|
|
|
|
if (events_to_add->len > 0)
|
|
add_events_in_idle (self, events_to_add);
|
|
}
|
|
|
|
if (events_to_update->len > 0)
|
|
update_events_in_idle (self, events_to_update);
|
|
|
|
/* Now remove lingering events */
|
|
event_ids_to_remove = g_hash_table_steal_all_keys (events_to_remove);
|
|
if (event_ids_to_remove->len > 0)
|
|
remove_events_in_idle (self, event_ids_to_remove);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
on_client_view_objects_removed_cb (ECalClientView *view,
|
|
const GSList *objects,
|
|
GcalCalendarMonitor *self)
|
|
{
|
|
g_autoptr (GPtrArray) event_ids = NULL;
|
|
const GSList *l;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
event_ids = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
for (l = objects; l; l = l->next)
|
|
{
|
|
g_autofree gchar *event_id = NULL;
|
|
ECalComponentId *component_id;
|
|
|
|
if (g_cancellable_is_cancelled (self->cancellable))
|
|
return;
|
|
|
|
component_id = l->data;
|
|
|
|
if (e_cal_component_id_get_rid (component_id))
|
|
{
|
|
event_id = g_strdup_printf ("%s:%s:%s",
|
|
gcal_calendar_get_id (self->calendar),
|
|
e_cal_component_id_get_uid (component_id),
|
|
e_cal_component_id_get_rid (component_id));
|
|
}
|
|
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))
|
|
g_ptr_array_add (event_ids, g_strdup (aux));
|
|
}
|
|
}
|
|
|
|
g_ptr_array_add (event_ids, g_strdup (event_id));
|
|
}
|
|
|
|
if (event_ids->len > 0)
|
|
remove_events_in_idle (self, event_ids);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
on_client_view_complete_cb (ECalClientView *view,
|
|
const GError *error,
|
|
GcalCalendarMonitor *self)
|
|
{
|
|
g_autoptr (GPtrArray) events_to_add = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
g_assert (!self->monitor_thread.populated);
|
|
|
|
events_to_add = g_steal_pointer (&self->monitor_thread.events_to_add);
|
|
|
|
if (events_to_add)
|
|
add_events_in_idle (self, events_to_add);
|
|
|
|
self->monitor_thread.populated = TRUE;
|
|
|
|
set_complete_in_idle (self, TRUE);
|
|
|
|
g_debug ("Finished initial loading of calendar '%s'", gcal_calendar_get_name (self->calendar));
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
create_view (GcalCalendarMonitor *self)
|
|
{
|
|
g_autoptr (GRWLockReaderLocker) reader_locker = NULL;
|
|
g_autofree gchar *filter = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
ECalClientView *view;
|
|
ECalClient *client;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
reader_locker = g_rw_lock_reader_locker_new (&self->shared.lock);
|
|
|
|
g_assert (self->cancellable == NULL);
|
|
self->cancellable = g_cancellable_new ();
|
|
|
|
if (!self->shared.range)
|
|
GCAL_RETURN ();
|
|
|
|
filter = build_subscriber_filter (self->shared.range, self->shared.filter);
|
|
|
|
g_clear_pointer (&reader_locker, g_rw_lock_reader_locker_free);
|
|
|
|
if (!gcal_calendar_get_visible (self->calendar))
|
|
GCAL_RETURN ();
|
|
|
|
client = gcal_calendar_get_client (self->calendar);
|
|
e_cal_client_get_view_sync (client, filter, &view, self->cancellable, &error);
|
|
|
|
if (error)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
g_warning ("Error creating view: %s", error->message);
|
|
GCAL_RETURN ();
|
|
}
|
|
|
|
GCAL_TRACE_MSG ("Initialized ECalClientView with query \"%s\"", filter);
|
|
|
|
g_signal_connect (view, "objects-added", G_CALLBACK (on_client_view_objects_added_cb), self);
|
|
g_signal_connect (view, "objects-modified", G_CALLBACK (on_client_view_objects_modified_cb), self);
|
|
g_signal_connect (view, "objects-removed", G_CALLBACK (on_client_view_objects_removed_cb), self);
|
|
g_signal_connect (view, "complete", G_CALLBACK (on_client_view_complete_cb), self);
|
|
|
|
if (g_cancellable_is_cancelled (self->cancellable))
|
|
GCAL_RETURN ();
|
|
|
|
g_debug ("Starting ECalClientView for calendar '%s'", gcal_calendar_get_name (self->calendar));
|
|
|
|
e_cal_client_view_start (view, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("Error starting up view: %s", error->message);
|
|
g_clear_object (&view);
|
|
GCAL_RETURN ();
|
|
}
|
|
|
|
self->monitor_thread.view = g_steal_pointer (&view);
|
|
|
|
set_complete_in_idle (self, FALSE);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
remove_view (GcalCalendarMonitor *self)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
g_clear_object (&self->cancellable);
|
|
|
|
if (!self->monitor_thread.view)
|
|
GCAL_RETURN ();
|
|
|
|
g_debug ("Tearing down view for calendar %s", gcal_calendar_get_id (self->calendar));
|
|
|
|
e_cal_client_view_stop (self->monitor_thread.view, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("Error stopping view: %s", error->message);
|
|
GCAL_RETURN ();
|
|
}
|
|
|
|
g_clear_object (&self->monitor_thread.view);
|
|
self->monitor_thread.populated = FALSE;
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GSource parent;
|
|
GcalCalendarMonitor *monitor;
|
|
GMainLoop *mainloop;
|
|
} MessageQueueSource;
|
|
|
|
static gboolean
|
|
message_queue_source_prepare (GSource *source,
|
|
gint *timeout)
|
|
{
|
|
GcalCalendarMonitor *self;
|
|
MessageQueueSource *queue_source;
|
|
|
|
queue_source = (MessageQueueSource*) source;
|
|
self = queue_source->monitor;
|
|
|
|
/*
|
|
* The prepare function can be called before g_thread_new()
|
|
* returns, and that's valid. However, in that case, no messages
|
|
* should be available in the queue, as the main thread didn't
|
|
* return execution to the mainloop yet. Protect against that.
|
|
*/
|
|
g_assert (GCAL_IS_THREAD (self->thread) ||
|
|
(!self->thread && g_async_queue_length (self->messages) == 0));
|
|
|
|
return g_async_queue_length (self->messages) > 0;
|
|
}
|
|
|
|
static gboolean
|
|
message_queue_source_dispatch (GSource *source,
|
|
GSourceFunc callback,
|
|
gpointer user_data)
|
|
{
|
|
GcalCalendarMonitor *self;
|
|
MessageQueueSource *queue_source;
|
|
MonitorThreadEvent event;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
queue_source = (MessageQueueSource*) source;
|
|
self = queue_source->monitor;
|
|
event = GPOINTER_TO_INT (g_async_queue_pop (self->messages));
|
|
|
|
g_assert (GCAL_IS_THREAD (self->thread));
|
|
|
|
switch (event)
|
|
{
|
|
case FILTER_UPDATED:
|
|
case RANGE_UPDATED:
|
|
remove_view (self);
|
|
create_view (self);
|
|
break;
|
|
|
|
case CREATE_VIEW:
|
|
create_view (self);
|
|
break;
|
|
|
|
case REMOVE_VIEW:
|
|
remove_view (self);
|
|
break;
|
|
|
|
case INVALID_EVENT:
|
|
case QUIT:
|
|
remove_view (self);
|
|
g_main_loop_quit (queue_source->mainloop);
|
|
GCAL_RETURN (G_SOURCE_REMOVE);
|
|
}
|
|
|
|
GCAL_RETURN (G_SOURCE_CONTINUE);
|
|
}
|
|
|
|
static GSourceFuncs monitor_queue_source_funcs =
|
|
{
|
|
message_queue_source_prepare,
|
|
NULL,
|
|
message_queue_source_dispatch,
|
|
NULL,
|
|
};
|
|
|
|
static MessageQueueSource*
|
|
views_monitor_source_new (GcalCalendarMonitor *self,
|
|
GMainLoop *mainloop)
|
|
{
|
|
MessageQueueSource *queue_source;
|
|
GSource *source;
|
|
|
|
/* Prepare the message queue source */
|
|
source = g_source_new (&monitor_queue_source_funcs, sizeof (MessageQueueSource));
|
|
queue_source = (MessageQueueSource*) source;
|
|
queue_source->monitor = self;
|
|
queue_source->mainloop = mainloop;
|
|
g_source_set_name (source, "Message Queue Source");
|
|
|
|
return queue_source;
|
|
}
|
|
|
|
static gpointer
|
|
calendar_view_thread_func (gpointer data)
|
|
{
|
|
g_autoptr (GMainLoop) mainloop = NULL;
|
|
MessageQueueSource *queue_source;
|
|
GcalCalendarMonitor *self;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
self = data;
|
|
mainloop = g_main_loop_new (self->thread_context, FALSE);
|
|
g_main_context_push_thread_default (self->thread_context);
|
|
|
|
/* Events source */
|
|
queue_source = views_monitor_source_new (self, mainloop);
|
|
g_source_attach ((GSource*) queue_source, self->thread_context);
|
|
|
|
g_main_loop_run (mainloop);
|
|
g_main_loop_quit (mainloop);
|
|
|
|
g_main_context_pop_thread_default (self->thread_context);
|
|
|
|
GCAL_RETURN (NULL);
|
|
}
|
|
|
|
/*
|
|
* Auxiliary methods
|
|
*/
|
|
|
|
static void
|
|
notify_view_thread (GcalCalendarMonitor *self,
|
|
MonitorThreadEvent event)
|
|
{
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
g_async_queue_push (self->messages, GINT_TO_POINTER (event));
|
|
g_main_context_wakeup (self->thread_context);
|
|
}
|
|
|
|
static void
|
|
maybe_spawn_view_thread (GcalCalendarMonitor *self)
|
|
{
|
|
g_autofree gchar *thread_name = NULL;
|
|
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
if (self->thread || !self->shared.range)
|
|
return;
|
|
|
|
thread_name = g_strdup_printf ("GcalCalendarMonitor (%s)", gcal_calendar_get_id (self->calendar));
|
|
self->thread = g_thread_new (thread_name, calendar_view_thread_func, self);
|
|
|
|
g_debug ("Spawning thread %s", thread_name);
|
|
}
|
|
|
|
static void
|
|
remove_events_outside_range (GcalCalendarMonitor *self,
|
|
GcalRange *range)
|
|
{
|
|
g_autoptr (GRWLockWriterLocker) writer_locker = NULL;
|
|
g_autoptr (GPtrArray) events_to_remove = NULL;
|
|
GHashTableIter iter;
|
|
GcalEvent *event;
|
|
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
GCAL_TRACE_MSG ("Removing events outside range from monitor");
|
|
|
|
writer_locker = g_rw_lock_writer_locker_new (&self->shared.lock);
|
|
|
|
events_to_remove = g_ptr_array_new_full (g_hash_table_size (self->shared.events),
|
|
g_object_unref);
|
|
|
|
g_hash_table_iter_init (&iter, self->shared.events);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &event))
|
|
{
|
|
GcalRange *event_range = gcal_event_get_range (event);
|
|
|
|
if (gcal_range_calculate_overlap (range, event_range, NULL) != GCAL_RANGE_NO_OVERLAP)
|
|
continue;
|
|
|
|
g_ptr_array_add (events_to_remove, event);
|
|
g_hash_table_iter_steal (&iter);
|
|
}
|
|
|
|
if (events_to_remove->len > 0)
|
|
self->listener->remove_events (self, events_to_remove, self->listener_user_data);
|
|
}
|
|
|
|
static void
|
|
remove_all_events (GcalCalendarMonitor *self)
|
|
{
|
|
g_autoptr (GRWLockWriterLocker) writer_locker = NULL;
|
|
g_autoptr (GPtrArray) events_to_remove = NULL;
|
|
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
GCAL_TRACE_MSG ("Removing all events from view");
|
|
|
|
writer_locker = g_rw_lock_writer_locker_new (&self->shared.lock);
|
|
|
|
events_to_remove = g_hash_table_steal_all_values (self->shared.events);
|
|
|
|
if (events_to_remove->len > 0)
|
|
self->listener->remove_events (self, events_to_remove, self->listener_user_data);
|
|
}
|
|
|
|
static void
|
|
set_complete (GcalCalendarMonitor *self,
|
|
gboolean complete)
|
|
{
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
if (self->complete == complete)
|
|
return;
|
|
|
|
GCAL_TRACE_MSG ("Setting complete to %s", complete ? "TRUE" : "FALSE");
|
|
|
|
self->complete = complete;
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COMPLETE]);
|
|
}
|
|
|
|
|
|
/*
|
|
* Callbacks
|
|
*/
|
|
|
|
static gboolean
|
|
add_events_to_timeline_in_idle_cb (gpointer user_data)
|
|
{
|
|
g_autoptr (GRWLockWriterLocker) writer_locker = NULL;
|
|
g_autoptr (GPtrArray) events_to_add = NULL;
|
|
GcalCalendarMonitor *self;
|
|
GPtrArray *events;
|
|
IdleData *idle_data;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
idle_data = user_data;
|
|
self = idle_data->monitor;
|
|
events = idle_data->events;
|
|
g_assert (idle_data->event_ids == NULL);
|
|
|
|
events_to_add = g_ptr_array_sized_new (events->len);
|
|
|
|
writer_locker = g_rw_lock_writer_locker_new (&self->shared.lock);
|
|
for (guint i = 0; i < events->len; i++)
|
|
{
|
|
GcalEvent *event;
|
|
const gchar *uid;
|
|
|
|
event = g_ptr_array_index (events, i);
|
|
uid = gcal_event_get_uid (event);
|
|
|
|
if (!g_hash_table_contains (self->shared.events, uid))
|
|
{
|
|
g_hash_table_insert (self->shared.events, g_strdup (uid), g_object_ref (event));
|
|
g_ptr_array_add (events_to_add, event);
|
|
}
|
|
}
|
|
|
|
if (events_to_add->len > 0)
|
|
self->listener->add_events (self, events_to_add, self->listener_user_data);
|
|
|
|
GCAL_RETURN (G_SOURCE_REMOVE);
|
|
}
|
|
|
|
static gboolean
|
|
update_events_in_idle_cb (gpointer user_data)
|
|
{
|
|
g_autoptr (GRWLockWriterLocker) writer_locker = NULL;
|
|
g_autoptr (GPtrArray) old_events = NULL;
|
|
g_autoptr (GPtrArray) new_events = NULL;
|
|
g_autoptr (GPtrArray) events = NULL;
|
|
GcalCalendarMonitor *self;
|
|
IdleData *idle_data;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
idle_data = user_data;
|
|
self = idle_data->monitor;
|
|
events = g_steal_pointer (&idle_data->events);
|
|
g_assert (idle_data->event_ids == NULL);
|
|
|
|
old_events = g_ptr_array_new_full (events->len, g_object_unref);
|
|
new_events = g_ptr_array_sized_new (events->len);
|
|
|
|
writer_locker = g_rw_lock_writer_locker_new (&self->shared.lock);
|
|
for (guint i = 0; i < events->len; i++)
|
|
{
|
|
g_autoptr (GcalEvent) old_event = NULL;
|
|
g_autofree gchar *old_event_id = NULL;
|
|
GcalEvent *event;
|
|
const gchar *event_id;
|
|
|
|
event = g_ptr_array_index (events, i);
|
|
event_id = gcal_event_get_uid (event);
|
|
|
|
if (g_hash_table_steal_extended (self->shared.events,
|
|
event_id,
|
|
(gpointer*) &old_event_id,
|
|
(gpointer*) &old_event))
|
|
{
|
|
g_hash_table_insert (self->shared.events, g_strdup (event_id), g_object_ref (event));
|
|
g_ptr_array_add (old_events, g_steal_pointer (&old_event));
|
|
g_ptr_array_add (new_events, event);
|
|
}
|
|
}
|
|
|
|
g_assert (old_events->len == new_events->len);
|
|
|
|
if (new_events->len > 0)
|
|
self->listener->update_events (self, old_events, new_events, self->listener_user_data);
|
|
|
|
GCAL_RETURN (G_SOURCE_REMOVE);
|
|
}
|
|
|
|
static gboolean
|
|
remove_events_from_timeline_in_idle_cb (gpointer user_data)
|
|
{
|
|
g_autoptr (GRWLockWriterLocker) writer_locker = NULL;
|
|
g_autoptr (GPtrArray) events_to_remove = NULL;
|
|
g_autoptr (GPtrArray) event_ids = NULL;
|
|
GcalCalendarMonitor *self;
|
|
IdleData *idle_data;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
idle_data = user_data;
|
|
self = idle_data->monitor;
|
|
event_ids = g_steal_pointer (&idle_data->event_ids);
|
|
g_assert (idle_data->events == NULL);
|
|
|
|
events_to_remove = g_ptr_array_new_full (event_ids->len, g_object_unref);
|
|
|
|
writer_locker = g_rw_lock_writer_locker_new (&self->shared.lock);
|
|
|
|
for (guint i = 0; i < event_ids->len; i++)
|
|
{
|
|
g_autoptr (GcalEvent) event = NULL;
|
|
const gchar *event_id;
|
|
|
|
event_id = g_ptr_array_index (event_ids, i);
|
|
event = g_hash_table_lookup (self->shared.events, event_id);
|
|
|
|
if (event)
|
|
{
|
|
/* Keep the event alive until the listener process it*/
|
|
g_ptr_array_add (events_to_remove, g_object_ref (event));
|
|
g_hash_table_remove (self->shared.events, event_id);
|
|
}
|
|
}
|
|
|
|
if (events_to_remove->len > 0)
|
|
self->listener->remove_events (self, events_to_remove, self->listener_user_data);
|
|
|
|
GCAL_RETURN (G_SOURCE_REMOVE);
|
|
}
|
|
|
|
static void
|
|
on_calendar_visible_changed_cb (GcalCalendar *calendar,
|
|
GParamSpec *pspec,
|
|
GcalCalendarMonitor *self)
|
|
{
|
|
if (gcal_calendar_get_visible (calendar))
|
|
{
|
|
notify_view_thread (self, CREATE_VIEW);
|
|
}
|
|
else
|
|
{
|
|
remove_all_events (self);
|
|
notify_view_thread (self, REMOVE_VIEW);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
complete_in_idle_cb (gpointer user_data)
|
|
{
|
|
GcalCalendarMonitor *self;
|
|
IdleData *idle_data;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_MAIN_THREAD ());
|
|
|
|
idle_data = user_data;
|
|
self = idle_data->monitor;
|
|
g_assert (idle_data->events == NULL);
|
|
g_assert (idle_data->event_ids == NULL);
|
|
|
|
set_complete (self, idle_data->complete);
|
|
|
|
GCAL_RETURN (G_SOURCE_REMOVE);
|
|
}
|
|
|
|
|
|
/*
|
|
* GObject overrides
|
|
*/
|
|
|
|
static void
|
|
gcal_calendar_monitor_finalize (GObject *object)
|
|
{
|
|
GcalCalendarMonitor *self = (GcalCalendarMonitor *)object;
|
|
|
|
g_cancellable_cancel (self->cancellable);
|
|
notify_view_thread (self, QUIT);
|
|
|
|
if (self->thread)
|
|
{
|
|
g_thread_join (self->thread);
|
|
self->thread = NULL;
|
|
}
|
|
|
|
remove_all_events (self);
|
|
|
|
g_clear_object (&self->calendar);
|
|
g_clear_pointer (&self->thread_context, g_main_context_unref);
|
|
g_clear_pointer (&self->main_context, g_main_context_unref);
|
|
g_clear_pointer (&self->messages, g_async_queue_unref);
|
|
g_clear_pointer (&self->shared.events, g_hash_table_destroy);
|
|
g_clear_pointer (&self->shared.filter, g_free);
|
|
g_clear_pointer (&self->shared.range, gcal_range_unref);
|
|
|
|
G_OBJECT_CLASS (gcal_calendar_monitor_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gcal_calendar_monitor_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalCalendarMonitor *self = GCAL_CALENDAR_MONITOR (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CALENDAR:
|
|
g_value_set_object (value, self->calendar);
|
|
break;
|
|
|
|
case PROP_COMPLETE:
|
|
g_value_set_boolean (value, self->complete);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_calendar_monitor_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalCalendarMonitor *self = GCAL_CALENDAR_MONITOR (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CALENDAR:
|
|
g_assert (self->calendar == NULL);
|
|
self->calendar = g_value_dup_object (value);
|
|
g_signal_connect (self->calendar,
|
|
"notify::visible",
|
|
G_CALLBACK (on_calendar_visible_changed_cb),
|
|
self);
|
|
break;
|
|
|
|
case PROP_COMPLETE:
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_calendar_monitor_class_init (GcalCalendarMonitorClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = gcal_calendar_monitor_finalize;
|
|
object_class->get_property = gcal_calendar_monitor_get_property;
|
|
object_class->set_property = gcal_calendar_monitor_set_property;
|
|
|
|
properties[PROP_CALENDAR] = g_param_spec_object ("calendar",
|
|
"Calendar",
|
|
"Calendar to be monitored",
|
|
GCAL_TYPE_CALENDAR,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
|
|
properties[PROP_COMPLETE] = g_param_spec_boolean ("complete",
|
|
"Complete",
|
|
"Whether",
|
|
FALSE,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
|
}
|
|
|
|
static void
|
|
gcal_calendar_monitor_init (GcalCalendarMonitor *self)
|
|
{
|
|
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 ();
|
|
self->messages = g_async_queue_new ();
|
|
self->complete = FALSE;
|
|
|
|
g_rw_lock_init (&self->shared.lock);
|
|
}
|
|
|
|
GcalCalendarMonitor*
|
|
gcal_calendar_monitor_new (GcalCalendar *calendar,
|
|
const GcalCalendarMonitorListener *listener,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr (GcalCalendarMonitor) monitor = NULL;
|
|
|
|
g_assert (calendar != NULL && GCAL_IS_CALENDAR (calendar));
|
|
g_assert (listener != NULL);
|
|
|
|
monitor = g_object_new (GCAL_TYPE_CALENDAR_MONITOR,
|
|
"calendar", calendar,
|
|
NULL);
|
|
|
|
monitor->listener = listener;
|
|
monitor->listener_user_data = user_data;
|
|
|
|
return g_steal_pointer (&monitor);
|
|
}
|
|
|
|
/**
|
|
* gcal_calendar_monitor_set_range:
|
|
* @self: a #GcalCalendarMonitor
|
|
* @range: a #GcalRange
|
|
*
|
|
* Updates the range of @self. This usually will result in
|
|
* @self creating a new view, and gathering the events from
|
|
* the events server.
|
|
*/
|
|
void
|
|
gcal_calendar_monitor_set_range (GcalCalendarMonitor *self,
|
|
GcalRange *range)
|
|
{
|
|
g_autoptr (GRWLockWriterLocker) writer_locker = NULL;
|
|
gboolean range_changed;
|
|
|
|
g_return_if_fail (GCAL_IS_CALENDAR_MONITOR (self));
|
|
|
|
GCAL_ENTRY;
|
|
|
|
writer_locker = g_rw_lock_writer_locker_new (&self->shared.lock);
|
|
|
|
range_changed =
|
|
!self->shared.range ||
|
|
!range ||
|
|
gcal_range_calculate_overlap (self->shared.range, range, NULL) != GCAL_RANGE_EQUAL;
|
|
|
|
if (!range_changed)
|
|
GCAL_RETURN ();
|
|
|
|
g_clear_pointer (&self->shared.range, gcal_range_unref);
|
|
self->shared.range = range ? gcal_range_copy (range) : NULL;
|
|
|
|
g_clear_pointer (&writer_locker, g_rw_lock_writer_locker_free);
|
|
|
|
maybe_spawn_view_thread (self);
|
|
remove_events_outside_range (self, range);
|
|
|
|
g_cancellable_cancel (self->cancellable);
|
|
|
|
if (gcal_calendar_get_visible (self->calendar))
|
|
notify_view_thread (self, RANGE_UPDATED);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* gcal_calendar_monitor_get_cached_event:
|
|
* @self: a #GcalCalendarMonitor
|
|
* @event_id: the id of the #GcalEvent
|
|
*
|
|
* Returns: (transfer full)(nullable): a #GcalEvent, or %NULL
|
|
*/
|
|
GcalEvent*
|
|
gcal_calendar_monitor_get_cached_event (GcalCalendarMonitor *self,
|
|
const gchar *event_id)
|
|
{
|
|
GcalEvent *event;
|
|
|
|
g_return_val_if_fail (GCAL_IS_CALENDAR_MONITOR (self), NULL);
|
|
g_return_val_if_fail (event_id, NULL);
|
|
|
|
g_rw_lock_reader_lock (&self->shared.lock);
|
|
event = g_hash_table_lookup (self->shared.events, event_id);
|
|
g_rw_lock_reader_unlock (&self->shared.lock);
|
|
|
|
return event ? g_object_ref (event) : NULL;
|
|
}
|
|
|
|
void
|
|
gcal_calendar_monitor_set_filter (GcalCalendarMonitor *self,
|
|
const gchar *filter)
|
|
{
|
|
g_return_if_fail (GCAL_IS_CALENDAR_MONITOR (self));
|
|
|
|
g_rw_lock_writer_lock (&self->shared.lock);
|
|
|
|
g_clear_pointer (&self->shared.filter, g_free);
|
|
self->shared.filter = g_strdup (filter);
|
|
|
|
g_rw_lock_writer_unlock (&self->shared.lock);
|
|
|
|
remove_all_events (self);
|
|
|
|
if (gcal_calendar_get_visible (self->calendar))
|
|
notify_view_thread (self, FILTER_UPDATED);
|
|
}
|
|
|
|
gboolean
|
|
gcal_calendar_monitor_is_complete (GcalCalendarMonitor *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_CALENDAR_MONITOR (self), FALSE);
|
|
|
|
return self->complete;
|
|
}
|