[gnome-calendar/ui-rewrite] build: added ECalDataModel related files



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 (&notif_data->client);
+       g_clear_object (&notif_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]