From bed94963d0727768f289af9b5fd44b82b2da5275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erick=20P=C3=A9rez=20Castellanos?= Date: Wed, 15 Oct 2014 20:36:04 -0400 Subject: [PATCH] build: added ECalDataModel related files Implements an ECalDataModel object from evolution courtesy of Milan Crha ECalDataModel hides the details of events retrieving from calendars. Provides a time range and a subscriber system allowing objects implements the necessary callbacks to be notified of events happening on the selected time range. --- configure.ac | 21 +- src/Makefile.am | 4 + src/e-cal-data-model-subscriber.c | 161 ++ src/e-cal-data-model-subscriber.h | 81 + src/e-cal-data-model.c | 2927 +++++++++++++++++++++++++++++ src/e-cal-data-model.h | 151 ++ 6 files changed, 3336 insertions(+), 9 deletions(-) create mode 100644 src/e-cal-data-model-subscriber.c create mode 100644 src/e-cal-data-model-subscriber.h create mode 100644 src/e-cal-data-model.c create mode 100644 src/e-cal-data-model.h diff --git a/configure.ac b/configure.ac index 3e7d23f3..92aa37e8 100644 --- a/configure.ac +++ b/configure.ac @@ -67,14 +67,15 @@ AC_PATH_PROG(GLIB_MKENUMS, glib-mkenums) GNOME_COMPILE_WARNINGS(maximum) -MAINTAINER_COMPILER_FLAGS="$MAINTAINER_COMPILER_FLAGS \ - -Wall -Wcast-align -Wuninitialized \ - -Wno-strict-aliasing -Wempty-body -Wformat \ - -Wformat-security -Wformat-nonliteral -Winit-self \ - -Wdeclaration-after-statement -Wvla \ - -Wpointer-arith -Wmissing-declarations \ - -Wcast-align \ - -Wredundant-decls" +MAINTAINER_COMPILER_FLAGS="\ + $MAINTAINER_COMPILER_FLAGS\ + -Wall -Wcast-align -Wuninitialized\ + -Wno-strict-aliasing -Wempty-body -Wformat\ + -Wformat-security -Wformat-nonliteral -Winit-self\ + -Wdeclaration-after-statement -Wvla\ + -Wpointer-arith -Wmissing-declarations\ + -Wcast-align -Wmissing-prototypes\ + -Wredundant-decls" # strip leading spaces MAINTAINER_COMPILER_FLAGS=${MAINTAINER_COMPILER_FLAGS#* } @@ -114,7 +115,9 @@ echo " prefix: ${prefix} compiler: ${CC} - flags: ${CFLAGS} ${CPPFLAGS} ${CALENDAR_CFLAGS} ${LDFLAGS} ${CALENDAR_LIBS} ${LIBS} + flags: ${CFLAGS} ${CPPFLAGS} ${LDFLAGS} + flags: ${MAINTAINER_COMPILER_FLAGS} + flags: ${CALENDAR_CFLAGS} ${CALENDAR_LIBS} ${LIBS} Now type 'make' to build $PACKAGE " diff --git a/src/Makefile.am b/src/Makefile.am index d07d34a3..20d38a7b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,6 +20,10 @@ gnome_calendar_SOURCES = \ $(BUILT_SOURCES) \ e-cell-renderer-color.c \ e-cell-renderer-color.h \ + e-cal-data-model-subscriber.h \ + e-cal-data-model-subscriber.c \ + e-cal-data-model.h \ + e-cal-data-model.c \ main.c \ gcal-application.h \ gcal-application.c \ diff --git a/src/e-cal-data-model-subscriber.c b/src/e-cal-data-model-subscriber.c new file mode 100644 index 00000000..b08b0c12 --- /dev/null +++ b/src/e-cal-data-model-subscriber.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + * + * Authors: Milan Crha + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-cal-data-model-subscriber.h" + +G_DEFINE_INTERFACE (ECalDataModelSubscriber, e_cal_data_model_subscriber, G_TYPE_OBJECT) + +static void +e_cal_data_model_subscriber_default_init (ECalDataModelSubscriberInterface *iface) +{ +} + +/** + * e_cal_data_model_subscriber_component_added: + * @subscriber: an #ECalDataModelSubscriber + * @client: an #ECalClient, which notifies about the component addition + * @icalcomp: an #ECalComponent which was added + * + * Notifies the @subscriber about an added component which belongs + * to the time range used by the @subscriber. + * + * Note: The @subscriber can be frozen during these calls, to be able + * to cumulate multiple changes and propagate them at once. + **/ +void +e_cal_data_model_subscriber_component_added (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp) +{ + ECalDataModelSubscriberInterface *iface; + + g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber)); + g_return_if_fail (E_IS_CAL_COMPONENT (comp)); + + iface = E_CAL_DATA_MODEL_SUBSCRIBER_GET_INTERFACE (subscriber); + g_return_if_fail (iface->component_added != NULL); + + iface->component_added (subscriber, client, comp); +} + +/** + * e_cal_data_model_subscriber_component_modified: + * @subscriber: an #ECalDataModelSubscriber + * @client: an #ECalClient, which notifies about the component modification + * @comp: an #ECalComponent which was modified + * + * Notifies the @subscriber about a modified component which belongs + * to the time range used by the @subscriber. + * + * Note: The @subscriber can be frozen during these calls, to be able + * to cumulate multiple changes and propagate them at once. + **/ +void +e_cal_data_model_subscriber_component_modified (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp) +{ + ECalDataModelSubscriberInterface *iface; + + g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber)); + g_return_if_fail (E_IS_CAL_COMPONENT (comp)); + + iface = E_CAL_DATA_MODEL_SUBSCRIBER_GET_INTERFACE (subscriber); + g_return_if_fail (iface->component_modified != NULL); + + iface->component_modified (subscriber, client, comp); +} + +/** + * e_cal_data_model_subscriber_component_removed: + * @subscriber: an #ECalDataModelSubscriber + * @client: an #ECalClient, which notifies about the component removal + * @uid: UID of a removed component + * @rid: RID of a removed component + * + * Notifies the @subscriber about a removed component identified + * by @uid and @rid. This component may or may not be within + * the time range specified by the @subscriber. + * + * Note: The @subscriber can be frozen during these calls, to be able + * to cumulate multiple changes and propagate them at once. + **/ +void +e_cal_data_model_subscriber_component_removed (ECalDataModelSubscriber *subscriber, + ECalClient *client, + const gchar *uid, + const gchar *rid) +{ + ECalDataModelSubscriberInterface *iface; + + g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber)); + + iface = E_CAL_DATA_MODEL_SUBSCRIBER_GET_INTERFACE (subscriber); + g_return_if_fail (iface->component_removed != NULL); + + iface->component_removed (subscriber, client, uid, rid); +} + +/** + * e_cal_data_model_subscriber_freeze: + * @subscriber: an #ECalDataModelSubscriber + * + * Tells the @subscriber that it'll be notified about multiple + * changes. Once all the notifications are done, + * a e_cal_data_model_subscriber_thaw() is called. + * + * Note: This function can be called multiple times/recursively, with + * the same count of the e_cal_data_model_subscriber_thaw(), thus + * count with it. + **/ +void +e_cal_data_model_subscriber_freeze (ECalDataModelSubscriber *subscriber) +{ + ECalDataModelSubscriberInterface *iface; + + g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber)); + + iface = E_CAL_DATA_MODEL_SUBSCRIBER_GET_INTERFACE (subscriber); + g_return_if_fail (iface->freeze != NULL); + + iface->freeze (subscriber); +} + +/** + * e_cal_data_model_subscriber_thaw: + * @subscriber: an #ECalDataModelSubscriber + * + * A pair function for e_cal_data_model_subscriber_freeze(), which notifies + * about the end of a content update. + **/ +void +e_cal_data_model_subscriber_thaw (ECalDataModelSubscriber *subscriber) +{ + ECalDataModelSubscriberInterface *iface; + + g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber)); + + iface = E_CAL_DATA_MODEL_SUBSCRIBER_GET_INTERFACE (subscriber); + g_return_if_fail (iface->thaw != NULL); + + iface->thaw (subscriber); +} diff --git a/src/e-cal-data-model-subscriber.h b/src/e-cal-data-model-subscriber.h new file mode 100644 index 00000000..8d76fb0f --- /dev/null +++ b/src/e-cal-data-model-subscriber.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + * + * Authors: Milan Crha + */ + +#ifndef E_CAL_DATA_MODEL_SUBSCRIBER_H +#define E_CAL_DATA_MODEL_SUBSCRIBER_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_CAL_DATA_MODEL_SUBSCRIBER \ + (e_cal_data_model_subscriber_get_type ()) +#define E_CAL_DATA_MODEL_SUBSCRIBER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CAL_DATA_MODEL_SUBSCRIBER, ECalDataModelSubscriber)) +#define E_CAL_DATA_MODEL_SUBSCRIBER_INTERFACE(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CAL_DATA_MODEL_SUBSCRIBER, ECalDataModelSubscriberInterface)) +#define E_IS_CAL_DATA_MODEL_SUBSCRIBER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CAL_DATA_MODEL_SUBSCRIBER)) +#define E_IS_CAL_DATA_MODEL_SUBSCRIBER_INTERFACE(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CAL_DATA_MODEL_SUBSCRIBER)) +#define E_CAL_DATA_MODEL_SUBSCRIBER_GET_INTERFACE(obj) \ + (G_TYPE_INSTANCE_GET_INTERFACE \ + ((obj), E_TYPE_CAL_DATA_MODEL_SUBSCRIBER, ECalDataModelSubscriberInterface)) + +G_BEGIN_DECLS + +typedef struct _ECalDataModelSubscriber ECalDataModelSubscriber; +typedef struct _ECalDataModelSubscriberInterface ECalDataModelSubscriberInterface; + +struct _ECalDataModelSubscriberInterface { + GTypeInterface parent_interface; + + void (*component_added) (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp); + void (*component_modified) (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp); + void (*component_removed) (ECalDataModelSubscriber *subscriber, + ECalClient *client, + const gchar *uid, + const gchar *rid); + void (*freeze) (ECalDataModelSubscriber *subscriber); + void (*thaw) (ECalDataModelSubscriber *subscriber); +}; + +GType e_cal_data_model_subscriber_get_type (void) G_GNUC_CONST; +void e_cal_data_model_subscriber_component_added (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp); +void e_cal_data_model_subscriber_component_modified (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp); +void e_cal_data_model_subscriber_component_removed (ECalDataModelSubscriber *subscriber, + ECalClient *client, + const gchar *uid, + const gchar *rid); +void e_cal_data_model_subscriber_freeze (ECalDataModelSubscriber *subscriber); +void e_cal_data_model_subscriber_thaw (ECalDataModelSubscriber *subscriber); + +G_END_DECLS + +#endif /* E_CAL_DATA_MODEL_SUBSCRIBER_H */ diff --git a/src/e-cal-data-model.c b/src/e-cal-data-model.c new file mode 100644 index 00000000..43da454e --- /dev/null +++ b/src/e-cal-data-model.c @@ -0,0 +1,2927 @@ +/* + * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + * + * Authors: Milan Crha + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +/*#include */ + +#include "e-cal-data-model.h" + +#define LOCK_PROPS() g_rec_mutex_lock (&data_model->priv->props_lock) +#define UNLOCK_PROPS() g_rec_mutex_unlock (&data_model->priv->props_lock) + +static void +cal_comp_get_instance_times (ECalClient *client, + icalcomponent *icalcomp, + const icaltimezone *default_zone, + time_t *instance_start, + gboolean *start_is_date, + time_t *instance_end, + gboolean *end_is_date, + GCancellable *cancellable) +{ + struct icaltimetype start_time, end_time; + const icaltimezone *zone = default_zone; + + g_return_if_fail (E_IS_CAL_CLIENT (client)); + g_return_if_fail (icalcomp != NULL); + g_return_if_fail (instance_start != NULL); + g_return_if_fail (instance_end != NULL); + + start_time = icalcomponent_get_dtstart (icalcomp); + end_time = icalcomponent_get_dtend (icalcomp); + + if (start_time.zone) { + zone = start_time.zone; + } else { + icalparameter *param = NULL; + icalproperty *prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY); + + if (prop) { + param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER); + + if (param) { + const gchar *tzid = NULL; + icaltimezone *st_zone = NULL; + + tzid = icalparameter_get_tzid (param); + if (tzid) + e_cal_client_get_timezone_sync (client, tzid, &st_zone, cancellable, NULL); + + if (st_zone) + zone = st_zone; + } + } + } + + *instance_start = icaltime_as_timet_with_zone (start_time, zone); + if (start_is_date) + *start_is_date = start_time.is_date; + + if (end_time.zone) { + zone = end_time.zone; + } else { + icalparameter *param = NULL; + icalproperty *prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY); + + if (prop) { + param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER); + + if (param) { + const gchar *tzid = NULL; + icaltimezone *end_zone = NULL; + + tzid = icalparameter_get_tzid (param); + if (tzid) + e_cal_client_get_timezone_sync (client, tzid, &end_zone, cancellable, NULL); + + if (end_zone) + zone = end_zone; + } + } + + } + + *instance_end = icaltime_as_timet_with_zone (end_time, zone); + if (end_is_date) + *end_is_date = end_time.is_date; +} + +struct _ECalDataModelPrivate { + GThread *main_thread; + ECalDataModelSubmitThreadJobFunc submit_thread_job_func; + GWeakRef *submit_thread_job_responder; + GThreadPool *thread_pool; + + GRecMutex props_lock; /* to guard all the below members */ + + gboolean disposing; + gboolean expand_recurrences; + gchar *filter; + gchar *full_filter; /* to be used with views */ + icaltimezone *zone; + time_t range_start; + time_t range_end; + + GHashTable *clients; /* ESource::uid ~> ECalClient */ + GHashTable *views; /* ECalClient ~> ViewData */ + GSList *subscribers; /* ~> SubscriberData */ + + guint32 views_update_freeze; + gboolean views_update_required; +}; + +enum { + PROP_0, + PROP_EXPAND_RECURRENCES, + PROP_TIMEZONE +}; + +enum { + VIEW_STATE_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE (ECalDataModel, e_cal_data_model, G_TYPE_OBJECT) + +typedef struct _ComponentData { + ECalComponent *component; + time_t instance_start; + time_t instance_end; + gboolean is_detached; +} ComponentData; + +typedef struct _ViewData { + gint ref_count; + GRecMutex lock; + gboolean is_used; + + ECalClient *client; + ECalClientView *view; + gulong objects_added_id; + gulong objects_modified_id; + gulong objects_removed_id; + gulong progress_id; + gulong complete_id; + + GHashTable *components; /* ECalComponentId ~> ComponentData */ + GHashTable *lost_components; /* ECalComponentId ~> ComponentData; when re-running view, valid till 'complete' is received */ + gboolean received_complete; + GSList *to_expand_recurrences; /* icalcomponent */ + GSList *expanded_recurrences; /* ComponentData */ + gint pending_expand_recurrences; /* how many is waiting to be processed */ + + GCancellable *cancellable; +} ViewData; + +typedef struct _SubscriberData { + ECalDataModelSubscriber *subscriber; + time_t range_start; + time_t range_end; +} SubscriberData; + +static ComponentData * +component_data_new (ECalComponent *comp, + time_t instance_start, + time_t instance_end, + gboolean is_detached) +{ + ComponentData *comp_data; + + g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL); + + comp_data = g_new0 (ComponentData, 1); + comp_data->component = g_object_ref (comp); + comp_data->instance_start = instance_start; + comp_data->instance_end = instance_end; + comp_data->is_detached = is_detached; + + return comp_data; +} + +static void +component_data_free (gpointer ptr) +{ + ComponentData *comp_data = ptr; + + if (comp_data) { + g_object_unref (comp_data->component); + g_free (comp_data); + } +} + +static gboolean +component_data_equal (ComponentData *comp_data1, + ComponentData *comp_data2) +{ + icalcomponent *icomp1, *icomp2; + struct icaltimetype tt1, tt2; + gchar *as_str1, *as_str2; + gboolean equal; + + if (comp_data1 == comp_data2) + return TRUE; + + if (!comp_data1 || !comp_data2 || !comp_data1->component || !comp_data2->component) + return FALSE; + + if (comp_data1->instance_start != comp_data2->instance_start || + comp_data1->instance_end != comp_data2->instance_end) + return FALSE; + + icomp1 = e_cal_component_get_icalcomponent (comp_data1->component); + icomp2 = e_cal_component_get_icalcomponent (comp_data2->component); + + if (!icomp1 || !icomp2 || + icalcomponent_get_sequence (icomp1) != icalcomponent_get_sequence (icomp2) || + g_strcmp0 (icalcomponent_get_uid (icomp1), icalcomponent_get_uid (icomp2)) != 0) + return FALSE; + + tt1 = icalcomponent_get_recurrenceid (icomp1); + tt2 = icalcomponent_get_recurrenceid (icomp2); + if ((icaltime_is_valid_time (tt1) ? 1 : 0) != (icaltime_is_valid_time (tt2) ? 1 : 0) || + (icaltime_is_null_time (tt1) ? 1 : 0) != (icaltime_is_null_time (tt2) ? 1 : 0) || + icaltime_compare (tt1, tt2) != 0) + return FALSE; + + tt1 = icalcomponent_get_dtstamp (icomp1); + tt2 = icalcomponent_get_dtstamp (icomp2); + if ((icaltime_is_valid_time (tt1) ? 1 : 0) != (icaltime_is_valid_time (tt2) ? 1 : 0) || + (icaltime_is_null_time (tt1) ? 1 : 0) != (icaltime_is_null_time (tt2) ? 1 : 0) || + icaltime_compare (tt1, tt2) != 0) + return FALSE; + + /* Maybe not so effective compare, but might be still more effective + than updating whole UI with false notifications */ + as_str1 = icalcomponent_as_ical_string_r (icomp1); + as_str2 = icalcomponent_as_ical_string_r (icomp2); + + equal = g_strcmp0 (as_str1, as_str2) == 0; + + g_free (as_str1); + g_free (as_str2); + + return equal; +} + +static ViewData * +view_data_new (ECalClient *client) +{ + ViewData *view_data; + + g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL); + + view_data = g_new0 (ViewData, 1); + view_data->ref_count = 1; + g_rec_mutex_init (&view_data->lock); + view_data->is_used = TRUE; + view_data->client = g_object_ref (client); + view_data->components = g_hash_table_new_full ( + (GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal, + (GDestroyNotify) e_cal_component_free_id, component_data_free); + + return view_data; +} + +static void +view_data_disconnect_view (ViewData *view_data) +{ + if (view_data && view_data->view) { + #define disconnect(x) G_STMT_START { \ + if (view_data->x) { \ + g_signal_handler_disconnect (view_data->view, view_data->x); \ + view_data->x = 0; \ + } \ + } G_STMT_END + + disconnect (objects_added_id); + disconnect (objects_modified_id); + disconnect (objects_removed_id); + disconnect (progress_id); + disconnect (complete_id); + + #undef disconnect + } +} + +static ViewData * +view_data_ref (ViewData *view_data) +{ + g_return_val_if_fail (view_data != NULL, NULL); + + g_atomic_int_inc (&view_data->ref_count); + + return view_data; +} + +static void +view_data_unref (gpointer ptr) +{ + ViewData *view_data = ptr; + + if (view_data) { + if (g_atomic_int_dec_and_test (&view_data->ref_count)) { + view_data_disconnect_view (view_data); + if (view_data->cancellable) + g_cancellable_cancel (view_data->cancellable); + g_clear_object (&view_data->cancellable); + g_clear_object (&view_data->client); + g_clear_object (&view_data->view); + g_hash_table_destroy (view_data->components); + if (view_data->lost_components) + g_hash_table_destroy (view_data->lost_components); + g_slist_free_full (view_data->to_expand_recurrences, (GDestroyNotify) icalcomponent_free); + g_slist_free_full (view_data->expanded_recurrences, component_data_free); + g_rec_mutex_clear (&view_data->lock); + g_free (view_data); + } + } +} + +static void +view_data_lock (ViewData *view_data) +{ + g_return_if_fail (view_data != NULL); + + g_rec_mutex_lock (&view_data->lock); +} + +static void +view_data_unlock (ViewData *view_data) +{ + g_return_if_fail (view_data != NULL); + + g_rec_mutex_unlock (&view_data->lock); +} + +static SubscriberData * +subscriber_data_new (ECalDataModelSubscriber *subscriber, + time_t range_start, + time_t range_end) +{ + SubscriberData *subs_data; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber), NULL); + + subs_data = g_new0 (SubscriberData, 1); + subs_data->subscriber = g_object_ref (subscriber); + subs_data->range_start = range_start; + subs_data->range_end = range_end; + + return subs_data; +} + +static void +subscriber_data_free (gpointer ptr) +{ + SubscriberData *subs_data = ptr; + + if (subs_data) { + g_clear_object (&subs_data->subscriber); + g_free (subs_data); + } +} + +typedef struct _ViewStateChangedData { + ECalDataModel *data_model; + ECalClientView *view; + ECalDataModelViewState state; + guint percent; + gchar *message; + GError *error; +} ViewStateChangedData; + +static void +view_state_changed_data_free (gpointer ptr) +{ + ViewStateChangedData *vscd = ptr; + + if (vscd) { + g_clear_object (&vscd->data_model); + g_clear_object (&vscd->view); + g_clear_error (&vscd->error); + g_free (vscd->message); + g_free (vscd); + } +} + +static gboolean +cal_data_model_emit_view_state_changed_timeout_cb (gpointer user_data) +{ + ViewStateChangedData *vscd = user_data; + + g_return_val_if_fail (vscd != NULL, FALSE); + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (vscd->data_model), FALSE); + g_return_val_if_fail (E_IS_CAL_CLIENT_VIEW (vscd->view), FALSE); + + g_signal_emit (vscd->data_model, signals[VIEW_STATE_CHANGED], 0, + vscd->view, vscd->state, vscd->percent, vscd->message, vscd->error); + + return FALSE; +} + +static void +cal_data_model_emit_view_state_changed (ECalDataModel *data_model, + ECalClientView *view, + ECalDataModelViewState state, + guint percent, + const gchar *message, + const GError *error) +{ + ViewStateChangedData *vscd; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (E_IS_CAL_CLIENT_VIEW (view)); + + if (e_cal_data_model_get_disposing (data_model)) + return; + + vscd = g_new0 (ViewStateChangedData, 1); + vscd->data_model = g_object_ref (data_model); + vscd->view = g_object_ref (view); + vscd->state = state; + vscd->percent = percent; + vscd->message = g_strdup (message); + vscd->error = error ? g_error_copy (error) : NULL; + + g_timeout_add_full (G_PRIORITY_DEFAULT, 1, + cal_data_model_emit_view_state_changed_timeout_cb, + vscd, view_state_changed_data_free); +} + +typedef void (* InternalThreadJobFunc) (ECalDataModel *data_model, gpointer user_data); + +typedef struct _InternalThreadJobData { + InternalThreadJobFunc func; + gpointer user_data; +} InternalThreadJobData; + +static void +cal_data_model_internal_thread_job_func (gpointer data, + gpointer user_data) +{ + ECalDataModel *data_model = user_data; + InternalThreadJobData *job_data = data; + + g_return_if_fail (job_data != NULL); + g_return_if_fail (job_data->func != NULL); + + job_data->func (data_model, job_data->user_data); + + g_free (job_data); +} + +static void +cal_data_model_submit_internal_thread_job (ECalDataModel *data_model, + InternalThreadJobFunc func, + gpointer user_data) +{ + InternalThreadJobData *job_data; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (func != NULL); + + job_data = g_new0 (InternalThreadJobData, 1); + job_data->func = func; + job_data->user_data = user_data; + + g_thread_pool_push (data_model->priv->thread_pool, job_data, NULL); +} + +typedef struct _SubmitThreadJobData { + ECalDataModel *data_model; + EThreadJobFunc func; + gpointer user_data; + GDestroyNotify free_user_data; + + GCancellable *cancellable; + gboolean finished; + GMutex mutex; + GCond cond; +} SubmitThreadJobData; + +static gboolean +cal_data_model_call_submit_thread_job (gpointer user_data) +{ + SubmitThreadJobData *stj_data = user_data; + + g_return_val_if_fail (stj_data != NULL, FALSE); + + g_mutex_lock (&stj_data->mutex); + + stj_data->cancellable = stj_data->data_model->priv->submit_thread_job_func (stj_data->func, stj_data->user_data, stj_data->free_user_data); + + stj_data->finished = TRUE; + g_cond_signal (&stj_data->cond); + g_mutex_unlock (&stj_data->mutex); + + return FALSE; +} + +/** + * e_cal_data_model_submit_thread_job: + * @data_model: an #ECalDataModel + * @description: user-friendly description of the job, to be shown in UI + * @alert_ident: in case of an error, this alert identificator is used + * for EAlert construction + * @alert_arg_0: (allow-none): in case of an error, use this string as + * the first argument to the EAlert construction; the second argument + * is the actual error message; can be #NULL, in which case only + * the error message is passed to the EAlert construction + * @func: function to be run in a dedicated thread + * @user_data: (allow-none): custom data passed into @func; can be #NULL + * @free_user_data: (allow-none): function to be called on @user_data, + * when the job is over; can be #NULL + * + * Runs the @func in a dedicated thread. Any error is propagated to UI. + * The cancellable passed into the @func is a #CamelOperation, thus + * the caller can overwrite progress and description message on it. + * + * Returns: (transfer full): Newly created #GCancellable on success. + * The caller is responsible to g_object_unref() it when done with it. + * + * Note: The @free_user_data, if set, is called in the main thread. + * + * Note: This is a blocking call, it waits until the thread job is submitted. + * + * Since: 3.14 + **/ +GCancellable * +e_cal_data_model_submit_thread_job (ECalDataModel *data_model, + EThreadJobFunc func, + gpointer user_data, + GDestroyNotify free_user_data) +{ + SubmitThreadJobData stj_data; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL); + g_return_val_if_fail (data_model->priv->submit_thread_job_func != NULL, NULL); + + if (g_thread_self () == data_model->priv->main_thread) { + GCancellable *cancellable; + + cancellable = data_model->priv->submit_thread_job_func ( + func, user_data, free_user_data); + + return cancellable; + } + + stj_data.data_model = data_model; + stj_data.func = func; + stj_data.user_data = user_data; + stj_data.free_user_data = free_user_data; + stj_data.cancellable = NULL; + stj_data.finished = FALSE; + g_mutex_init (&stj_data.mutex); + g_cond_init (&stj_data.cond); + + g_timeout_add (1, cal_data_model_call_submit_thread_job, &stj_data); + + g_mutex_lock (&stj_data.mutex); + while (!stj_data.finished) { + g_cond_wait (&stj_data.cond, &stj_data.mutex); + } + g_mutex_unlock (&stj_data.mutex); + + g_cond_clear (&stj_data.cond); + g_mutex_clear (&stj_data.mutex); + + return stj_data.cancellable; +} + +typedef void (* ECalDataModelForeachSubscriberFunc) (ECalDataModel *data_model, + ECalClient *client, + ECalDataModelSubscriber *subscriber, + gpointer user_data); + +static void +cal_data_model_foreach_subscriber_in_range (ECalDataModel *data_model, + ECalClient *client, + time_t in_range_start, + time_t in_range_end, + ECalDataModelForeachSubscriberFunc func, + gpointer user_data) +{ + GSList *link; + + g_return_if_fail (func != NULL); + + LOCK_PROPS (); + + if (in_range_end == (time_t) 0) { + in_range_end = in_range_start; + } + + for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) { + SubscriberData *subs_data = link->data; + + if ((in_range_start == (time_t) 0 && in_range_end == (time_t) 0) || + (subs_data->range_start == (time_t) 0 && subs_data->range_end == (time_t) 0) || + (subs_data->range_start <= in_range_end && subs_data->range_end >= in_range_start)) + func (data_model, client, subs_data->subscriber, user_data); + } + + UNLOCK_PROPS (); +} + +static void +cal_data_model_foreach_subscriber (ECalDataModel *data_model, + ECalClient *client, + ECalDataModelForeachSubscriberFunc func, + gpointer user_data) +{ + g_return_if_fail (func != NULL); + + cal_data_model_foreach_subscriber_in_range (data_model, client, (time_t) 0, (time_t) 0, func, user_data); +} + +static void +cal_data_model_freeze_subscriber_cb (ECalDataModel *data_model, + ECalClient *client, + ECalDataModelSubscriber *subscriber, + gpointer user_data) +{ + e_cal_data_model_subscriber_freeze (subscriber); +} + +static void +cal_data_model_thaw_subscriber_cb (ECalDataModel *data_model, + ECalClient *client, + ECalDataModelSubscriber *subscriber, + gpointer user_data) +{ + e_cal_data_model_subscriber_thaw (subscriber); +} + +static void +cal_data_model_freeze_all_subscribers (ECalDataModel *data_model) +{ + cal_data_model_foreach_subscriber (data_model, NULL, cal_data_model_freeze_subscriber_cb, NULL); +} + +static void +cal_data_model_thaw_all_subscribers (ECalDataModel *data_model) +{ + cal_data_model_foreach_subscriber (data_model, NULL, cal_data_model_thaw_subscriber_cb, NULL); +} + +static void +cal_data_model_add_component_cb (ECalDataModel *data_model, + ECalClient *client, + ECalDataModelSubscriber *subscriber, + gpointer user_data) +{ + ECalComponent *comp = user_data; + + g_return_if_fail (comp != NULL); + + e_cal_data_model_subscriber_component_added (subscriber, client, comp); +} + +static void +cal_data_model_modify_component_cb (ECalDataModel *data_model, + ECalClient *client, + ECalDataModelSubscriber *subscriber, + gpointer user_data) +{ + ECalComponent *comp = user_data; + + g_return_if_fail (comp != NULL); + + e_cal_data_model_subscriber_component_modified (subscriber, client, comp); +} + +static void +cal_data_model_remove_one_view_component_cb (ECalDataModel *data_model, + ECalClient *client, + ECalDataModelSubscriber *subscriber, + gpointer user_data) +{ + const ECalComponentId *id = user_data; + + g_return_if_fail (id != NULL); + + e_cal_data_model_subscriber_component_removed (subscriber, client, id->uid, id->rid); +} + +static void +cal_data_model_remove_components (ECalDataModel *data_model, + ECalClient *client, + GHashTable *components, + GHashTable *also_remove_from) +{ + GList *ids, *ilink; + + g_return_if_fail (data_model != NULL); + g_return_if_fail (components != NULL); + + cal_data_model_freeze_all_subscribers (data_model); + + ids = g_hash_table_get_keys (components); + + for (ilink = ids; ilink; ilink = g_list_next (ilink)) { + ECalComponentId *id = ilink->data; + ComponentData *comp_data; + time_t instance_start = (time_t) 0, instance_end = (time_t) 0; + + if (!id) + continue; + + /* Try to limit which subscribers will be notified about removal */ + comp_data = g_hash_table_lookup (components, id); + if (comp_data) { + instance_start = comp_data->instance_start; + instance_end = comp_data->instance_end; + } + + cal_data_model_foreach_subscriber_in_range (data_model, client, + instance_start, instance_end, + cal_data_model_remove_one_view_component_cb, id); + + if (also_remove_from) + g_hash_table_remove (also_remove_from, id); + } + + g_list_free (ids); + + cal_data_model_thaw_all_subscribers (data_model); +} + +static void +cal_data_model_calc_range (ECalDataModel *data_model, + time_t *range_start, + time_t *range_end) +{ + GSList *link; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (range_start != NULL); + g_return_if_fail (range_end != NULL); + + *range_start = (time_t) 0; + *range_end = (time_t) 0; + + LOCK_PROPS (); + + for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) { + SubscriberData *subs_data = link->data; + + if (!subs_data) + continue; + + if (subs_data->range_start == (time_t) 0 && subs_data->range_end == (time_t) 0) { + *range_start = (time_t) 0; + *range_end = (time_t) 0; + break; + } + + if (link == data_model->priv->subscribers) { + *range_start = subs_data->range_start; + *range_end = subs_data->range_end; + } else { + if (*range_start > subs_data->range_start) + *range_start = subs_data->range_start; + if (*range_end < subs_data->range_end) + *range_end = subs_data->range_end; + } + } + + UNLOCK_PROPS (); +} + +static gboolean +cal_data_model_update_full_filter (ECalDataModel *data_model) +{ + gchar *filter; + time_t range_start, range_end; + gboolean changed; + + LOCK_PROPS (); + + cal_data_model_calc_range (data_model, &range_start, &range_end); + + if (range_start != (time_t) 0 || range_end != (time_t) 0) { + gchar *iso_start, *iso_end; + const gchar *default_tzloc = NULL; + + iso_start = isodate_from_time_t (range_start); + iso_end = isodate_from_time_t (range_end); + + if (data_model->priv->zone && data_model->priv->zone != icaltimezone_get_utc_timezone ()) + default_tzloc = icaltimezone_get_location (data_model->priv->zone); + if (!default_tzloc) + default_tzloc = ""; + + if (data_model->priv->filter) { + filter = g_strdup_printf ( + "(and (occur-in-time-range? (make-time \"%s\") (make-time \"%s\") \"%s\") %s)", + iso_start, iso_end, default_tzloc, data_model->priv->filter); + } else { + filter = g_strdup_printf ( + "(occur-in-time-range? (make-time \"%s\") (make-time \"%s\") \"%s\")", + iso_start, iso_end, default_tzloc); + } + + g_free (iso_start); + g_free (iso_end); + } else if (data_model->priv->filter) { + filter = g_strdup (data_model->priv->filter); + } else { + filter = g_strdup ("#t"); + } + + changed = g_strcmp0 (data_model->priv->full_filter, filter) != 0; + + if (changed) { + g_free (data_model->priv->full_filter); + data_model->priv->full_filter = filter; + } else { + g_free (filter); + } + + UNLOCK_PROPS (); + + return changed; +} + +/* This consumes the comp_data - not so nice, but simpler + than adding reference counter for the structure */ +static void +cal_data_model_process_added_component (ECalDataModel *data_model, + ViewData *view_data, + ComponentData *comp_data, + GHashTable *known_instances) +{ + ECalComponentId *id; + ComponentData *old_comp_data = NULL; + gboolean comp_data_equal; + + g_return_if_fail (data_model != NULL); + g_return_if_fail (view_data != NULL); + g_return_if_fail (comp_data != NULL); + + id = e_cal_component_get_id (comp_data->component); + g_return_if_fail (id != NULL); + + view_data_lock (view_data); + + if (!old_comp_data && view_data->lost_components) + old_comp_data = g_hash_table_lookup (view_data->lost_components, id); + + if (!old_comp_data && known_instances) + old_comp_data = g_hash_table_lookup (known_instances, id); + + if (!old_comp_data) + old_comp_data = g_hash_table_lookup (view_data->components, id); + + if (old_comp_data) { + /* It can be a previously added detached instance received + during recurrences expand */ + if (!comp_data->is_detached) + comp_data->is_detached = old_comp_data->is_detached; + } + + comp_data_equal = component_data_equal (comp_data, old_comp_data); + + if (view_data->lost_components) + g_hash_table_remove (view_data->lost_components, id); + + if (known_instances) + g_hash_table_remove (known_instances, id); + + /* Note: old_comp_data is freed or NULL now */ + + /* 'id' is stolen by view_data->components */ + g_hash_table_insert (view_data->components, id, comp_data); + + if (!comp_data_equal) { + if (!old_comp_data) + cal_data_model_foreach_subscriber_in_range (data_model, view_data->client, + comp_data->instance_start, comp_data->instance_end, + cal_data_model_add_component_cb, comp_data->component); + else + cal_data_model_foreach_subscriber_in_range (data_model, view_data->client, + comp_data->instance_start, comp_data->instance_end, + cal_data_model_modify_component_cb, comp_data->component); + } + + view_data_unlock (view_data); +} + +typedef struct _GatherComponentsData { + const gchar *uid; + GList **pcomponent_ids; /* ECalComponentId, can be owned by the hash table */ + GHashTable *component_ids_hash; + gboolean copy_ids; + gboolean all_instances; /* FALSE to get only nondetached component instances */ +} GatherComponentsData; + +static void +cal_data_model_gather_components (gpointer key, + gpointer value, + gpointer user_data) +{ + ECalComponentId *id = key; + ComponentData *comp_data = value; + GatherComponentsData *gather_data = user_data; + + g_return_if_fail (id != NULL); + g_return_if_fail (comp_data != NULL); + g_return_if_fail (gather_data != NULL); + g_return_if_fail (gather_data->pcomponent_ids != NULL || gather_data->component_ids_hash != NULL); + g_return_if_fail (gather_data->pcomponent_ids == NULL || gather_data->component_ids_hash == NULL); + + if ((gather_data->all_instances || !comp_data->is_detached) && g_strcmp0 (id->uid, gather_data->uid) == 0) { + if (gather_data->component_ids_hash) { + ComponentData *comp_data_copy; + + comp_data_copy = component_data_new (comp_data->component, + comp_data->instance_start, comp_data->instance_end, + comp_data->is_detached); + + if (gather_data->copy_ids) { + g_hash_table_insert (gather_data->component_ids_hash, + e_cal_component_id_copy (id), comp_data_copy); + } else { + g_hash_table_insert (gather_data->component_ids_hash, id, comp_data_copy); + } + } else if (gather_data->copy_ids) { + *gather_data->pcomponent_ids = g_list_prepend (*gather_data->pcomponent_ids, + e_cal_component_id_copy (id)); + } else { + *gather_data->pcomponent_ids = g_list_prepend (*gather_data->pcomponent_ids, id); + } + } +} + +typedef struct _NotifyRecurrencesData { + ECalDataModel *data_model; + ECalClient *client; +} NotifyRecurrencesData; + +static gboolean +cal_data_model_notify_recurrences_cb (gpointer user_data) +{ + NotifyRecurrencesData *notif_data = user_data; + ECalDataModel *data_model; + ViewData *view_data; + + g_return_val_if_fail (notif_data != NULL, FALSE); + + data_model = notif_data->data_model; + + LOCK_PROPS (); + + view_data = g_hash_table_lookup (data_model->priv->views, notif_data->client); + if (view_data) + view_data_ref (view_data); + + UNLOCK_PROPS (); + + if (view_data) { + GHashTable *gathered_uids; + GHashTable *known_instances; + GSList *expanded_recurrences, *link; + + view_data_lock (view_data); + expanded_recurrences = view_data->expanded_recurrences; + view_data->expanded_recurrences = NULL; + + cal_data_model_freeze_all_subscribers (data_model); + + gathered_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + known_instances = g_hash_table_new_full ( + (GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal, + (GDestroyNotify) e_cal_component_free_id, component_data_free); + + for (link = expanded_recurrences; link && view_data->is_used; link = g_slist_next (link)) { + ComponentData *comp_data = link->data; + icalcomponent *icomp; + const gchar *uid; + + if (!comp_data) + continue; + + icomp = e_cal_component_get_icalcomponent (comp_data->component); + if (!icomp || !icalcomponent_get_uid (icomp)) + continue; + + uid = icalcomponent_get_uid (icomp); + + if (!g_hash_table_contains (gathered_uids, uid)) { + GatherComponentsData gather_data; + + gather_data.uid = uid; + gather_data.pcomponent_ids = NULL; + gather_data.component_ids_hash = known_instances; + gather_data.copy_ids = TRUE; + gather_data.all_instances = FALSE; + + g_hash_table_foreach (view_data->components, + cal_data_model_gather_components, &gather_data); + + g_hash_table_insert (gathered_uids, g_strdup (uid), GINT_TO_POINTER (1)); + } + + /* Steal the comp_data */ + link->data = NULL; + + cal_data_model_process_added_component (data_model, view_data, comp_data, known_instances); + } + + if (view_data->is_used && g_hash_table_size (known_instances) > 0) { + cal_data_model_remove_components (data_model, view_data->client, known_instances, view_data->components); + g_hash_table_remove_all (known_instances); + } + + if (g_atomic_int_dec_and_test (&view_data->pending_expand_recurrences) && + view_data->is_used && view_data->lost_components && view_data->received_complete) { + cal_data_model_remove_components (data_model, view_data->client, view_data->lost_components, NULL); + g_hash_table_destroy (view_data->lost_components); + view_data->lost_components = NULL; + } + + g_hash_table_destroy (gathered_uids); + g_hash_table_destroy (known_instances); + + view_data_unlock (view_data); + + cal_data_model_thaw_all_subscribers (data_model); + + view_data_unref (view_data); + + g_slist_free_full (expanded_recurrences, component_data_free); + } + + g_clear_object (¬if_data->client); + g_clear_object (¬if_data->data_model); + g_free (notif_data); + + return FALSE; +} + +typedef struct +{ + ECalClient *client; + icaltimezone *zone; + GSList **pexpanded_recurrences; +} GenerateInstancesData; + +static gboolean +cal_data_model_instance_generated (ECalComponent *comp, + time_t instance_start, + time_t instance_end, + gpointer data) +{ + GenerateInstancesData *gid = data; + ComponentData *comp_data; + + g_return_val_if_fail (gid != NULL, FALSE); + + cal_comp_get_instance_times (gid->client, e_cal_component_get_icalcomponent (comp), + gid->zone, &instance_start, NULL, &instance_end, NULL, NULL); + + if (instance_end > instance_start) + instance_end--; + + comp_data = component_data_new (comp, instance_start, instance_end, FALSE); + *gid->pexpanded_recurrences = g_slist_prepend (*gid->pexpanded_recurrences, comp_data); + + return TRUE; +} + +static void +cal_data_model_expand_recurrences_thread (ECalDataModel *data_model, + gpointer user_data) +{ + ECalClient *client = user_data; + GSList *to_expand_recurrences, *link; + GSList *expanded_recurrences = NULL; + time_t range_start, range_end; + ViewData *view_data; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + view_data = g_hash_table_lookup (data_model->priv->views, client); + if (view_data) + view_data_ref (view_data); + + range_start = data_model->priv->range_start; + range_end = data_model->priv->range_end; + + UNLOCK_PROPS (); + + if (!view_data) { + g_object_unref (client); + return; + } + + view_data_lock (view_data); + + if (!view_data->is_used) { + view_data_unlock (view_data); + view_data_unref (view_data); + g_object_unref (client); + return; + } + + to_expand_recurrences = view_data->to_expand_recurrences; + view_data->to_expand_recurrences = NULL; + + view_data_unlock (view_data); + + for (link = to_expand_recurrences; link && view_data->is_used; link = g_slist_next (link)) { + icalcomponent *icomp = link->data; + GenerateInstancesData gid; + + if (!icomp) + continue; + + gid.client = client; + gid.pexpanded_recurrences = &expanded_recurrences; + gid.zone = data_model->priv->zone; + + e_cal_client_generate_instances_for_object_sync (client, icomp, range_start, range_end, + cal_data_model_instance_generated, &gid); + } + + g_slist_free_full (to_expand_recurrences, (GDestroyNotify) icalcomponent_free); + + view_data_lock (view_data); + if (expanded_recurrences) + view_data->expanded_recurrences = g_slist_concat (view_data->expanded_recurrences, expanded_recurrences); + if (view_data->is_used) { + NotifyRecurrencesData *notif_data; + + notif_data = g_new0 (NotifyRecurrencesData, 1); + notif_data->data_model = g_object_ref (data_model); + notif_data->client = g_object_ref (client); + + g_timeout_add (1, cal_data_model_notify_recurrences_cb, notif_data); + } + + view_data_unlock (view_data); + view_data_unref (view_data); + g_object_unref (client); +} + +static void +cal_data_model_process_modified_or_added_objects (ECalClientView *view, + const GSList *objects, + ECalDataModel *data_model, + gboolean is_add) +{ + ViewData *view_data; + ECalClient *client; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + client = e_cal_client_view_ref_client (view); + view_data = g_hash_table_lookup (data_model->priv->views, client); + if (view_data) { + view_data_ref (view_data); + g_warn_if_fail (view_data->view == view); + } + g_object_unref (client); + + UNLOCK_PROPS (); + + if (!view_data) + return; + + view_data_lock (view_data); + + if (view_data->is_used) { + const GSList *link; + GSList *to_expand_recurrences = NULL; + + if (!is_add) { + /* Received a modify before the view was claimed as being complete, + aka fully populated, thus drop any previously known components, + because there is no hope for a merge. */ + if (view_data->lost_components) { + cal_data_model_remove_components (data_model, client, view_data->lost_components, NULL); + g_hash_table_destroy (view_data->lost_components); + view_data->lost_components = NULL; + } + } + + cal_data_model_freeze_all_subscribers (data_model); + + for (link = objects; link; link = g_slist_next (link)) { + icalcomponent *icomp = link->data; + + if (!icomp || !icalcomponent_get_uid (icomp)) + continue; + + if (data_model->priv->expand_recurrences && + !e_cal_util_component_is_instance (icomp) && + e_cal_util_component_has_recurrences (icomp)) { + /* This component requires an expand of recurrences, which + will be done in a dedicated thread, thus remember it */ + to_expand_recurrences = g_slist_prepend (to_expand_recurrences, + icalcomponent_new_clone (icomp)); + } else { + /* Single or detached instance, the simple case */ + ECalComponent *comp; + ComponentData *comp_data; + time_t instance_start, instance_end; + + comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (icomp)); + if (!comp) + continue; + + cal_comp_get_instance_times (client, icomp, data_model->priv->zone, &instance_start, NULL, &instance_end, NULL, NULL); + + if (instance_end > instance_start) + instance_end--; + + comp_data = component_data_new (comp, instance_start, instance_end, + e_cal_util_component_is_instance (icomp)); + + cal_data_model_process_added_component (data_model, view_data, comp_data, NULL); + + g_object_unref (comp); + } + } + + cal_data_model_thaw_all_subscribers (data_model); + + if (to_expand_recurrences) { + ECalClient *client = e_cal_client_view_ref_client (view); + + view_data_lock (view_data); + view_data->to_expand_recurrences = g_slist_concat ( + view_data->to_expand_recurrences, to_expand_recurrences); + g_atomic_int_inc (&view_data->pending_expand_recurrences); + view_data_unlock (view_data); + + cal_data_model_submit_internal_thread_job (data_model, + cal_data_model_expand_recurrences_thread, client); + } + } + + view_data_unlock (view_data); + view_data_unref (view_data); +} + +static void +cal_data_model_view_objects_added (ECalClientView *view, + const GSList *objects, + ECalDataModel *data_model) +{ + cal_data_model_process_modified_or_added_objects (view, objects, data_model, TRUE); +} + +static void +cal_data_model_view_objects_modified (ECalClientView *view, + const GSList *objects, + ECalDataModel *data_model) +{ + cal_data_model_process_modified_or_added_objects (view, objects, data_model, FALSE); +} + +static void +cal_data_model_view_objects_removed (ECalClientView *view, + const GSList *uids, + ECalDataModel *data_model) +{ + ViewData *view_data; + const GSList *link; + ECalClient *client; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + client = e_cal_client_view_ref_client (view); + view_data = + g_hash_table_lookup (data_model->priv->views, client); + + if (view_data) { + view_data_ref (view_data); + g_warn_if_fail (view_data->view == view); + } + g_object_unref (client); + + UNLOCK_PROPS (); + + if (!view_data) + return; + + view_data_lock (view_data); + if (view_data->is_used) { + GHashTable *gathered_uids; + GList *removed = NULL, *rlink; + + gathered_uids = g_hash_table_new (g_str_hash, g_str_equal); + + for (link = uids; link; link = g_slist_next (link)) { + const ECalComponentId *id = link->data; + + if (id) { + if (!id->rid || !*id->rid) { + if (!g_hash_table_contains (gathered_uids, id->uid)) { + GatherComponentsData gather_data; + + gather_data.uid = id->uid; + gather_data.pcomponent_ids = &removed; + gather_data.component_ids_hash = NULL; + gather_data.copy_ids = TRUE; + gather_data.all_instances = TRUE; + + g_hash_table_foreach (view_data->components, + cal_data_model_gather_components, &gather_data); + if (view_data->lost_components) + g_hash_table_foreach (view_data->lost_components, + cal_data_model_gather_components, &gather_data); + + g_hash_table_insert (gathered_uids, id->uid, GINT_TO_POINTER (1)); + } + } else { + removed = g_list_prepend (removed, e_cal_component_id_copy (id)); + } + } + } + + cal_data_model_freeze_all_subscribers (data_model); + + for (rlink = removed; rlink; rlink = g_list_next (rlink)) { + ECalComponentId *id = rlink->data; + + if (id) { + ComponentData *comp_data; + time_t instance_start = (time_t) 0, instance_end = (time_t) 0; + + /* Try to limit which subscribers will be notified about removal */ + comp_data = g_hash_table_lookup (view_data->components, id); + if (comp_data) { + instance_start = comp_data->instance_start; + instance_end = comp_data->instance_end; + } else if (view_data->lost_components) { + comp_data = g_hash_table_lookup (view_data->lost_components, id); + if (comp_data) { + instance_start = comp_data->instance_start; + instance_end = comp_data->instance_end; + } + } + + g_hash_table_remove (view_data->components, id); + if (view_data->lost_components) + g_hash_table_remove (view_data->lost_components, id); + + cal_data_model_foreach_subscriber_in_range (data_model, view_data->client, + instance_start, instance_end, + cal_data_model_remove_one_view_component_cb, id); + } + } + + cal_data_model_thaw_all_subscribers (data_model); + + g_list_free_full (removed, (GDestroyNotify) e_cal_component_free_id); + g_hash_table_destroy (gathered_uids); + } + view_data_unlock (view_data); + view_data_unref (view_data); +} + +static void +cal_data_model_view_progress (ECalClientView *view, + guint percent, + const gchar *message, + ECalDataModel *data_model) +{ + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_PROGRESS, percent, message, NULL); +} + +static void +cal_data_model_view_complete (ECalClientView *view, + const GError *error, + ECalDataModel *data_model) +{ + ViewData *view_data; + ECalClient *client; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + client = e_cal_client_view_ref_client (view); + view_data = + g_hash_table_lookup (data_model->priv->views, client); + if (view_data) { + view_data_ref (view_data); + g_warn_if_fail (view_data->view == view); + } + g_object_unref (client); + + UNLOCK_PROPS (); + + if (!view_data) + return; + + view_data_lock (view_data); + + view_data->received_complete = TRUE; + if (view_data->is_used && + view_data->lost_components && + !view_data->pending_expand_recurrences) { + cal_data_model_remove_components (data_model, view_data->client, view_data->lost_components, NULL); + g_hash_table_destroy (view_data->lost_components); + view_data->lost_components = NULL; + } + + view_data_unlock (view_data); + view_data_unref (view_data); +} + +typedef struct _CreateViewData { + ECalDataModel *data_model; + ECalClient *client; +} CreateViewData; + +static void +create_view_data_free (gpointer ptr) +{ + CreateViewData *cv_data = ptr; + + if (cv_data) { + g_clear_object (&cv_data->data_model); + g_clear_object (&cv_data->client); + g_free (cv_data); + } +} + +static void +cal_data_model_create_view_thread (gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + CreateViewData *cv_data = user_data; + ViewData *view_data; + ECalDataModel *data_model; + ECalClient *client; + ECalClientView *view; + gchar *filter; + + g_return_if_fail (cv_data != NULL); + + data_model = cv_data->data_model; + client = cv_data->client; + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (E_IS_CAL_CLIENT (client)); + + LOCK_PROPS (); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) { + UNLOCK_PROPS (); + return; + } + + view_data = g_hash_table_lookup (data_model->priv->views, client); + if (!view_data) { + UNLOCK_PROPS (); + g_warn_if_reached (); + return; + } + + filter = g_strdup (data_model->priv->full_filter); + + view_data_ref (view_data); + UNLOCK_PROPS (); + + view_data_lock (view_data); + g_warn_if_fail (view_data->view == NULL); + + if (!e_cal_client_get_view_sync (client, filter, &view_data->view, cancellable, error)) { + view_data_unlock (view_data); + view_data_unref (view_data); + g_free (filter); + return; + } + + g_warn_if_fail (view_data->view != NULL); + + view_data->objects_added_id = g_signal_connect (view_data->view, "objects-added", + G_CALLBACK (cal_data_model_view_objects_added), data_model); + view_data->objects_modified_id = g_signal_connect (view_data->view, "objects-modified", + G_CALLBACK (cal_data_model_view_objects_modified), data_model); + view_data->objects_removed_id = g_signal_connect (view_data->view, "objects-removed", + G_CALLBACK (cal_data_model_view_objects_removed), data_model); + view_data->progress_id = g_signal_connect (view_data->view, "progress", + G_CALLBACK (cal_data_model_view_progress), data_model); + view_data->complete_id = g_signal_connect (view_data->view, "complete", + G_CALLBACK (cal_data_model_view_complete), data_model); + + view = g_object_ref (view_data->view); + + view_data_unlock (view_data); + view_data_unref (view_data); + + g_free (filter); + + if (!g_cancellable_is_cancelled (cancellable)) { + cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_START, 0, NULL, NULL); + e_cal_client_view_start (view, error); + } + + g_clear_object (&view); +} + +typedef struct _NotifyRemoveComponentsData { + ECalDataModel *data_model; + ECalClient *client; +} NotifyRemoveComponentsData; + +static void +cal_data_model_notify_remove_components_cb (gpointer key, + gpointer value, + gpointer user_data) +{ + ECalComponentId *id = key; + ComponentData *comp_data = value; + NotifyRemoveComponentsData *nrc_data = user_data; + + g_return_if_fail (id != NULL); + g_return_if_fail (comp_data != NULL); + g_return_if_fail (nrc_data != NULL); + + cal_data_model_foreach_subscriber_in_range (nrc_data->data_model, nrc_data->client, + comp_data->instance_start, comp_data->instance_end, + cal_data_model_remove_one_view_component_cb, id); +} + +static void +cal_data_model_update_client_view (ECalDataModel *data_model, + ECalClient *client) +{ + /*ESource *source;*/ + ViewData *view_data; + CreateViewData *cv_data; + /*const gchar *alert_ident; + gchar *description;*/ + + LOCK_PROPS (); + + view_data = g_hash_table_lookup (data_model->priv->views, client); + if (!view_data) { + view_data = view_data_new (client); + g_hash_table_insert (data_model->priv->views, client, view_data); + } + + view_data_lock (view_data); + + if (view_data->cancellable) + g_cancellable_cancel (view_data->cancellable); + g_clear_object (&view_data->cancellable); + + if (view_data->view) { + view_data_disconnect_view (view_data); + cal_data_model_emit_view_state_changed (data_model, view_data->view, E_CAL_DATA_MODEL_VIEW_STATE_STOP, 0, NULL, NULL); + g_clear_object (&view_data->view); + } + + if (!view_data->received_complete) { + NotifyRemoveComponentsData nrc_data; + + nrc_data.data_model = data_model; + nrc_data.client = client; + + cal_data_model_freeze_all_subscribers (data_model); + + g_hash_table_foreach (view_data->components, + cal_data_model_notify_remove_components_cb, &nrc_data); + + g_hash_table_remove_all (view_data->components); + if (view_data->lost_components) { + g_hash_table_foreach (view_data->lost_components, + cal_data_model_notify_remove_components_cb, &nrc_data); + + g_hash_table_destroy (view_data->lost_components); + view_data->lost_components = NULL; + } + + cal_data_model_thaw_all_subscribers (data_model); + } else { + if (view_data->lost_components) { + NotifyRemoveComponentsData nrc_data; + + nrc_data.data_model = data_model; + nrc_data.client = client; + + cal_data_model_freeze_all_subscribers (data_model); + g_hash_table_foreach (view_data->lost_components, + cal_data_model_notify_remove_components_cb, &nrc_data); + cal_data_model_thaw_all_subscribers (data_model); + + g_hash_table_destroy (view_data->lost_components); + view_data->lost_components = NULL; + } + + view_data->lost_components = view_data->components; + view_data->components = g_hash_table_new_full ( + (GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal, + (GDestroyNotify) e_cal_component_free_id, component_data_free); + } + + view_data_unlock (view_data); + + if (!data_model->priv->full_filter) { + UNLOCK_PROPS (); + return; + } + + /*source = e_client_get_source (E_CLIENT (client)); + + switch (e_cal_client_get_source_type (client)) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + alert_ident = "calendar:failed-create-view-calendar"; + description = g_strdup_printf (_("Creating view for calendar '%s'"), e_source_get_display_name (source)); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + alert_ident = "calendar:failed-create-view-tasks"; + description = g_strdup_printf (_("Creating view for task list '%s'"), e_source_get_display_name (source)); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + alert_ident = "calendar:failed-create-view-memos"; + description = g_strdup_printf (_("Creating view for memo list '%s'"), e_source_get_display_name (source)); + break; + case E_CAL_CLIENT_SOURCE_TYPE_LAST: + g_warn_if_reached (); + UNLOCK_PROPS (); + return; + }*/ + + cv_data = g_new0 (CreateViewData, 1); + cv_data->data_model = g_object_ref (data_model); + cv_data->client = g_object_ref (client); + + view_data->received_complete = FALSE; + view_data->cancellable = e_cal_data_model_submit_thread_job (data_model, + cal_data_model_create_view_thread, cv_data, create_view_data_free); + + /*g_free (description);*/ + + UNLOCK_PROPS (); +} + +static void +cal_data_model_remove_client_view (ECalDataModel *data_model, + ECalClient *client) +{ + ViewData *view_data; + + LOCK_PROPS (); + + view_data = g_hash_table_lookup (data_model->priv->views, client); + + if (view_data) { + NotifyRemoveComponentsData nrc_data; + + view_data_lock (view_data); + + nrc_data.data_model = data_model; + nrc_data.client = client; + + cal_data_model_freeze_all_subscribers (data_model); + + g_hash_table_foreach (view_data->components, + cal_data_model_notify_remove_components_cb, &nrc_data); + g_hash_table_remove_all (view_data->components); + + cal_data_model_thaw_all_subscribers (data_model); + + if (view_data->view) + cal_data_model_emit_view_state_changed (data_model, view_data->view, E_CAL_DATA_MODEL_VIEW_STATE_STOP, 0, NULL, NULL); + + view_data->is_used = FALSE; + view_data_unlock (view_data); + + g_hash_table_remove (data_model->priv->views, client); + } + + UNLOCK_PROPS (); +} + +static gboolean +cal_data_model_add_to_subscriber (ECalDataModel *data_model, + ECalClient *client, + const ECalComponentId *id, + ECalComponent *component, + time_t instance_start, + time_t instance_end, + gpointer user_data) +{ + ECalDataModelSubscriber *subscriber = user_data; + + g_return_val_if_fail (subscriber != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + + e_cal_data_model_subscriber_component_added (subscriber, client, component); + + return TRUE; +} + +static gboolean +cal_data_model_add_to_subscriber_except_its_range (ECalDataModel *data_model, + ECalClient *client, + const ECalComponentId *id, + ECalComponent *component, + time_t instance_start, + time_t instance_end, + gpointer user_data) +{ + SubscriberData *subs_data = user_data; + + g_return_val_if_fail (subs_data != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + + /* subs_data should have set the old time range, which + means only components which didn't fit into the old + time range will be added */ + if (!(instance_start <= subs_data->range_end && + instance_end >= subs_data->range_start)) + e_cal_data_model_subscriber_component_added (subs_data->subscriber, client, component); + + return TRUE; +} + +static gboolean +cal_data_model_remove_from_subscriber_except_its_range (ECalDataModel *data_model, + ECalClient *client, + const ECalComponentId *id, + ECalComponent *component, + time_t instance_start, + time_t instance_end, + gpointer user_data) +{ + SubscriberData *subs_data = user_data; + + g_return_val_if_fail (subs_data != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + + /* subs_data should have set the new time range, which + means only components which don't fit into this new + time range will be removed */ + if (!(instance_start <= subs_data->range_end && + instance_end >= subs_data->range_start)) + e_cal_data_model_subscriber_component_removed (subs_data->subscriber, client, id->uid, id->rid); + + return TRUE; +} + +static void +cal_data_model_set_client_default_zone_cb (gpointer key, + gpointer value, + gpointer user_data) +{ + ECalClient *client = value; + icaltimezone *zone = user_data; + + g_return_if_fail (E_IS_CAL_CLIENT (client)); + g_return_if_fail (zone != NULL); + + e_cal_client_set_default_timezone (client, zone); +} + +static void +cal_data_model_rebuild_everything (ECalDataModel *data_model, + gboolean complete_rebuild) +{ + GHashTableIter iter; + gpointer key, value; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + if (data_model->priv->views_update_freeze > 0) { + data_model->priv->views_update_required = TRUE; + UNLOCK_PROPS (); + return; + } + + data_model->priv->views_update_required = FALSE; + + g_hash_table_iter_init (&iter, data_model->priv->clients); + while (g_hash_table_iter_next (&iter, &key, &value)) { + ECalClient *client = value; + + if (complete_rebuild) + cal_data_model_remove_client_view (data_model, client); + cal_data_model_update_client_view (data_model, client); + } + + UNLOCK_PROPS (); +} + +static void +cal_data_model_update_time_range (ECalDataModel *data_model) +{ + time_t range_start, range_end; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + if (data_model->priv->disposing) { + UNLOCK_PROPS (); + + return; + } + + range_start = data_model->priv->range_start; + range_end = data_model->priv->range_end; + + cal_data_model_calc_range (data_model, &range_start, &range_end); + + if (data_model->priv->range_start != range_start || + data_model->priv->range_end != range_end) { + data_model->priv->range_start = range_start; + data_model->priv->range_end = range_end; + + if (cal_data_model_update_full_filter (data_model)) + cal_data_model_rebuild_everything (data_model, FALSE); + } + + UNLOCK_PROPS (); +} + +static void +cal_data_model_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_EXPAND_RECURRENCES: + e_cal_data_model_set_expand_recurrences ( + E_CAL_DATA_MODEL (object), + g_value_get_boolean (value)); + return; + + case PROP_TIMEZONE: + e_cal_data_model_set_timezone ( + E_CAL_DATA_MODEL (object), + g_value_get_pointer (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cal_data_model_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_EXPAND_RECURRENCES: + g_value_set_boolean ( + value, + e_cal_data_model_get_expand_recurrences ( + E_CAL_DATA_MODEL (object))); + return; + + case PROP_TIMEZONE: + g_value_set_pointer ( + value, + e_cal_data_model_get_timezone ( + E_CAL_DATA_MODEL (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cal_data_model_dispose (GObject *object) +{ + ECalDataModel *data_model = E_CAL_DATA_MODEL (object); + + data_model->priv->disposing = TRUE; + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_cal_data_model_parent_class)->dispose (object); +} + +static void +cal_data_model_finalize (GObject *object) +{ + ECalDataModel *data_model = E_CAL_DATA_MODEL (object); + + g_thread_pool_free (data_model->priv->thread_pool, TRUE, FALSE); + g_hash_table_destroy (data_model->priv->clients); + g_hash_table_destroy (data_model->priv->views); + g_slist_free_full (data_model->priv->subscribers, subscriber_data_free); + g_free (data_model->priv->filter); + g_free (data_model->priv->full_filter); + + g_rec_mutex_clear (&data_model->priv->props_lock); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_cal_data_model_parent_class)->finalize (object); +} + +static void +e_cal_data_model_class_init (ECalDataModelClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (ECalDataModelPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = cal_data_model_set_property; + object_class->get_property = cal_data_model_get_property; + object_class->dispose = cal_data_model_dispose; + object_class->finalize = cal_data_model_finalize; + + g_object_class_install_property ( + object_class, + PROP_EXPAND_RECURRENCES, + g_param_spec_boolean ( + "expand-recurrences", + "Expand Recurrences", + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_TIMEZONE, + g_param_spec_pointer ( + "timezone", + "Time Zone", + NULL, + G_PARAM_READWRITE)); + + signals[VIEW_STATE_CHANGED] = g_signal_new ( + "view-state-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECalDataModelClass, view_state_changed), + NULL, NULL, + NULL, + G_TYPE_NONE, 5, E_TYPE_CAL_CLIENT_VIEW, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_ERROR); +} + +static void +e_cal_data_model_init (ECalDataModel *data_model) +{ + data_model->priv = G_TYPE_INSTANCE_GET_PRIVATE (data_model, E_TYPE_CAL_DATA_MODEL, ECalDataModelPrivate); + + /* Suppose the data_model is always created in the main/UI thread */ + data_model->priv->main_thread = g_thread_self (); + data_model->priv->thread_pool = g_thread_pool_new ( + cal_data_model_internal_thread_job_func, data_model, 5, FALSE, NULL); + + data_model->priv->clients = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + data_model->priv->views = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, view_data_unref); + data_model->priv->subscribers = NULL; + + data_model->priv->disposing = FALSE; + data_model->priv->expand_recurrences = FALSE; + data_model->priv->zone = icaltimezone_get_utc_timezone (); + + data_model->priv->views_update_freeze = 0; + data_model->priv->views_update_required = FALSE; + + g_rec_mutex_init (&data_model->priv->props_lock); +} + +/** + * e_cal_data_model_new: + * @func: a function to be called when the data model needs to create + * a thread job within UI + * + * Creates a new instance of #ECalDataModel. The @func is mandatory, because + * it is used to create new thread jobs with UI feedback. + * + * Returns: (transfer full): A new #ECalDataModel instance + * + * Since: 3.14 + **/ +ECalDataModel * +e_cal_data_model_new (ECalDataModelSubmitThreadJobFunc func) +{ + ECalDataModel *data_model; + + g_return_val_if_fail (func != NULL, NULL); + + data_model = g_object_new (E_TYPE_CAL_DATA_MODEL, NULL); + data_model->priv->submit_thread_job_func = func; + + return data_model; +} + +/** + * e_cal_data_model_new_clone: + * @src_data_model: an #ECalDataModel to clone + * + * Creates a clone of @src_data_model, which means a copy with the same clients, filter and + * other properties set (not the subscribers). + * + * Returns: (transfer full): A new #ECalDataModel instance deriving settings from @src_data_model + * + * Since: 3.14 + **/ +ECalDataModel * +e_cal_data_model_new_clone (ECalDataModel *src_data_model) +{ + ECalDataModel *clone; + GList *clients, *link; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (src_data_model), NULL); + + clone = e_cal_data_model_new (src_data_model->priv->submit_thread_job_func); + + e_cal_data_model_set_expand_recurrences (clone, e_cal_data_model_get_expand_recurrences (src_data_model)); + e_cal_data_model_set_timezone (clone, e_cal_data_model_get_timezone (src_data_model)); + e_cal_data_model_set_filter (clone, src_data_model->priv->filter); + + clients = e_cal_data_model_get_clients (src_data_model); + for (link = clients; link; link = g_list_next (link)) { + ECalClient *client = link->data; + + e_cal_data_model_add_client (clone, client); + } + + g_list_free_full (clients, g_object_unref); + + return clone; +} + +/** + * e_cal_data_model_get_disposing: + * @data_model: an #EDataModel instance + * + * Obtains whether the @data_model is disposing and will be freed (soon). + * + * Returns: Whether the @data_model is disposing. + * + * Since: 3.14 + **/ +gboolean +e_cal_data_model_get_disposing (ECalDataModel *data_model) +{ + gboolean disposing; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE); + + LOCK_PROPS (); + + disposing = data_model->priv->disposing; + + UNLOCK_PROPS (); + + return disposing; +} + +/** + * e_cal_data_model_set_disposing: + * @data_model: an #EDataModel instance + * @disposing: whether the object is disposing + * + * Sets whether the @data_model is disposing itself (soon). + * If set to %TRUE, then no updates are done on changes + * which would otherwise trigger view and subscriber updates. + * + * Since: 3.14 + **/ +void +e_cal_data_model_set_disposing (ECalDataModel *data_model, + gboolean disposing) +{ + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + if ((data_model->priv->disposing ? 1 : 0) == (disposing ? 1 : 0)) { + UNLOCK_PROPS (); + return; + } + + data_model->priv->disposing = disposing; + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_get_expand_recurrences: + * @data_model: an #EDataModel instance + * + * Obtains whether the @data_model expands recurrences of recurring + * components by default. The default value is #FALSE, to not expand + * recurrences. + * + * Returns: Whether the @data_model expands recurrences of recurring + * components. + * + * Since: 3.14 + **/ +gboolean +e_cal_data_model_get_expand_recurrences (ECalDataModel *data_model) +{ + gboolean expand_recurrences; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE); + + LOCK_PROPS (); + + expand_recurrences = data_model->priv->expand_recurrences; + + UNLOCK_PROPS (); + + return expand_recurrences; +} + +/** + * e_cal_data_model_set_expand_recurrences: + * @data_model: an #EDataModel instance + * @expand_recurrences: whether to expand recurrences + * + * Sets whether the @data_model should expand recurrences of recurring + * components by default. + * + * Since: 3.14 + **/ +void +e_cal_data_model_set_expand_recurrences (ECalDataModel *data_model, + gboolean expand_recurrences) +{ + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + if ((data_model->priv->expand_recurrences ? 1 : 0) == (expand_recurrences ? 1 : 0)) { + UNLOCK_PROPS (); + return; + } + + data_model->priv->expand_recurrences = expand_recurrences; + + cal_data_model_rebuild_everything (data_model, TRUE); + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_get_timezone: + * @data_model: an #EDataModel instance + * + * Obtains a timezone being used for calendar views. The returned + * timezone is owned by the @data_model. + * + * Returns: (transfer none): An #icaltimezone being used for calendar views. + * + * Since: 3.14 + **/ +icaltimezone * +e_cal_data_model_get_timezone (ECalDataModel *data_model) +{ + icaltimezone *zone; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL); + + LOCK_PROPS (); + + zone = data_model->priv->zone; + + UNLOCK_PROPS (); + + return zone; +} +/** + * e_cal_data_model_set_timezone: + * @data_model: an #EDataModel instance + * @zone: an #icaltimezone + * + * Sets a trimezone to be used for calendar views. This change + * regenerates all views. + * + * Since: 3.14 + **/ +void +e_cal_data_model_set_timezone (ECalDataModel *data_model, + icaltimezone *zone) +{ + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (zone != NULL); + + LOCK_PROPS (); + + if (data_model->priv->zone != zone) { + data_model->priv->zone = zone; + + g_hash_table_foreach (data_model->priv->clients, cal_data_model_set_client_default_zone_cb, zone); + + if (cal_data_model_update_full_filter (data_model)) + cal_data_model_rebuild_everything (data_model, TRUE); + } + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_set_filter: + * @data_model: an #EDataModel instance + * @sexp: an expression defining a filter + * + * Sets an additional filter for the views. The filter should not + * contain time constraints, these are meant to be defined by + * subscribers. + * + * Since: 3.14 + **/ +void +e_cal_data_model_set_filter (ECalDataModel *data_model, + const gchar *sexp) +{ + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (sexp != NULL); + + LOCK_PROPS (); + + if (sexp && !*sexp) + sexp = NULL; + + if (g_strcmp0 (data_model->priv->filter, sexp) != 0) { + g_free (data_model->priv->filter); + data_model->priv->filter = g_strdup (sexp); + + if (cal_data_model_update_full_filter (data_model)) + cal_data_model_rebuild_everything (data_model, TRUE); + } + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_dup_filter: + * @data_model: an #EDataModel instance + * + * Obtains currently used filter (an expression) for the views. + * + * Returns: (transfer full): A copy of the currently used + * filter for views. Free it with g_free() when done with it. + * Returns #NULL when there is no extra filter set. + * + * Since: 3.14 + **/ +gchar * +e_cal_data_model_dup_filter (ECalDataModel *data_model) +{ + gchar *filter; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL); + + LOCK_PROPS (); + + filter = g_strdup (data_model->priv->filter); + + UNLOCK_PROPS (); + + return filter; +} + +/** + * e_cal_data_model_add_client: + * @data_model: an #EDataModel instance + * @client: an #ECalClient + * + * Adds a new @client into the set of clients which should be used + * to populate data for subscribers. Adding the same client multiple + * times does nothing. + * + * Since: 3.14 + **/ +void +e_cal_data_model_add_client (ECalDataModel *data_model, + ECalClient *client) +{ + ESource *source; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (E_IS_CAL_CLIENT (client)); + + source = e_client_get_source (E_CLIENT (client)); + g_return_if_fail (E_IS_SOURCE (source)); + g_return_if_fail (e_source_get_uid (source) != NULL); + + LOCK_PROPS (); + + if (g_hash_table_contains (data_model->priv->clients, e_source_get_uid (source))) { + UNLOCK_PROPS (); + return; + } + + g_hash_table_insert (data_model->priv->clients, e_source_dup_uid (source), g_object_ref (client)); + + e_cal_client_set_default_timezone (client, data_model->priv->zone); + + cal_data_model_update_client_view (data_model, client); + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_remove_client: + * @uid: a UID of a client to remove + * + * Removes a client identified by @uid from a set of clients + * which populate the data for subscribers. Removing the client + * which is not used in the @data_model does nothing. + * + * Since: 3.14 + **/ +void +e_cal_data_model_remove_client (ECalDataModel *data_model, + const gchar *uid) +{ + ECalClient *client; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (uid != NULL); + + LOCK_PROPS (); + + client = g_hash_table_lookup (data_model->priv->clients, uid); + if (!client) { + UNLOCK_PROPS (); + return; + } + + cal_data_model_remove_client_view (data_model, client); + g_hash_table_remove (data_model->priv->clients, uid); + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_ref_client: + * @data_model: an #EDataModel instance + * @uid: a UID of a client to return + * + * Obtains an #ECalClient with given @uid from the set of clients + * being used by the @data_modal. Returns #NULL, if no such client + * is used by the @data_model. + * + * Returns: (tranfer full): An #ECalClient with given @uid being + * used by @data_model, or NULL, when no such is used by + * the @data_model. Unref returned (non-NULL) client with + * g_object_unref() when done with it. + * + * Since: 3.14 + **/ +ECalClient * +e_cal_data_model_ref_client (ECalDataModel *data_model, + const gchar *uid) +{ + ECalClient *client; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL); + + LOCK_PROPS (); + + client = g_hash_table_lookup (data_model->priv->clients, uid); + if (client) + g_object_ref (client); + + UNLOCK_PROPS (); + + return client; +} + +/** + * e_cal_data_model_get_clients: + * @data_model: an #EDataModel instance + * + * Obtains a list of all clients being used by the @data_model. + * Each client in the returned list is referenced and the list + * itself is also newly allocated, thus free it with + * g_list_free_full (list, g_object_unref); when done with it. + * + * Returns: (transfer full): A list of currently used #ECalClient-s. + * + * Since: 3.14 + **/ +GList * +e_cal_data_model_get_clients (ECalDataModel *data_model) +{ + GList *clients; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL); + + LOCK_PROPS (); + + clients = g_hash_table_get_values (data_model->priv->clients); + g_list_foreach (clients, (GFunc) g_object_ref, NULL); + + UNLOCK_PROPS (); + + return clients; +} + +static gboolean +cal_data_model_prepend_component (ECalDataModel *data_model, + ECalClient *client, + const ECalComponentId *id, + ECalComponent *comp, + time_t instance_start, + time_t instance_end, + gpointer user_data) +{ + GSList **components = user_data; + + g_return_val_if_fail (components != NULL, FALSE); + g_return_val_if_fail (comp != NULL, FALSE); + + *components = g_slist_prepend (*components, g_object_ref (comp)); + + return TRUE; +} + +/** + * e_cal_data_model_get_components: + * @data_model: an #EDataModel instance + * @in_range_start: Start of the time range + * @in_range_end: End of the time range + * + * Obtains a list of components from the given time range. The time range is + * clamp by the actual time range defined by subscribers (if there is no + * subscriber, or all subscribers define times out of the given time range, + * then no components are returned). + * + * Returns: (transfer full): A #GSList of #ECalComponent-s known for the given + * time range in the time of the call. The #GSList, togher with the components, + * is owned by the caller, which should free it with + * g_slist_free_full (list, g_object_unref); when done with it. + * + * Note: A special case when both @in_range_start and @in_range_end are zero + * is treated as a request for all known components. + * + * Since: 3.14 + **/ +GSList * +e_cal_data_model_get_components (ECalDataModel *data_model, + time_t in_range_start, + time_t in_range_end) +{ + GSList *components = NULL; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL); + + e_cal_data_model_foreach_component (data_model, in_range_start, in_range_end, + cal_data_model_prepend_component, &components); + + return g_slist_reverse (components); +} + +static gboolean +cal_data_model_foreach_component (ECalDataModel *data_model, + time_t in_range_start, + time_t in_range_end, + ECalDataModelForeachFunc func, + gpointer user_data, + gboolean include_lost_components) +{ + GHashTableIter viter; + gpointer key, value; + gboolean checked_all = TRUE; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE); + g_return_val_if_fail (func != NULL, FALSE); + + LOCK_PROPS (); + + /* Is the given time range in the currently used time range? */ + if (!(in_range_start == in_range_end && in_range_start == (time_t) 0) && + (in_range_start >= data_model->priv->range_end || + in_range_end <= data_model->priv->range_start)) { + UNLOCK_PROPS (); + return checked_all; + } + + g_hash_table_iter_init (&viter, data_model->priv->views); + while (checked_all && g_hash_table_iter_next (&viter, &key, &value)) { + ViewData *view_data = value; + GHashTableIter citer; + + if (!view_data) + continue; + + view_data_lock (view_data); + + g_hash_table_iter_init (&citer, view_data->components); + while (checked_all && g_hash_table_iter_next (&citer, &key, &value)) { + ECalComponentId *id = key; + ComponentData *comp_data = value; + + if (!comp_data) + continue; + + if ((in_range_start == in_range_end && in_range_start == (time_t) 0) || + (comp_data->instance_start < in_range_end && + comp_data->instance_end > in_range_start)) { + if (!func (data_model, view_data->client, id, comp_data->component, + comp_data->instance_start, comp_data->instance_end, user_data)) + checked_all = FALSE; + } + } + + if (include_lost_components && view_data->lost_components) { + g_hash_table_iter_init (&citer, view_data->lost_components); + while (checked_all && g_hash_table_iter_next (&citer, &key, &value)) { + ECalComponentId *id = key; + ComponentData *comp_data = value; + + if (!comp_data) + continue; + + if ((in_range_start == in_range_end && in_range_start == (time_t) 0) || + (comp_data->instance_start < in_range_end && + comp_data->instance_end > in_range_start)) { + if (!func (data_model, view_data->client, id, comp_data->component, + comp_data->instance_start, comp_data->instance_end, user_data)) + checked_all = FALSE; + } + } + } + + view_data_unlock (view_data); + } + + UNLOCK_PROPS (); + + return checked_all; +} + +/** + * e_cal_data_model_foreach_component: + * @data_model: an #EDataModel instance + * @in_range_start: Start of the time range + * @in_range_end: End of the time range + * @func: a function to be called for each component in the given time range + * @user_data: user data being passed into the @func + * + * Calls @func for each component in the given time range. The time range is + * clamp by the actual time range defined by subscribers (if there is no + * subscriber, or all subscribers define times out of the given time range, + * then the function is not called at all and a #FALSE is returned). + * + * The @func returns #TRUE to continue the traversal. If it wants to stop + * the traversal earlier, then it returns #FALSE. + * + * Returns: Whether all the components were checked. The returned value is + * usually #TRUE, unless the @func stops traversal earlier. + * + * Note: A special case when both @in_range_start and @in_range_end are zero + * is treated as a request for all known components. + * + * Since: 3.14 + **/ +gboolean +e_cal_data_model_foreach_component (ECalDataModel *data_model, + time_t in_range_start, + time_t in_range_end, + ECalDataModelForeachFunc func, + gpointer user_data) +{ + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE); + g_return_val_if_fail (func != NULL, FALSE); + + return cal_data_model_foreach_component (data_model, in_range_start, in_range_end, func, user_data, FALSE); +} + +/** + * e_cal_data_model_subscribe: + * @data_model: an #EDataModel instance + * @subscriber: an #ECalDataModelSubscriber instance + * @range_start: Start of the time range used by the @subscriber + * @range_end: End of the time range used by the @subscriber + * + * Either adds a new @subscriber to the set of subscribers for this + * @data_model, or changes a time range used by the @subscriber, + * in case it was added to the @data_model earlier. + * + * Reference count of the @subscriber is increased by one, in case + * it is newly added. The reference count is decreased by one + * when e_cal_data_model_unsubscribe() is called. + * + * Note: A special case when both @range_start and @range_end are zero + * is treated as a request with no time constraint. This limits + * the result only to those components which satisfy given filter. + * + * Since: 3.14 + **/ +void +e_cal_data_model_subscribe (ECalDataModel *data_model, + ECalDataModelSubscriber *subscriber, + time_t range_start, + time_t range_end) +{ + SubscriberData *subs_data = NULL; + GSList *link; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber)); + + LOCK_PROPS (); + + for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) { + SubscriberData *subs_data = link->data; + + if (!subs_data) + continue; + + if (subs_data->subscriber == subscriber) + break; + } + + if (link != NULL) { + time_t new_range_start = range_start, new_range_end = range_end; + time_t old_range_start, old_range_end; + + /* The subscriber updates its time range (it is already known) */ + subs_data = link->data; + + /* No range change */ + if (range_start == subs_data->range_start && + range_end == subs_data->range_end) { + UNLOCK_PROPS (); + return; + } + + old_range_start = subs_data->range_start; + old_range_end = subs_data->range_end; + + if (new_range_start == (time_t) 0 && new_range_end == (time_t) 0) { + new_range_start = data_model->priv->range_start; + new_range_end = data_model->priv->range_end; + } + + if (new_range_start == (time_t) 0 && new_range_end == (time_t) 0) { + /* The subscriber is looking for everything and the data_model has everything too */ + e_cal_data_model_subscriber_freeze (subs_data->subscriber); + cal_data_model_foreach_component (data_model, + new_range_start, old_range_start, + cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE); + e_cal_data_model_subscriber_thaw (subs_data->subscriber); + } else { + e_cal_data_model_subscriber_freeze (subs_data->subscriber); + + if (new_range_start >= old_range_end || + new_range_end <= old_range_start) { + subs_data->range_start = range_start; + subs_data->range_end = range_end; + + /* Completely new range, not overlapping with the former range, + everything previously added can be removed... */ + cal_data_model_foreach_component (data_model, + old_range_start, old_range_end, + cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE); + + subs_data->range_start = old_range_start; + subs_data->range_end = old_range_end; + + /* ...and components from the new range can be added */ + cal_data_model_foreach_component (data_model, + new_range_start, new_range_end, + cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE); + } else { + if (new_range_start < old_range_start) { + /* Add those known in the new extended range from the start */ + cal_data_model_foreach_component (data_model, + new_range_start, old_range_start, + cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE); + } else if (new_range_start > old_range_start) { + subs_data->range_start = range_start; + subs_data->range_end = range_end; + + /* Remove those out of the new range from the start */ + cal_data_model_foreach_component (data_model, + old_range_start, new_range_start, + cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE); + + subs_data->range_start = old_range_start; + subs_data->range_end = old_range_end; + } + + if (new_range_end > old_range_end) { + /* Add those known in the new extended range from the end */ + cal_data_model_foreach_component (data_model, + old_range_end, new_range_end, + cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE); + } else if (new_range_end < old_range_end) { + subs_data->range_start = range_start; + subs_data->range_end = range_end; + + /* Remove those out of the new range from the end */ + cal_data_model_foreach_component (data_model, + new_range_end, old_range_end, + cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE); + + subs_data->range_start = old_range_start; + subs_data->range_end = old_range_end; + } + } + + e_cal_data_model_subscriber_thaw (subs_data->subscriber); + } + + subs_data->range_start = range_start; + subs_data->range_end = range_end; + } else { + subs_data = subscriber_data_new (subscriber, range_start, range_end); + + data_model->priv->subscribers = g_slist_prepend (data_model->priv->subscribers, subs_data); + + e_cal_data_model_subscriber_freeze (subscriber); + cal_data_model_foreach_component (data_model, range_start, range_end, + cal_data_model_add_to_subscriber, subscriber, TRUE); + e_cal_data_model_subscriber_thaw (subscriber); + } + + cal_data_model_update_time_range (data_model); + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_unsubscribe: + * @data_model: an #EDataModel instance + * @subscriber: an #ECalDataModelSubscriber instance + * + * Removes the @subscriber from the set of subscribers for the @data_model. + * Remove of the @subscriber, which is not in the set of subscribers for + * the @data_model does nothing. + * + * Note: The @subscriber is not notified about a removal of the components + * which could be added previously, while it was subscribed for the change + * notifications. + * + * Since: 3.14 + **/ +void +e_cal_data_model_unsubscribe (ECalDataModel *data_model, + ECalDataModelSubscriber *subscriber) +{ + GSList *link; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber)); + + LOCK_PROPS (); + + for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) { + SubscriberData *subs_data = link->data; + + if (!subs_data) + continue; + + if (subs_data->subscriber == subscriber) { + data_model->priv->subscribers = g_slist_remove (data_model->priv->subscribers, subs_data); + subscriber_data_free (subs_data); + break; + } + } + + cal_data_model_update_time_range (data_model); + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_get_subscriber_range: + * @data_model: an #EDataModel instance + * @subscriber: an #ECalDataModelSubscriber instance + * @range_start: (out): time range start for the @subscriber + * @range_end: (out): time range end for the @subscriber + * + * Obtains currently set time range for the @subscriber. In case + * the subscriber is not found returns #FALSE and both @range_start + * and @range_end are left untouched. + * + * Returns: Whether the @subscriber was found and the @range_start with + * the @range_end were set to its current time range it uses. + * + * Since: 3.14 + **/ +gboolean +e_cal_data_model_get_subscriber_range (ECalDataModel *data_model, + ECalDataModelSubscriber *subscriber, + time_t *range_start, + time_t *range_end) +{ + GSList *link; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE); + g_return_val_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber), FALSE); + g_return_val_if_fail (range_start, FALSE); + g_return_val_if_fail (range_end, FALSE); + + LOCK_PROPS (); + + for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) { + SubscriberData *subs_data = link->data; + + if (!subs_data) + continue; + + if (subs_data->subscriber == subscriber) { + *range_start = subs_data->range_start; + *range_end = subs_data->range_end; + break; + } + } + + UNLOCK_PROPS (); + + return link != NULL; +} + +/** + * e_cal_data_model_freeze_views_update: + * @data_model: an #EDataModel instance + * + * Freezes any views updates until e_cal_data_model_thaw_views_update() is + * called. This can be called nested, then the same count of the calls of + * e_cal_data_model_thaw_views_update() is expected to unlock the views update. + * + * Since: 3.14 + **/ +void +e_cal_data_model_freeze_views_update (ECalDataModel *data_model) +{ + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + data_model->priv->views_update_freeze++; + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_thaw_views_update: + * @data_model: an #EDataModel instance + * + * A pair function for e_cal_data_model_freeze_views_update(), to unlock + * views update. + * + * Since: 3.14 + **/ +void +e_cal_data_model_thaw_views_update (ECalDataModel *data_model) +{ + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + + LOCK_PROPS (); + + if (!data_model->priv->views_update_freeze) { + UNLOCK_PROPS (); + g_warn_if_reached (); + return; + } + + data_model->priv->views_update_freeze--; + if (data_model->priv->views_update_freeze == 0 && + data_model->priv->views_update_required) + cal_data_model_rebuild_everything (data_model, TRUE); + + UNLOCK_PROPS (); +} + +/** + * e_cal_data_model_is_views_update_frozen: + * @data_model: an #EDataModel instance + * + * Check whether any views updates are currently frozen. This is influenced by + * e_cal_data_model_freeze_views_update() and e_cal_data_model_thaw_views_update(). + * + * Returns: Whether any views updates are currently frozen. + * + * Since: 3.14 + **/ +gboolean +e_cal_data_model_is_views_update_frozen (ECalDataModel *data_model) +{ + gboolean is_frozen; + + g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE); + + LOCK_PROPS (); + + is_frozen = data_model->priv->views_update_freeze > 0; + + UNLOCK_PROPS (); + + return is_frozen; +} diff --git a/src/e-cal-data-model.h b/src/e-cal-data-model.h new file mode 100644 index 00000000..5ddea296 --- /dev/null +++ b/src/e-cal-data-model.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + * + * Authors: Milan Crha + */ + +#ifndef E_CAL_DATA_MODEL_H +#define E_CAL_DATA_MODEL_H + +#include + +#include "e-cal-data-model-subscriber.h" + +/* Standard GObject macros */ +#define E_TYPE_CAL_DATA_MODEL \ + (e_cal_data_model_get_type ()) +#define E_CAL_DATA_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CAL_DATA_MODEL, ECalDataModel)) +#define E_CAL_DATA_MODEL_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CAL_DATA_MODEL, ECalDataModelClass)) +#define E_IS_CAL_DATA_MODEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CAL_DATA_MODEL)) +#define E_IS_CAL_DATA_MODEL_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CAL_DATA_MODEL)) +#define E_CAL_DATA_MODEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CAL_DATA_MODEL, ECalDataModelClass)) + +G_BEGIN_DECLS + +typedef enum { + E_CAL_DATA_MODEL_VIEW_STATE_START, + E_CAL_DATA_MODEL_VIEW_STATE_PROGRESS, + E_CAL_DATA_MODEL_VIEW_STATE_COMPLETE, + E_CAL_DATA_MODEL_VIEW_STATE_STOP +} ECalDataModelViewState; + +typedef struct _ECalDataModel ECalDataModel; +typedef struct _ECalDataModelClass ECalDataModelClass; +typedef struct _ECalDataModelPrivate ECalDataModelPrivate; + +struct _ECalDataModel { + GObject parent; + ECalDataModelPrivate *priv; +}; + +struct _ECalDataModelClass { + GObjectClass parent_class; + + /* Signals */ + void (* view_state_changed) (ECalDataModel *data_model, + ECalClientView *view, + ECalDataModelViewState state, + guint percent, + const gchar *message, + const GError *error); +}; + +typedef void (* EThreadJobFunc) (gpointer user_data, + GCancellable *cancellable, + GError **error); +typedef GCancellable * (* ECalDataModelSubmitThreadJobFunc) + (EThreadJobFunc func, + gpointer user_data, + GDestroyNotify free_user_data); + +GType e_cal_data_model_get_type (void); +ECalDataModel * e_cal_data_model_new (ECalDataModelSubmitThreadJobFunc func); +ECalDataModel * e_cal_data_model_new_clone (ECalDataModel *src_data_model); +GCancellable * e_cal_data_model_submit_thread_job + (ECalDataModel *data_model, + EThreadJobFunc func, + gpointer user_data, + GDestroyNotify free_user_data); +gboolean e_cal_data_model_get_disposing (ECalDataModel *data_model); +void e_cal_data_model_set_disposing (ECalDataModel *data_model, + gboolean disposing); +gboolean e_cal_data_model_get_expand_recurrences + (ECalDataModel *data_model); +void e_cal_data_model_set_expand_recurrences + (ECalDataModel *data_model, + gboolean expand_recurrences); +icaltimezone * e_cal_data_model_get_timezone (ECalDataModel *data_model); +void e_cal_data_model_set_timezone (ECalDataModel *data_model, + icaltimezone *zone); +void e_cal_data_model_set_filter (ECalDataModel *data_model, + const gchar *sexp); +gchar * e_cal_data_model_dup_filter (ECalDataModel *data_model); +void e_cal_data_model_add_client (ECalDataModel *data_model, + ECalClient *client); +void e_cal_data_model_remove_client (ECalDataModel *data_model, + const gchar *uid); +ECalClient * e_cal_data_model_ref_client (ECalDataModel *data_model, + const gchar *uid); +GList * e_cal_data_model_get_clients (ECalDataModel *data_model); +GSList * e_cal_data_model_get_components (ECalDataModel *data_model, + time_t in_range_start, + time_t in_range_end); + +typedef gboolean (* ECalDataModelForeachFunc) (ECalDataModel *data_model, + ECalClient *client, + const ECalComponentId *id, + ECalComponent *comp, + time_t instance_start, + time_t instance_end, + gpointer user_data); + +gboolean e_cal_data_model_foreach_component + (ECalDataModel *data_model, + time_t in_range_start, + time_t in_range_end, + ECalDataModelForeachFunc func, + gpointer user_data); + +void e_cal_data_model_subscribe (ECalDataModel *data_model, + ECalDataModelSubscriber *subscriber, + time_t range_start, + time_t range_end); +void e_cal_data_model_unsubscribe (ECalDataModel *data_model, + ECalDataModelSubscriber *subscriber); +gboolean e_cal_data_model_get_subscriber_range + (ECalDataModel *data_model, + ECalDataModelSubscriber *subscriber, + time_t *range_start, + time_t *range_end); +void e_cal_data_model_freeze_views_update + (ECalDataModel *data_model); +void e_cal_data_model_thaw_views_update + (ECalDataModel *data_model); +gboolean e_cal_data_model_is_views_update_frozen + (ECalDataModel *data_model); + +G_END_DECLS + +#endif /* E_CAL_DATA_MODEL_H */