Files
federated-calendar/src/e-cal-data-model.c
T
Georges Basile Stavracas Neto 5a75abb52e data-model: fix boudaries' checks
The end date is non-incluse, as defined by the iCalendar
protocol.
2016-02-18 03:31:35 +01:00

2938 lines
80 KiB
C

/*
* 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 (icaltime_is_null_time (end_time))
end_time = start_time;
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);
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);
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);
if (!client) {
UNLOCK_PROPS ();
return;
}
view_data = g_hash_table_lookup (data_model->priv->views, client);
g_clear_object (&client);
if (view_data) {
view_data_ref (view_data);
g_warn_if_fail (view_data->view == view);
}
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;
}
cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_COMPLETE, 0, NULL, error);
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);
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_remove_all (view_data->lost_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;
}