[gnome-calendar/ui-rewrite] build: added ECalDataModel related files
- From: Erick Pérez Castellanos <erickpc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-calendar/ui-rewrite] build: added ECalDataModel related files
- Date: Thu, 16 Oct 2014 12:39:48 +0000 (UTC)
commit b258e2202b6996f9d91b5b14d98e8054decc11ca
Author: Erick Pérez Castellanos <erick red gmail com>
Date: Wed Oct 15 20:36:04 2014 -0400
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(-)
---
diff --git a/configure.ac b/configure.ac
index 3e7d23f..92aa37e 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 d07d34a..20d38a7 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 0000000..b08b0c1
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha redhat com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 0000000..8d76fb0
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha redhat com>
+ */
+
+#ifndef E_CAL_DATA_MODEL_SUBSCRIBER_H
+#define E_CAL_DATA_MODEL_SUBSCRIBER_H
+
+#include <libecal/libecal.h>
+
+/* 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 0000000..43da454
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha redhat com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+/*#include <glib/gi18n-lib.h>*/
+
+#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 0000000..5ddea29
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha redhat com>
+ */
+
+#ifndef E_CAL_DATA_MODEL_H
+#define E_CAL_DATA_MODEL_H
+
+#include <libecal/libecal.h>
+
+#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 */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]