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.
2928 lines
79 KiB
C
2928 lines
79 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 (start_time.zone) {
|
|
zone = start_time.zone;
|
|
} else {
|
|
icalparameter *param = NULL;
|
|
icalproperty *prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY);
|
|
|
|
if (prop) {
|
|
param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
|
|
|
|
if (param) {
|
|
const gchar *tzid = NULL;
|
|
icaltimezone *st_zone = NULL;
|
|
|
|
tzid = icalparameter_get_tzid (param);
|
|
if (tzid)
|
|
e_cal_client_get_timezone_sync (client, tzid, &st_zone, cancellable, NULL);
|
|
|
|
if (st_zone)
|
|
zone = st_zone;
|
|
}
|
|
}
|
|
}
|
|
|
|
*instance_start = icaltime_as_timet_with_zone (start_time, zone);
|
|
if (start_is_date)
|
|
*start_is_date = start_time.is_date;
|
|
|
|
if (end_time.zone) {
|
|
zone = end_time.zone;
|
|
} else {
|
|
icalparameter *param = NULL;
|
|
icalproperty *prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY);
|
|
|
|
if (prop) {
|
|
param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
|
|
|
|
if (param) {
|
|
const gchar *tzid = NULL;
|
|
icaltimezone *end_zone = NULL;
|
|
|
|
tzid = icalparameter_get_tzid (param);
|
|
if (tzid)
|
|
e_cal_client_get_timezone_sync (client, tzid, &end_zone, cancellable, NULL);
|
|
|
|
if (end_zone)
|
|
zone = end_zone;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
*instance_end = icaltime_as_timet_with_zone (end_time, zone);
|
|
if (end_is_date)
|
|
*end_is_date = end_time.is_date;
|
|
}
|
|
|
|
struct _ECalDataModelPrivate {
|
|
GThread *main_thread;
|
|
ECalDataModelSubmitThreadJobFunc submit_thread_job_func;
|
|
GWeakRef *submit_thread_job_responder;
|
|
GThreadPool *thread_pool;
|
|
|
|
GRecMutex props_lock; /* to guard all the below members */
|
|
|
|
gboolean disposing;
|
|
gboolean expand_recurrences;
|
|
gchar *filter;
|
|
gchar *full_filter; /* to be used with views */
|
|
icaltimezone *zone;
|
|
time_t range_start;
|
|
time_t range_end;
|
|
|
|
GHashTable *clients; /* ESource::uid ~> ECalClient */
|
|
GHashTable *views; /* ECalClient ~> ViewData */
|
|
GSList *subscribers; /* ~> SubscriberData */
|
|
|
|
guint32 views_update_freeze;
|
|
gboolean views_update_required;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_EXPAND_RECURRENCES,
|
|
PROP_TIMEZONE
|
|
};
|
|
|
|
enum {
|
|
VIEW_STATE_CHANGED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL];
|
|
|
|
G_DEFINE_TYPE (ECalDataModel, e_cal_data_model, G_TYPE_OBJECT)
|
|
|
|
typedef struct _ComponentData {
|
|
ECalComponent *component;
|
|
time_t instance_start;
|
|
time_t instance_end;
|
|
gboolean is_detached;
|
|
} ComponentData;
|
|
|
|
typedef struct _ViewData {
|
|
gint ref_count;
|
|
GRecMutex lock;
|
|
gboolean is_used;
|
|
|
|
ECalClient *client;
|
|
ECalClientView *view;
|
|
gulong objects_added_id;
|
|
gulong objects_modified_id;
|
|
gulong objects_removed_id;
|
|
gulong progress_id;
|
|
gulong complete_id;
|
|
|
|
GHashTable *components; /* ECalComponentId ~> ComponentData */
|
|
GHashTable *lost_components; /* ECalComponentId ~> ComponentData; when re-running view, valid till 'complete' is received */
|
|
gboolean received_complete;
|
|
GSList *to_expand_recurrences; /* icalcomponent */
|
|
GSList *expanded_recurrences; /* ComponentData */
|
|
gint pending_expand_recurrences; /* how many is waiting to be processed */
|
|
|
|
GCancellable *cancellable;
|
|
} ViewData;
|
|
|
|
typedef struct _SubscriberData {
|
|
ECalDataModelSubscriber *subscriber;
|
|
time_t range_start;
|
|
time_t range_end;
|
|
} SubscriberData;
|
|
|
|
static ComponentData *
|
|
component_data_new (ECalComponent *comp,
|
|
time_t instance_start,
|
|
time_t instance_end,
|
|
gboolean is_detached)
|
|
{
|
|
ComponentData *comp_data;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
|
|
|
|
comp_data = g_new0 (ComponentData, 1);
|
|
comp_data->component = g_object_ref (comp);
|
|
comp_data->instance_start = instance_start;
|
|
comp_data->instance_end = instance_end;
|
|
comp_data->is_detached = is_detached;
|
|
|
|
return comp_data;
|
|
}
|
|
|
|
static void
|
|
component_data_free (gpointer ptr)
|
|
{
|
|
ComponentData *comp_data = ptr;
|
|
|
|
if (comp_data) {
|
|
g_object_unref (comp_data->component);
|
|
g_free (comp_data);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
component_data_equal (ComponentData *comp_data1,
|
|
ComponentData *comp_data2)
|
|
{
|
|
icalcomponent *icomp1, *icomp2;
|
|
struct icaltimetype tt1, tt2;
|
|
gchar *as_str1, *as_str2;
|
|
gboolean equal;
|
|
|
|
if (comp_data1 == comp_data2)
|
|
return TRUE;
|
|
|
|
if (!comp_data1 || !comp_data2 || !comp_data1->component || !comp_data2->component)
|
|
return FALSE;
|
|
|
|
if (comp_data1->instance_start != comp_data2->instance_start ||
|
|
comp_data1->instance_end != comp_data2->instance_end)
|
|
return FALSE;
|
|
|
|
icomp1 = e_cal_component_get_icalcomponent (comp_data1->component);
|
|
icomp2 = e_cal_component_get_icalcomponent (comp_data2->component);
|
|
|
|
if (!icomp1 || !icomp2 ||
|
|
icalcomponent_get_sequence (icomp1) != icalcomponent_get_sequence (icomp2) ||
|
|
g_strcmp0 (icalcomponent_get_uid (icomp1), icalcomponent_get_uid (icomp2)) != 0)
|
|
return FALSE;
|
|
|
|
tt1 = icalcomponent_get_recurrenceid (icomp1);
|
|
tt2 = icalcomponent_get_recurrenceid (icomp2);
|
|
if ((icaltime_is_valid_time (tt1) ? 1 : 0) != (icaltime_is_valid_time (tt2) ? 1 : 0) ||
|
|
(icaltime_is_null_time (tt1) ? 1 : 0) != (icaltime_is_null_time (tt2) ? 1 : 0) ||
|
|
icaltime_compare (tt1, tt2) != 0)
|
|
return FALSE;
|
|
|
|
tt1 = icalcomponent_get_dtstamp (icomp1);
|
|
tt2 = icalcomponent_get_dtstamp (icomp2);
|
|
if ((icaltime_is_valid_time (tt1) ? 1 : 0) != (icaltime_is_valid_time (tt2) ? 1 : 0) ||
|
|
(icaltime_is_null_time (tt1) ? 1 : 0) != (icaltime_is_null_time (tt2) ? 1 : 0) ||
|
|
icaltime_compare (tt1, tt2) != 0)
|
|
return FALSE;
|
|
|
|
/* Maybe not so effective compare, but might be still more effective
|
|
than updating whole UI with false notifications */
|
|
as_str1 = icalcomponent_as_ical_string_r (icomp1);
|
|
as_str2 = icalcomponent_as_ical_string_r (icomp2);
|
|
|
|
equal = g_strcmp0 (as_str1, as_str2) == 0;
|
|
|
|
g_free (as_str1);
|
|
g_free (as_str2);
|
|
|
|
return equal;
|
|
}
|
|
|
|
static ViewData *
|
|
view_data_new (ECalClient *client)
|
|
{
|
|
ViewData *view_data;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
|
|
|
|
view_data = g_new0 (ViewData, 1);
|
|
view_data->ref_count = 1;
|
|
g_rec_mutex_init (&view_data->lock);
|
|
view_data->is_used = TRUE;
|
|
view_data->client = g_object_ref (client);
|
|
view_data->components = g_hash_table_new_full (
|
|
(GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal,
|
|
(GDestroyNotify) e_cal_component_free_id, component_data_free);
|
|
|
|
return view_data;
|
|
}
|
|
|
|
static void
|
|
view_data_disconnect_view (ViewData *view_data)
|
|
{
|
|
if (view_data && view_data->view) {
|
|
#define disconnect(x) G_STMT_START { \
|
|
if (view_data->x) { \
|
|
g_signal_handler_disconnect (view_data->view, view_data->x); \
|
|
view_data->x = 0; \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
disconnect (objects_added_id);
|
|
disconnect (objects_modified_id);
|
|
disconnect (objects_removed_id);
|
|
disconnect (progress_id);
|
|
disconnect (complete_id);
|
|
|
|
#undef disconnect
|
|
}
|
|
}
|
|
|
|
static ViewData *
|
|
view_data_ref (ViewData *view_data)
|
|
{
|
|
g_return_val_if_fail (view_data != NULL, NULL);
|
|
|
|
g_atomic_int_inc (&view_data->ref_count);
|
|
|
|
return view_data;
|
|
}
|
|
|
|
static void
|
|
view_data_unref (gpointer ptr)
|
|
{
|
|
ViewData *view_data = ptr;
|
|
|
|
if (view_data) {
|
|
if (g_atomic_int_dec_and_test (&view_data->ref_count)) {
|
|
view_data_disconnect_view (view_data);
|
|
if (view_data->cancellable)
|
|
g_cancellable_cancel (view_data->cancellable);
|
|
g_clear_object (&view_data->cancellable);
|
|
g_clear_object (&view_data->client);
|
|
g_clear_object (&view_data->view);
|
|
g_hash_table_destroy (view_data->components);
|
|
if (view_data->lost_components)
|
|
g_hash_table_destroy (view_data->lost_components);
|
|
g_slist_free_full (view_data->to_expand_recurrences, (GDestroyNotify) icalcomponent_free);
|
|
g_slist_free_full (view_data->expanded_recurrences, component_data_free);
|
|
g_rec_mutex_clear (&view_data->lock);
|
|
g_free (view_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
view_data_lock (ViewData *view_data)
|
|
{
|
|
g_return_if_fail (view_data != NULL);
|
|
|
|
g_rec_mutex_lock (&view_data->lock);
|
|
}
|
|
|
|
static void
|
|
view_data_unlock (ViewData *view_data)
|
|
{
|
|
g_return_if_fail (view_data != NULL);
|
|
|
|
g_rec_mutex_unlock (&view_data->lock);
|
|
}
|
|
|
|
static SubscriberData *
|
|
subscriber_data_new (ECalDataModelSubscriber *subscriber,
|
|
time_t range_start,
|
|
time_t range_end)
|
|
{
|
|
SubscriberData *subs_data;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber), NULL);
|
|
|
|
subs_data = g_new0 (SubscriberData, 1);
|
|
subs_data->subscriber = g_object_ref (subscriber);
|
|
subs_data->range_start = range_start;
|
|
subs_data->range_end = range_end;
|
|
|
|
return subs_data;
|
|
}
|
|
|
|
static void
|
|
subscriber_data_free (gpointer ptr)
|
|
{
|
|
SubscriberData *subs_data = ptr;
|
|
|
|
if (subs_data) {
|
|
g_clear_object (&subs_data->subscriber);
|
|
g_free (subs_data);
|
|
}
|
|
}
|
|
|
|
typedef struct _ViewStateChangedData {
|
|
ECalDataModel *data_model;
|
|
ECalClientView *view;
|
|
ECalDataModelViewState state;
|
|
guint percent;
|
|
gchar *message;
|
|
GError *error;
|
|
} ViewStateChangedData;
|
|
|
|
static void
|
|
view_state_changed_data_free (gpointer ptr)
|
|
{
|
|
ViewStateChangedData *vscd = ptr;
|
|
|
|
if (vscd) {
|
|
g_clear_object (&vscd->data_model);
|
|
g_clear_object (&vscd->view);
|
|
g_clear_error (&vscd->error);
|
|
g_free (vscd->message);
|
|
g_free (vscd);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
cal_data_model_emit_view_state_changed_timeout_cb (gpointer user_data)
|
|
{
|
|
ViewStateChangedData *vscd = user_data;
|
|
|
|
g_return_val_if_fail (vscd != NULL, FALSE);
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (vscd->data_model), FALSE);
|
|
g_return_val_if_fail (E_IS_CAL_CLIENT_VIEW (vscd->view), FALSE);
|
|
|
|
g_signal_emit (vscd->data_model, signals[VIEW_STATE_CHANGED], 0,
|
|
vscd->view, vscd->state, vscd->percent, vscd->message, vscd->error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
cal_data_model_emit_view_state_changed (ECalDataModel *data_model,
|
|
ECalClientView *view,
|
|
ECalDataModelViewState state,
|
|
guint percent,
|
|
const gchar *message,
|
|
const GError *error)
|
|
{
|
|
ViewStateChangedData *vscd;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (E_IS_CAL_CLIENT_VIEW (view));
|
|
|
|
if (e_cal_data_model_get_disposing (data_model))
|
|
return;
|
|
|
|
vscd = g_new0 (ViewStateChangedData, 1);
|
|
vscd->data_model = g_object_ref (data_model);
|
|
vscd->view = g_object_ref (view);
|
|
vscd->state = state;
|
|
vscd->percent = percent;
|
|
vscd->message = g_strdup (message);
|
|
vscd->error = error ? g_error_copy (error) : NULL;
|
|
|
|
g_timeout_add_full (G_PRIORITY_DEFAULT, 1,
|
|
cal_data_model_emit_view_state_changed_timeout_cb,
|
|
vscd, view_state_changed_data_free);
|
|
}
|
|
|
|
typedef void (* InternalThreadJobFunc) (ECalDataModel *data_model, gpointer user_data);
|
|
|
|
typedef struct _InternalThreadJobData {
|
|
InternalThreadJobFunc func;
|
|
gpointer user_data;
|
|
} InternalThreadJobData;
|
|
|
|
static void
|
|
cal_data_model_internal_thread_job_func (gpointer data,
|
|
gpointer user_data)
|
|
{
|
|
ECalDataModel *data_model = user_data;
|
|
InternalThreadJobData *job_data = data;
|
|
|
|
g_return_if_fail (job_data != NULL);
|
|
g_return_if_fail (job_data->func != NULL);
|
|
|
|
job_data->func (data_model, job_data->user_data);
|
|
|
|
g_free (job_data);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_submit_internal_thread_job (ECalDataModel *data_model,
|
|
InternalThreadJobFunc func,
|
|
gpointer user_data)
|
|
{
|
|
InternalThreadJobData *job_data;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (func != NULL);
|
|
|
|
job_data = g_new0 (InternalThreadJobData, 1);
|
|
job_data->func = func;
|
|
job_data->user_data = user_data;
|
|
|
|
g_thread_pool_push (data_model->priv->thread_pool, job_data, NULL);
|
|
}
|
|
|
|
typedef struct _SubmitThreadJobData {
|
|
ECalDataModel *data_model;
|
|
EThreadJobFunc func;
|
|
gpointer user_data;
|
|
GDestroyNotify free_user_data;
|
|
|
|
GCancellable *cancellable;
|
|
gboolean finished;
|
|
GMutex mutex;
|
|
GCond cond;
|
|
} SubmitThreadJobData;
|
|
|
|
static gboolean
|
|
cal_data_model_call_submit_thread_job (gpointer user_data)
|
|
{
|
|
SubmitThreadJobData *stj_data = user_data;
|
|
|
|
g_return_val_if_fail (stj_data != NULL, FALSE);
|
|
|
|
g_mutex_lock (&stj_data->mutex);
|
|
|
|
stj_data->cancellable = stj_data->data_model->priv->submit_thread_job_func (stj_data->func, stj_data->user_data, stj_data->free_user_data);
|
|
|
|
stj_data->finished = TRUE;
|
|
g_cond_signal (&stj_data->cond);
|
|
g_mutex_unlock (&stj_data->mutex);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_submit_thread_job:
|
|
* @data_model: an #ECalDataModel
|
|
* @description: user-friendly description of the job, to be shown in UI
|
|
* @alert_ident: in case of an error, this alert identificator is used
|
|
* for EAlert construction
|
|
* @alert_arg_0: (allow-none): in case of an error, use this string as
|
|
* the first argument to the EAlert construction; the second argument
|
|
* is the actual error message; can be #NULL, in which case only
|
|
* the error message is passed to the EAlert construction
|
|
* @func: function to be run in a dedicated thread
|
|
* @user_data: (allow-none): custom data passed into @func; can be #NULL
|
|
* @free_user_data: (allow-none): function to be called on @user_data,
|
|
* when the job is over; can be #NULL
|
|
*
|
|
* Runs the @func in a dedicated thread. Any error is propagated to UI.
|
|
* The cancellable passed into the @func is a #CamelOperation, thus
|
|
* the caller can overwrite progress and description message on it.
|
|
*
|
|
* Returns: (transfer full): Newly created #GCancellable on success.
|
|
* The caller is responsible to g_object_unref() it when done with it.
|
|
*
|
|
* Note: The @free_user_data, if set, is called in the main thread.
|
|
*
|
|
* Note: This is a blocking call, it waits until the thread job is submitted.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
GCancellable *
|
|
e_cal_data_model_submit_thread_job (ECalDataModel *data_model,
|
|
EThreadJobFunc func,
|
|
gpointer user_data,
|
|
GDestroyNotify free_user_data)
|
|
{
|
|
SubmitThreadJobData stj_data;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
|
|
g_return_val_if_fail (data_model->priv->submit_thread_job_func != NULL, NULL);
|
|
|
|
if (g_thread_self () == data_model->priv->main_thread) {
|
|
GCancellable *cancellable;
|
|
|
|
cancellable = data_model->priv->submit_thread_job_func (
|
|
func, user_data, free_user_data);
|
|
|
|
return cancellable;
|
|
}
|
|
|
|
stj_data.data_model = data_model;
|
|
stj_data.func = func;
|
|
stj_data.user_data = user_data;
|
|
stj_data.free_user_data = free_user_data;
|
|
stj_data.cancellable = NULL;
|
|
stj_data.finished = FALSE;
|
|
g_mutex_init (&stj_data.mutex);
|
|
g_cond_init (&stj_data.cond);
|
|
|
|
g_timeout_add (1, cal_data_model_call_submit_thread_job, &stj_data);
|
|
|
|
g_mutex_lock (&stj_data.mutex);
|
|
while (!stj_data.finished) {
|
|
g_cond_wait (&stj_data.cond, &stj_data.mutex);
|
|
}
|
|
g_mutex_unlock (&stj_data.mutex);
|
|
|
|
g_cond_clear (&stj_data.cond);
|
|
g_mutex_clear (&stj_data.mutex);
|
|
|
|
return stj_data.cancellable;
|
|
}
|
|
|
|
typedef void (* ECalDataModelForeachSubscriberFunc) (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
ECalDataModelSubscriber *subscriber,
|
|
gpointer user_data);
|
|
|
|
static void
|
|
cal_data_model_foreach_subscriber_in_range (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
time_t in_range_start,
|
|
time_t in_range_end,
|
|
ECalDataModelForeachSubscriberFunc func,
|
|
gpointer user_data)
|
|
{
|
|
GSList *link;
|
|
|
|
g_return_if_fail (func != NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if (in_range_end == (time_t) 0) {
|
|
in_range_end = in_range_start;
|
|
}
|
|
|
|
for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
|
|
SubscriberData *subs_data = link->data;
|
|
|
|
if ((in_range_start == (time_t) 0 && in_range_end == (time_t) 0) ||
|
|
(subs_data->range_start == (time_t) 0 && subs_data->range_end == (time_t) 0) ||
|
|
(subs_data->range_start <= in_range_end && subs_data->range_end >= in_range_start))
|
|
func (data_model, client, subs_data->subscriber, user_data);
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
static void
|
|
cal_data_model_foreach_subscriber (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
ECalDataModelForeachSubscriberFunc func,
|
|
gpointer user_data)
|
|
{
|
|
g_return_if_fail (func != NULL);
|
|
|
|
cal_data_model_foreach_subscriber_in_range (data_model, client, (time_t) 0, (time_t) 0, func, user_data);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_freeze_subscriber_cb (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
ECalDataModelSubscriber *subscriber,
|
|
gpointer user_data)
|
|
{
|
|
e_cal_data_model_subscriber_freeze (subscriber);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_thaw_subscriber_cb (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
ECalDataModelSubscriber *subscriber,
|
|
gpointer user_data)
|
|
{
|
|
e_cal_data_model_subscriber_thaw (subscriber);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_freeze_all_subscribers (ECalDataModel *data_model)
|
|
{
|
|
cal_data_model_foreach_subscriber (data_model, NULL, cal_data_model_freeze_subscriber_cb, NULL);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_thaw_all_subscribers (ECalDataModel *data_model)
|
|
{
|
|
cal_data_model_foreach_subscriber (data_model, NULL, cal_data_model_thaw_subscriber_cb, NULL);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_add_component_cb (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
ECalDataModelSubscriber *subscriber,
|
|
gpointer user_data)
|
|
{
|
|
ECalComponent *comp = user_data;
|
|
|
|
g_return_if_fail (comp != NULL);
|
|
|
|
e_cal_data_model_subscriber_component_added (subscriber, client, comp);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_modify_component_cb (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
ECalDataModelSubscriber *subscriber,
|
|
gpointer user_data)
|
|
{
|
|
ECalComponent *comp = user_data;
|
|
|
|
g_return_if_fail (comp != NULL);
|
|
|
|
e_cal_data_model_subscriber_component_modified (subscriber, client, comp);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_remove_one_view_component_cb (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
ECalDataModelSubscriber *subscriber,
|
|
gpointer user_data)
|
|
{
|
|
const ECalComponentId *id = user_data;
|
|
|
|
g_return_if_fail (id != NULL);
|
|
|
|
e_cal_data_model_subscriber_component_removed (subscriber, client, id->uid, id->rid);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_remove_components (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
GHashTable *components,
|
|
GHashTable *also_remove_from)
|
|
{
|
|
GList *ids, *ilink;
|
|
|
|
g_return_if_fail (data_model != NULL);
|
|
g_return_if_fail (components != NULL);
|
|
|
|
cal_data_model_freeze_all_subscribers (data_model);
|
|
|
|
ids = g_hash_table_get_keys (components);
|
|
|
|
for (ilink = ids; ilink; ilink = g_list_next (ilink)) {
|
|
ECalComponentId *id = ilink->data;
|
|
ComponentData *comp_data;
|
|
time_t instance_start = (time_t) 0, instance_end = (time_t) 0;
|
|
|
|
if (!id)
|
|
continue;
|
|
|
|
/* Try to limit which subscribers will be notified about removal */
|
|
comp_data = g_hash_table_lookup (components, id);
|
|
if (comp_data) {
|
|
instance_start = comp_data->instance_start;
|
|
instance_end = comp_data->instance_end;
|
|
}
|
|
|
|
cal_data_model_foreach_subscriber_in_range (data_model, client,
|
|
instance_start, instance_end,
|
|
cal_data_model_remove_one_view_component_cb, id);
|
|
|
|
if (also_remove_from)
|
|
g_hash_table_remove (also_remove_from, id);
|
|
}
|
|
|
|
g_list_free (ids);
|
|
|
|
cal_data_model_thaw_all_subscribers (data_model);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_calc_range (ECalDataModel *data_model,
|
|
time_t *range_start,
|
|
time_t *range_end)
|
|
{
|
|
GSList *link;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (range_start != NULL);
|
|
g_return_if_fail (range_end != NULL);
|
|
|
|
*range_start = (time_t) 0;
|
|
*range_end = (time_t) 0;
|
|
|
|
LOCK_PROPS ();
|
|
|
|
for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
|
|
SubscriberData *subs_data = link->data;
|
|
|
|
if (!subs_data)
|
|
continue;
|
|
|
|
if (subs_data->range_start == (time_t) 0 && subs_data->range_end == (time_t) 0) {
|
|
*range_start = (time_t) 0;
|
|
*range_end = (time_t) 0;
|
|
break;
|
|
}
|
|
|
|
if (link == data_model->priv->subscribers) {
|
|
*range_start = subs_data->range_start;
|
|
*range_end = subs_data->range_end;
|
|
} else {
|
|
if (*range_start > subs_data->range_start)
|
|
*range_start = subs_data->range_start;
|
|
if (*range_end < subs_data->range_end)
|
|
*range_end = subs_data->range_end;
|
|
}
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
static gboolean
|
|
cal_data_model_update_full_filter (ECalDataModel *data_model)
|
|
{
|
|
gchar *filter;
|
|
time_t range_start, range_end;
|
|
gboolean changed;
|
|
|
|
LOCK_PROPS ();
|
|
|
|
cal_data_model_calc_range (data_model, &range_start, &range_end);
|
|
|
|
if (range_start != (time_t) 0 || range_end != (time_t) 0) {
|
|
gchar *iso_start, *iso_end;
|
|
const gchar *default_tzloc = NULL;
|
|
|
|
iso_start = isodate_from_time_t (range_start);
|
|
iso_end = isodate_from_time_t (range_end);
|
|
|
|
if (data_model->priv->zone && data_model->priv->zone != icaltimezone_get_utc_timezone ())
|
|
default_tzloc = icaltimezone_get_location (data_model->priv->zone);
|
|
if (!default_tzloc)
|
|
default_tzloc = "";
|
|
|
|
if (data_model->priv->filter) {
|
|
filter = g_strdup_printf (
|
|
"(and (occur-in-time-range? (make-time \"%s\") (make-time \"%s\") \"%s\") %s)",
|
|
iso_start, iso_end, default_tzloc, data_model->priv->filter);
|
|
} else {
|
|
filter = g_strdup_printf (
|
|
"(occur-in-time-range? (make-time \"%s\") (make-time \"%s\") \"%s\")",
|
|
iso_start, iso_end, default_tzloc);
|
|
}
|
|
|
|
g_free (iso_start);
|
|
g_free (iso_end);
|
|
} else if (data_model->priv->filter) {
|
|
filter = g_strdup (data_model->priv->filter);
|
|
} else {
|
|
filter = g_strdup ("#t");
|
|
}
|
|
|
|
changed = g_strcmp0 (data_model->priv->full_filter, filter) != 0;
|
|
|
|
if (changed) {
|
|
g_free (data_model->priv->full_filter);
|
|
data_model->priv->full_filter = filter;
|
|
} else {
|
|
g_free (filter);
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* This consumes the comp_data - not so nice, but simpler
|
|
than adding reference counter for the structure */
|
|
static void
|
|
cal_data_model_process_added_component (ECalDataModel *data_model,
|
|
ViewData *view_data,
|
|
ComponentData *comp_data,
|
|
GHashTable *known_instances)
|
|
{
|
|
ECalComponentId *id;
|
|
ComponentData *old_comp_data = NULL;
|
|
gboolean comp_data_equal;
|
|
|
|
g_return_if_fail (data_model != NULL);
|
|
g_return_if_fail (view_data != NULL);
|
|
g_return_if_fail (comp_data != NULL);
|
|
|
|
id = e_cal_component_get_id (comp_data->component);
|
|
g_return_if_fail (id != NULL);
|
|
|
|
view_data_lock (view_data);
|
|
|
|
if (!old_comp_data && view_data->lost_components)
|
|
old_comp_data = g_hash_table_lookup (view_data->lost_components, id);
|
|
|
|
if (!old_comp_data && known_instances)
|
|
old_comp_data = g_hash_table_lookup (known_instances, id);
|
|
|
|
if (!old_comp_data)
|
|
old_comp_data = g_hash_table_lookup (view_data->components, id);
|
|
|
|
if (old_comp_data) {
|
|
/* It can be a previously added detached instance received
|
|
during recurrences expand */
|
|
if (!comp_data->is_detached)
|
|
comp_data->is_detached = old_comp_data->is_detached;
|
|
}
|
|
|
|
comp_data_equal = component_data_equal (comp_data, old_comp_data);
|
|
|
|
if (view_data->lost_components)
|
|
g_hash_table_remove (view_data->lost_components, id);
|
|
|
|
if (known_instances)
|
|
g_hash_table_remove (known_instances, id);
|
|
|
|
/* Note: old_comp_data is freed or NULL now */
|
|
|
|
/* 'id' is stolen by view_data->components */
|
|
g_hash_table_insert (view_data->components, id, comp_data);
|
|
|
|
if (!comp_data_equal) {
|
|
if (!old_comp_data)
|
|
cal_data_model_foreach_subscriber_in_range (data_model, view_data->client,
|
|
comp_data->instance_start, comp_data->instance_end,
|
|
cal_data_model_add_component_cb, comp_data->component);
|
|
else
|
|
cal_data_model_foreach_subscriber_in_range (data_model, view_data->client,
|
|
comp_data->instance_start, comp_data->instance_end,
|
|
cal_data_model_modify_component_cb, comp_data->component);
|
|
}
|
|
|
|
view_data_unlock (view_data);
|
|
}
|
|
|
|
typedef struct _GatherComponentsData {
|
|
const gchar *uid;
|
|
GList **pcomponent_ids; /* ECalComponentId, can be owned by the hash table */
|
|
GHashTable *component_ids_hash;
|
|
gboolean copy_ids;
|
|
gboolean all_instances; /* FALSE to get only nondetached component instances */
|
|
} GatherComponentsData;
|
|
|
|
static void
|
|
cal_data_model_gather_components (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
ECalComponentId *id = key;
|
|
ComponentData *comp_data = value;
|
|
GatherComponentsData *gather_data = user_data;
|
|
|
|
g_return_if_fail (id != NULL);
|
|
g_return_if_fail (comp_data != NULL);
|
|
g_return_if_fail (gather_data != NULL);
|
|
g_return_if_fail (gather_data->pcomponent_ids != NULL || gather_data->component_ids_hash != NULL);
|
|
g_return_if_fail (gather_data->pcomponent_ids == NULL || gather_data->component_ids_hash == NULL);
|
|
|
|
if ((gather_data->all_instances || !comp_data->is_detached) && g_strcmp0 (id->uid, gather_data->uid) == 0) {
|
|
if (gather_data->component_ids_hash) {
|
|
ComponentData *comp_data_copy;
|
|
|
|
comp_data_copy = component_data_new (comp_data->component,
|
|
comp_data->instance_start, comp_data->instance_end,
|
|
comp_data->is_detached);
|
|
|
|
if (gather_data->copy_ids) {
|
|
g_hash_table_insert (gather_data->component_ids_hash,
|
|
e_cal_component_id_copy (id), comp_data_copy);
|
|
} else {
|
|
g_hash_table_insert (gather_data->component_ids_hash, id, comp_data_copy);
|
|
}
|
|
} else if (gather_data->copy_ids) {
|
|
*gather_data->pcomponent_ids = g_list_prepend (*gather_data->pcomponent_ids,
|
|
e_cal_component_id_copy (id));
|
|
} else {
|
|
*gather_data->pcomponent_ids = g_list_prepend (*gather_data->pcomponent_ids, id);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct _NotifyRecurrencesData {
|
|
ECalDataModel *data_model;
|
|
ECalClient *client;
|
|
} NotifyRecurrencesData;
|
|
|
|
static gboolean
|
|
cal_data_model_notify_recurrences_cb (gpointer user_data)
|
|
{
|
|
NotifyRecurrencesData *notif_data = user_data;
|
|
ECalDataModel *data_model;
|
|
ViewData *view_data;
|
|
|
|
g_return_val_if_fail (notif_data != NULL, FALSE);
|
|
|
|
data_model = notif_data->data_model;
|
|
|
|
LOCK_PROPS ();
|
|
|
|
view_data = g_hash_table_lookup (data_model->priv->views, notif_data->client);
|
|
if (view_data)
|
|
view_data_ref (view_data);
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
if (view_data) {
|
|
GHashTable *gathered_uids;
|
|
GHashTable *known_instances;
|
|
GSList *expanded_recurrences, *link;
|
|
|
|
view_data_lock (view_data);
|
|
expanded_recurrences = view_data->expanded_recurrences;
|
|
view_data->expanded_recurrences = NULL;
|
|
|
|
cal_data_model_freeze_all_subscribers (data_model);
|
|
|
|
gathered_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
known_instances = g_hash_table_new_full (
|
|
(GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal,
|
|
(GDestroyNotify) e_cal_component_free_id, component_data_free);
|
|
|
|
for (link = expanded_recurrences; link && view_data->is_used; link = g_slist_next (link)) {
|
|
ComponentData *comp_data = link->data;
|
|
icalcomponent *icomp;
|
|
const gchar *uid;
|
|
|
|
if (!comp_data)
|
|
continue;
|
|
|
|
icomp = e_cal_component_get_icalcomponent (comp_data->component);
|
|
if (!icomp || !icalcomponent_get_uid (icomp))
|
|
continue;
|
|
|
|
uid = icalcomponent_get_uid (icomp);
|
|
|
|
if (!g_hash_table_contains (gathered_uids, uid)) {
|
|
GatherComponentsData gather_data;
|
|
|
|
gather_data.uid = uid;
|
|
gather_data.pcomponent_ids = NULL;
|
|
gather_data.component_ids_hash = known_instances;
|
|
gather_data.copy_ids = TRUE;
|
|
gather_data.all_instances = FALSE;
|
|
|
|
g_hash_table_foreach (view_data->components,
|
|
cal_data_model_gather_components, &gather_data);
|
|
|
|
g_hash_table_insert (gathered_uids, g_strdup (uid), GINT_TO_POINTER (1));
|
|
}
|
|
|
|
/* Steal the comp_data */
|
|
link->data = NULL;
|
|
|
|
cal_data_model_process_added_component (data_model, view_data, comp_data, known_instances);
|
|
}
|
|
|
|
if (view_data->is_used && g_hash_table_size (known_instances) > 0) {
|
|
cal_data_model_remove_components (data_model, view_data->client, known_instances, view_data->components);
|
|
g_hash_table_remove_all (known_instances);
|
|
}
|
|
|
|
if (g_atomic_int_dec_and_test (&view_data->pending_expand_recurrences) &&
|
|
view_data->is_used && view_data->lost_components && view_data->received_complete) {
|
|
cal_data_model_remove_components (data_model, view_data->client, view_data->lost_components, NULL);
|
|
g_hash_table_destroy (view_data->lost_components);
|
|
view_data->lost_components = NULL;
|
|
}
|
|
|
|
g_hash_table_destroy (gathered_uids);
|
|
g_hash_table_destroy (known_instances);
|
|
|
|
view_data_unlock (view_data);
|
|
|
|
cal_data_model_thaw_all_subscribers (data_model);
|
|
|
|
view_data_unref (view_data);
|
|
|
|
g_slist_free_full (expanded_recurrences, component_data_free);
|
|
}
|
|
|
|
g_clear_object (¬if_data->client);
|
|
g_clear_object (¬if_data->data_model);
|
|
g_free (notif_data);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
ECalClient *client;
|
|
icaltimezone *zone;
|
|
GSList **pexpanded_recurrences;
|
|
} GenerateInstancesData;
|
|
|
|
static gboolean
|
|
cal_data_model_instance_generated (ECalComponent *comp,
|
|
time_t instance_start,
|
|
time_t instance_end,
|
|
gpointer data)
|
|
{
|
|
GenerateInstancesData *gid = data;
|
|
ComponentData *comp_data;
|
|
|
|
g_return_val_if_fail (gid != NULL, FALSE);
|
|
|
|
cal_comp_get_instance_times (gid->client, e_cal_component_get_icalcomponent (comp),
|
|
gid->zone, &instance_start, NULL, &instance_end, NULL, NULL);
|
|
|
|
if (instance_end > instance_start)
|
|
instance_end--;
|
|
|
|
comp_data = component_data_new (comp, instance_start, instance_end, FALSE);
|
|
*gid->pexpanded_recurrences = g_slist_prepend (*gid->pexpanded_recurrences, comp_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
cal_data_model_expand_recurrences_thread (ECalDataModel *data_model,
|
|
gpointer user_data)
|
|
{
|
|
ECalClient *client = user_data;
|
|
GSList *to_expand_recurrences, *link;
|
|
GSList *expanded_recurrences = NULL;
|
|
time_t range_start, range_end;
|
|
ViewData *view_data;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
view_data = g_hash_table_lookup (data_model->priv->views, client);
|
|
if (view_data)
|
|
view_data_ref (view_data);
|
|
|
|
range_start = data_model->priv->range_start;
|
|
range_end = data_model->priv->range_end;
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
if (!view_data) {
|
|
g_object_unref (client);
|
|
return;
|
|
}
|
|
|
|
view_data_lock (view_data);
|
|
|
|
if (!view_data->is_used) {
|
|
view_data_unlock (view_data);
|
|
view_data_unref (view_data);
|
|
g_object_unref (client);
|
|
return;
|
|
}
|
|
|
|
to_expand_recurrences = view_data->to_expand_recurrences;
|
|
view_data->to_expand_recurrences = NULL;
|
|
|
|
view_data_unlock (view_data);
|
|
|
|
for (link = to_expand_recurrences; link && view_data->is_used; link = g_slist_next (link)) {
|
|
icalcomponent *icomp = link->data;
|
|
GenerateInstancesData gid;
|
|
|
|
if (!icomp)
|
|
continue;
|
|
|
|
gid.client = client;
|
|
gid.pexpanded_recurrences = &expanded_recurrences;
|
|
gid.zone = data_model->priv->zone;
|
|
|
|
e_cal_client_generate_instances_for_object_sync (client, icomp, range_start, range_end,
|
|
cal_data_model_instance_generated, &gid);
|
|
}
|
|
|
|
g_slist_free_full (to_expand_recurrences, (GDestroyNotify) icalcomponent_free);
|
|
|
|
view_data_lock (view_data);
|
|
if (expanded_recurrences)
|
|
view_data->expanded_recurrences = g_slist_concat (view_data->expanded_recurrences, expanded_recurrences);
|
|
if (view_data->is_used) {
|
|
NotifyRecurrencesData *notif_data;
|
|
|
|
notif_data = g_new0 (NotifyRecurrencesData, 1);
|
|
notif_data->data_model = g_object_ref (data_model);
|
|
notif_data->client = g_object_ref (client);
|
|
|
|
g_timeout_add (1, cal_data_model_notify_recurrences_cb, notif_data);
|
|
}
|
|
|
|
view_data_unlock (view_data);
|
|
view_data_unref (view_data);
|
|
g_object_unref (client);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_process_modified_or_added_objects (ECalClientView *view,
|
|
const GSList *objects,
|
|
ECalDataModel *data_model,
|
|
gboolean is_add)
|
|
{
|
|
ViewData *view_data;
|
|
ECalClient *client;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
client = e_cal_client_view_ref_client (view);
|
|
view_data = g_hash_table_lookup (data_model->priv->views, client);
|
|
if (view_data) {
|
|
view_data_ref (view_data);
|
|
g_warn_if_fail (view_data->view == view);
|
|
}
|
|
g_object_unref (client);
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
if (!view_data)
|
|
return;
|
|
|
|
view_data_lock (view_data);
|
|
|
|
if (view_data->is_used) {
|
|
const GSList *link;
|
|
GSList *to_expand_recurrences = NULL;
|
|
|
|
if (!is_add) {
|
|
/* Received a modify before the view was claimed as being complete,
|
|
aka fully populated, thus drop any previously known components,
|
|
because there is no hope for a merge. */
|
|
if (view_data->lost_components) {
|
|
cal_data_model_remove_components (data_model, client, view_data->lost_components, NULL);
|
|
g_hash_table_destroy (view_data->lost_components);
|
|
view_data->lost_components = NULL;
|
|
}
|
|
}
|
|
|
|
cal_data_model_freeze_all_subscribers (data_model);
|
|
|
|
for (link = objects; link; link = g_slist_next (link)) {
|
|
icalcomponent *icomp = link->data;
|
|
|
|
if (!icomp || !icalcomponent_get_uid (icomp))
|
|
continue;
|
|
|
|
if (data_model->priv->expand_recurrences &&
|
|
!e_cal_util_component_is_instance (icomp) &&
|
|
e_cal_util_component_has_recurrences (icomp)) {
|
|
/* This component requires an expand of recurrences, which
|
|
will be done in a dedicated thread, thus remember it */
|
|
to_expand_recurrences = g_slist_prepend (to_expand_recurrences,
|
|
icalcomponent_new_clone (icomp));
|
|
} else {
|
|
/* Single or detached instance, the simple case */
|
|
ECalComponent *comp;
|
|
ComponentData *comp_data;
|
|
time_t instance_start, instance_end;
|
|
|
|
comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (icomp));
|
|
if (!comp)
|
|
continue;
|
|
|
|
cal_comp_get_instance_times (client, icomp, data_model->priv->zone, &instance_start, NULL, &instance_end, NULL, NULL);
|
|
|
|
if (instance_end > instance_start)
|
|
instance_end--;
|
|
|
|
comp_data = component_data_new (comp, instance_start, instance_end,
|
|
e_cal_util_component_is_instance (icomp));
|
|
|
|
cal_data_model_process_added_component (data_model, view_data, comp_data, NULL);
|
|
|
|
g_object_unref (comp);
|
|
}
|
|
}
|
|
|
|
cal_data_model_thaw_all_subscribers (data_model);
|
|
|
|
if (to_expand_recurrences) {
|
|
ECalClient *client = e_cal_client_view_ref_client (view);
|
|
|
|
view_data_lock (view_data);
|
|
view_data->to_expand_recurrences = g_slist_concat (
|
|
view_data->to_expand_recurrences, to_expand_recurrences);
|
|
g_atomic_int_inc (&view_data->pending_expand_recurrences);
|
|
view_data_unlock (view_data);
|
|
|
|
cal_data_model_submit_internal_thread_job (data_model,
|
|
cal_data_model_expand_recurrences_thread, client);
|
|
}
|
|
}
|
|
|
|
view_data_unlock (view_data);
|
|
view_data_unref (view_data);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_view_objects_added (ECalClientView *view,
|
|
const GSList *objects,
|
|
ECalDataModel *data_model)
|
|
{
|
|
cal_data_model_process_modified_or_added_objects (view, objects, data_model, TRUE);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_view_objects_modified (ECalClientView *view,
|
|
const GSList *objects,
|
|
ECalDataModel *data_model)
|
|
{
|
|
cal_data_model_process_modified_or_added_objects (view, objects, data_model, FALSE);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_view_objects_removed (ECalClientView *view,
|
|
const GSList *uids,
|
|
ECalDataModel *data_model)
|
|
{
|
|
ViewData *view_data;
|
|
const GSList *link;
|
|
ECalClient *client;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
client = e_cal_client_view_ref_client (view);
|
|
view_data =
|
|
g_hash_table_lookup (data_model->priv->views, client);
|
|
|
|
if (view_data) {
|
|
view_data_ref (view_data);
|
|
g_warn_if_fail (view_data->view == view);
|
|
}
|
|
g_object_unref (client);
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
if (!view_data)
|
|
return;
|
|
|
|
view_data_lock (view_data);
|
|
if (view_data->is_used) {
|
|
GHashTable *gathered_uids;
|
|
GList *removed = NULL, *rlink;
|
|
|
|
gathered_uids = g_hash_table_new (g_str_hash, g_str_equal);
|
|
|
|
for (link = uids; link; link = g_slist_next (link)) {
|
|
const ECalComponentId *id = link->data;
|
|
|
|
if (id) {
|
|
if (!id->rid || !*id->rid) {
|
|
if (!g_hash_table_contains (gathered_uids, id->uid)) {
|
|
GatherComponentsData gather_data;
|
|
|
|
gather_data.uid = id->uid;
|
|
gather_data.pcomponent_ids = &removed;
|
|
gather_data.component_ids_hash = NULL;
|
|
gather_data.copy_ids = TRUE;
|
|
gather_data.all_instances = TRUE;
|
|
|
|
g_hash_table_foreach (view_data->components,
|
|
cal_data_model_gather_components, &gather_data);
|
|
if (view_data->lost_components)
|
|
g_hash_table_foreach (view_data->lost_components,
|
|
cal_data_model_gather_components, &gather_data);
|
|
|
|
g_hash_table_insert (gathered_uids, id->uid, GINT_TO_POINTER (1));
|
|
}
|
|
} else {
|
|
removed = g_list_prepend (removed, e_cal_component_id_copy (id));
|
|
}
|
|
}
|
|
}
|
|
|
|
cal_data_model_freeze_all_subscribers (data_model);
|
|
|
|
for (rlink = removed; rlink; rlink = g_list_next (rlink)) {
|
|
ECalComponentId *id = rlink->data;
|
|
|
|
if (id) {
|
|
ComponentData *comp_data;
|
|
time_t instance_start = (time_t) 0, instance_end = (time_t) 0;
|
|
|
|
/* Try to limit which subscribers will be notified about removal */
|
|
comp_data = g_hash_table_lookup (view_data->components, id);
|
|
if (comp_data) {
|
|
instance_start = comp_data->instance_start;
|
|
instance_end = comp_data->instance_end;
|
|
} else if (view_data->lost_components) {
|
|
comp_data = g_hash_table_lookup (view_data->lost_components, id);
|
|
if (comp_data) {
|
|
instance_start = comp_data->instance_start;
|
|
instance_end = comp_data->instance_end;
|
|
}
|
|
}
|
|
|
|
g_hash_table_remove (view_data->components, id);
|
|
if (view_data->lost_components)
|
|
g_hash_table_remove (view_data->lost_components, id);
|
|
|
|
cal_data_model_foreach_subscriber_in_range (data_model, view_data->client,
|
|
instance_start, instance_end,
|
|
cal_data_model_remove_one_view_component_cb, id);
|
|
}
|
|
}
|
|
|
|
cal_data_model_thaw_all_subscribers (data_model);
|
|
|
|
g_list_free_full (removed, (GDestroyNotify) e_cal_component_free_id);
|
|
g_hash_table_destroy (gathered_uids);
|
|
}
|
|
view_data_unlock (view_data);
|
|
view_data_unref (view_data);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_view_progress (ECalClientView *view,
|
|
guint percent,
|
|
const gchar *message,
|
|
ECalDataModel *data_model)
|
|
{
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_PROGRESS, percent, message, NULL);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_view_complete (ECalClientView *view,
|
|
const GError *error,
|
|
ECalDataModel *data_model)
|
|
{
|
|
ViewData *view_data;
|
|
ECalClient *client;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
client = e_cal_client_view_ref_client (view);
|
|
view_data =
|
|
g_hash_table_lookup (data_model->priv->views, client);
|
|
if (view_data) {
|
|
view_data_ref (view_data);
|
|
g_warn_if_fail (view_data->view == view);
|
|
}
|
|
g_object_unref (client);
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
if (!view_data)
|
|
return;
|
|
|
|
view_data_lock (view_data);
|
|
|
|
view_data->received_complete = TRUE;
|
|
if (view_data->is_used &&
|
|
view_data->lost_components &&
|
|
!view_data->pending_expand_recurrences) {
|
|
cal_data_model_remove_components (data_model, view_data->client, view_data->lost_components, NULL);
|
|
g_hash_table_destroy (view_data->lost_components);
|
|
view_data->lost_components = NULL;
|
|
}
|
|
|
|
view_data_unlock (view_data);
|
|
view_data_unref (view_data);
|
|
}
|
|
|
|
typedef struct _CreateViewData {
|
|
ECalDataModel *data_model;
|
|
ECalClient *client;
|
|
} CreateViewData;
|
|
|
|
static void
|
|
create_view_data_free (gpointer ptr)
|
|
{
|
|
CreateViewData *cv_data = ptr;
|
|
|
|
if (cv_data) {
|
|
g_clear_object (&cv_data->data_model);
|
|
g_clear_object (&cv_data->client);
|
|
g_free (cv_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
cal_data_model_create_view_thread (gpointer user_data,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
CreateViewData *cv_data = user_data;
|
|
ViewData *view_data;
|
|
ECalDataModel *data_model;
|
|
ECalClient *client;
|
|
ECalClientView *view;
|
|
gchar *filter;
|
|
|
|
g_return_if_fail (cv_data != NULL);
|
|
|
|
data_model = cv_data->data_model;
|
|
client = cv_data->client;
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (E_IS_CAL_CLIENT (client));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}
|
|
|
|
view_data = g_hash_table_lookup (data_model->priv->views, client);
|
|
if (!view_data) {
|
|
UNLOCK_PROPS ();
|
|
g_warn_if_reached ();
|
|
return;
|
|
}
|
|
|
|
filter = g_strdup (data_model->priv->full_filter);
|
|
|
|
view_data_ref (view_data);
|
|
UNLOCK_PROPS ();
|
|
|
|
view_data_lock (view_data);
|
|
g_warn_if_fail (view_data->view == NULL);
|
|
|
|
if (!e_cal_client_get_view_sync (client, filter, &view_data->view, cancellable, error)) {
|
|
view_data_unlock (view_data);
|
|
view_data_unref (view_data);
|
|
g_free (filter);
|
|
return;
|
|
}
|
|
|
|
g_warn_if_fail (view_data->view != NULL);
|
|
|
|
view_data->objects_added_id = g_signal_connect (view_data->view, "objects-added",
|
|
G_CALLBACK (cal_data_model_view_objects_added), data_model);
|
|
view_data->objects_modified_id = g_signal_connect (view_data->view, "objects-modified",
|
|
G_CALLBACK (cal_data_model_view_objects_modified), data_model);
|
|
view_data->objects_removed_id = g_signal_connect (view_data->view, "objects-removed",
|
|
G_CALLBACK (cal_data_model_view_objects_removed), data_model);
|
|
view_data->progress_id = g_signal_connect (view_data->view, "progress",
|
|
G_CALLBACK (cal_data_model_view_progress), data_model);
|
|
view_data->complete_id = g_signal_connect (view_data->view, "complete",
|
|
G_CALLBACK (cal_data_model_view_complete), data_model);
|
|
|
|
view = g_object_ref (view_data->view);
|
|
|
|
view_data_unlock (view_data);
|
|
view_data_unref (view_data);
|
|
|
|
g_free (filter);
|
|
|
|
if (!g_cancellable_is_cancelled (cancellable)) {
|
|
cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_START, 0, NULL, NULL);
|
|
e_cal_client_view_start (view, error);
|
|
}
|
|
|
|
g_clear_object (&view);
|
|
}
|
|
|
|
typedef struct _NotifyRemoveComponentsData {
|
|
ECalDataModel *data_model;
|
|
ECalClient *client;
|
|
} NotifyRemoveComponentsData;
|
|
|
|
static void
|
|
cal_data_model_notify_remove_components_cb (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
ECalComponentId *id = key;
|
|
ComponentData *comp_data = value;
|
|
NotifyRemoveComponentsData *nrc_data = user_data;
|
|
|
|
g_return_if_fail (id != NULL);
|
|
g_return_if_fail (comp_data != NULL);
|
|
g_return_if_fail (nrc_data != NULL);
|
|
|
|
cal_data_model_foreach_subscriber_in_range (nrc_data->data_model, nrc_data->client,
|
|
comp_data->instance_start, comp_data->instance_end,
|
|
cal_data_model_remove_one_view_component_cb, id);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_update_client_view (ECalDataModel *data_model,
|
|
ECalClient *client)
|
|
{
|
|
/*ESource *source;*/
|
|
ViewData *view_data;
|
|
CreateViewData *cv_data;
|
|
/*const gchar *alert_ident;
|
|
gchar *description;*/
|
|
|
|
LOCK_PROPS ();
|
|
|
|
view_data = g_hash_table_lookup (data_model->priv->views, client);
|
|
if (!view_data) {
|
|
view_data = view_data_new (client);
|
|
g_hash_table_insert (data_model->priv->views, client, view_data);
|
|
}
|
|
|
|
view_data_lock (view_data);
|
|
|
|
if (view_data->cancellable)
|
|
g_cancellable_cancel (view_data->cancellable);
|
|
g_clear_object (&view_data->cancellable);
|
|
|
|
if (view_data->view) {
|
|
view_data_disconnect_view (view_data);
|
|
cal_data_model_emit_view_state_changed (data_model, view_data->view, E_CAL_DATA_MODEL_VIEW_STATE_STOP, 0, NULL, NULL);
|
|
g_clear_object (&view_data->view);
|
|
}
|
|
|
|
if (!view_data->received_complete) {
|
|
NotifyRemoveComponentsData nrc_data;
|
|
|
|
nrc_data.data_model = data_model;
|
|
nrc_data.client = client;
|
|
|
|
cal_data_model_freeze_all_subscribers (data_model);
|
|
|
|
g_hash_table_foreach (view_data->components,
|
|
cal_data_model_notify_remove_components_cb, &nrc_data);
|
|
|
|
g_hash_table_remove_all (view_data->components);
|
|
if (view_data->lost_components) {
|
|
g_hash_table_foreach (view_data->lost_components,
|
|
cal_data_model_notify_remove_components_cb, &nrc_data);
|
|
|
|
g_hash_table_destroy (view_data->lost_components);
|
|
view_data->lost_components = NULL;
|
|
}
|
|
|
|
cal_data_model_thaw_all_subscribers (data_model);
|
|
} else {
|
|
if (view_data->lost_components) {
|
|
NotifyRemoveComponentsData nrc_data;
|
|
|
|
nrc_data.data_model = data_model;
|
|
nrc_data.client = client;
|
|
|
|
cal_data_model_freeze_all_subscribers (data_model);
|
|
g_hash_table_foreach (view_data->lost_components,
|
|
cal_data_model_notify_remove_components_cb, &nrc_data);
|
|
cal_data_model_thaw_all_subscribers (data_model);
|
|
|
|
g_hash_table_destroy (view_data->lost_components);
|
|
view_data->lost_components = NULL;
|
|
}
|
|
|
|
view_data->lost_components = view_data->components;
|
|
view_data->components = g_hash_table_new_full (
|
|
(GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal,
|
|
(GDestroyNotify) e_cal_component_free_id, component_data_free);
|
|
}
|
|
|
|
view_data_unlock (view_data);
|
|
|
|
if (!data_model->priv->full_filter) {
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}
|
|
|
|
/*source = e_client_get_source (E_CLIENT (client));
|
|
|
|
switch (e_cal_client_get_source_type (client)) {
|
|
case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
|
|
alert_ident = "calendar:failed-create-view-calendar";
|
|
description = g_strdup_printf (_("Creating view for calendar '%s'"), e_source_get_display_name (source));
|
|
break;
|
|
case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
|
|
alert_ident = "calendar:failed-create-view-tasks";
|
|
description = g_strdup_printf (_("Creating view for task list '%s'"), e_source_get_display_name (source));
|
|
break;
|
|
case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
|
|
alert_ident = "calendar:failed-create-view-memos";
|
|
description = g_strdup_printf (_("Creating view for memo list '%s'"), e_source_get_display_name (source));
|
|
break;
|
|
case E_CAL_CLIENT_SOURCE_TYPE_LAST:
|
|
g_warn_if_reached ();
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}*/
|
|
|
|
cv_data = g_new0 (CreateViewData, 1);
|
|
cv_data->data_model = g_object_ref (data_model);
|
|
cv_data->client = g_object_ref (client);
|
|
|
|
view_data->received_complete = FALSE;
|
|
view_data->cancellable = e_cal_data_model_submit_thread_job (data_model,
|
|
cal_data_model_create_view_thread, cv_data, create_view_data_free);
|
|
|
|
/*g_free (description);*/
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
static void
|
|
cal_data_model_remove_client_view (ECalDataModel *data_model,
|
|
ECalClient *client)
|
|
{
|
|
ViewData *view_data;
|
|
|
|
LOCK_PROPS ();
|
|
|
|
view_data = g_hash_table_lookup (data_model->priv->views, client);
|
|
|
|
if (view_data) {
|
|
NotifyRemoveComponentsData nrc_data;
|
|
|
|
view_data_lock (view_data);
|
|
|
|
nrc_data.data_model = data_model;
|
|
nrc_data.client = client;
|
|
|
|
cal_data_model_freeze_all_subscribers (data_model);
|
|
|
|
g_hash_table_foreach (view_data->components,
|
|
cal_data_model_notify_remove_components_cb, &nrc_data);
|
|
g_hash_table_remove_all (view_data->components);
|
|
|
|
cal_data_model_thaw_all_subscribers (data_model);
|
|
|
|
if (view_data->view)
|
|
cal_data_model_emit_view_state_changed (data_model, view_data->view, E_CAL_DATA_MODEL_VIEW_STATE_STOP, 0, NULL, NULL);
|
|
|
|
view_data->is_used = FALSE;
|
|
view_data_unlock (view_data);
|
|
|
|
g_hash_table_remove (data_model->priv->views, client);
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
static gboolean
|
|
cal_data_model_add_to_subscriber (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
const ECalComponentId *id,
|
|
ECalComponent *component,
|
|
time_t instance_start,
|
|
time_t instance_end,
|
|
gpointer user_data)
|
|
{
|
|
ECalDataModelSubscriber *subscriber = user_data;
|
|
|
|
g_return_val_if_fail (subscriber != NULL, FALSE);
|
|
g_return_val_if_fail (id != NULL, FALSE);
|
|
|
|
e_cal_data_model_subscriber_component_added (subscriber, client, component);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
cal_data_model_add_to_subscriber_except_its_range (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
const ECalComponentId *id,
|
|
ECalComponent *component,
|
|
time_t instance_start,
|
|
time_t instance_end,
|
|
gpointer user_data)
|
|
{
|
|
SubscriberData *subs_data = user_data;
|
|
|
|
g_return_val_if_fail (subs_data != NULL, FALSE);
|
|
g_return_val_if_fail (id != NULL, FALSE);
|
|
|
|
/* subs_data should have set the old time range, which
|
|
means only components which didn't fit into the old
|
|
time range will be added */
|
|
if (!(instance_start <= subs_data->range_end &&
|
|
instance_end >= subs_data->range_start))
|
|
e_cal_data_model_subscriber_component_added (subs_data->subscriber, client, component);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
cal_data_model_remove_from_subscriber_except_its_range (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
const ECalComponentId *id,
|
|
ECalComponent *component,
|
|
time_t instance_start,
|
|
time_t instance_end,
|
|
gpointer user_data)
|
|
{
|
|
SubscriberData *subs_data = user_data;
|
|
|
|
g_return_val_if_fail (subs_data != NULL, FALSE);
|
|
g_return_val_if_fail (id != NULL, FALSE);
|
|
|
|
/* subs_data should have set the new time range, which
|
|
means only components which don't fit into this new
|
|
time range will be removed */
|
|
if (!(instance_start <= subs_data->range_end &&
|
|
instance_end >= subs_data->range_start))
|
|
e_cal_data_model_subscriber_component_removed (subs_data->subscriber, client, id->uid, id->rid);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
cal_data_model_set_client_default_zone_cb (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
ECalClient *client = value;
|
|
icaltimezone *zone = user_data;
|
|
|
|
g_return_if_fail (E_IS_CAL_CLIENT (client));
|
|
g_return_if_fail (zone != NULL);
|
|
|
|
e_cal_client_set_default_timezone (client, zone);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_rebuild_everything (ECalDataModel *data_model,
|
|
gboolean complete_rebuild)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if (data_model->priv->views_update_freeze > 0) {
|
|
data_model->priv->views_update_required = TRUE;
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}
|
|
|
|
data_model->priv->views_update_required = FALSE;
|
|
|
|
g_hash_table_iter_init (&iter, data_model->priv->clients);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
ECalClient *client = value;
|
|
|
|
if (complete_rebuild)
|
|
cal_data_model_remove_client_view (data_model, client);
|
|
cal_data_model_update_client_view (data_model, client);
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
static void
|
|
cal_data_model_update_time_range (ECalDataModel *data_model)
|
|
{
|
|
time_t range_start, range_end;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if (data_model->priv->disposing) {
|
|
UNLOCK_PROPS ();
|
|
|
|
return;
|
|
}
|
|
|
|
range_start = data_model->priv->range_start;
|
|
range_end = data_model->priv->range_end;
|
|
|
|
cal_data_model_calc_range (data_model, &range_start, &range_end);
|
|
|
|
if (data_model->priv->range_start != range_start ||
|
|
data_model->priv->range_end != range_end) {
|
|
data_model->priv->range_start = range_start;
|
|
data_model->priv->range_end = range_end;
|
|
|
|
if (cal_data_model_update_full_filter (data_model))
|
|
cal_data_model_rebuild_everything (data_model, FALSE);
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
static void
|
|
cal_data_model_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id) {
|
|
case PROP_EXPAND_RECURRENCES:
|
|
e_cal_data_model_set_expand_recurrences (
|
|
E_CAL_DATA_MODEL (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_TIMEZONE:
|
|
e_cal_data_model_set_timezone (
|
|
E_CAL_DATA_MODEL (object),
|
|
g_value_get_pointer (value));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id) {
|
|
case PROP_EXPAND_RECURRENCES:
|
|
g_value_set_boolean (
|
|
value,
|
|
e_cal_data_model_get_expand_recurrences (
|
|
E_CAL_DATA_MODEL (object)));
|
|
return;
|
|
|
|
case PROP_TIMEZONE:
|
|
g_value_set_pointer (
|
|
value,
|
|
e_cal_data_model_get_timezone (
|
|
E_CAL_DATA_MODEL (object)));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_dispose (GObject *object)
|
|
{
|
|
ECalDataModel *data_model = E_CAL_DATA_MODEL (object);
|
|
|
|
data_model->priv->disposing = TRUE;
|
|
|
|
/* Chain up to parent's method. */
|
|
G_OBJECT_CLASS (e_cal_data_model_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
cal_data_model_finalize (GObject *object)
|
|
{
|
|
ECalDataModel *data_model = E_CAL_DATA_MODEL (object);
|
|
|
|
g_thread_pool_free (data_model->priv->thread_pool, TRUE, FALSE);
|
|
g_hash_table_destroy (data_model->priv->clients);
|
|
g_hash_table_destroy (data_model->priv->views);
|
|
g_slist_free_full (data_model->priv->subscribers, subscriber_data_free);
|
|
g_free (data_model->priv->filter);
|
|
g_free (data_model->priv->full_filter);
|
|
|
|
g_rec_mutex_clear (&data_model->priv->props_lock);
|
|
|
|
/* Chain up to parent's method. */
|
|
G_OBJECT_CLASS (e_cal_data_model_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
e_cal_data_model_class_init (ECalDataModelClass *class)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
g_type_class_add_private (class, sizeof (ECalDataModelPrivate));
|
|
|
|
object_class = G_OBJECT_CLASS (class);
|
|
object_class->set_property = cal_data_model_set_property;
|
|
object_class->get_property = cal_data_model_get_property;
|
|
object_class->dispose = cal_data_model_dispose;
|
|
object_class->finalize = cal_data_model_finalize;
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_EXPAND_RECURRENCES,
|
|
g_param_spec_boolean (
|
|
"expand-recurrences",
|
|
"Expand Recurrences",
|
|
NULL,
|
|
FALSE,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_TIMEZONE,
|
|
g_param_spec_pointer (
|
|
"timezone",
|
|
"Time Zone",
|
|
NULL,
|
|
G_PARAM_READWRITE));
|
|
|
|
signals[VIEW_STATE_CHANGED] = g_signal_new (
|
|
"view-state-changed",
|
|
G_TYPE_FROM_CLASS (class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ECalDataModelClass, view_state_changed),
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_NONE, 5, E_TYPE_CAL_CLIENT_VIEW, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_ERROR);
|
|
}
|
|
|
|
static void
|
|
e_cal_data_model_init (ECalDataModel *data_model)
|
|
{
|
|
data_model->priv = G_TYPE_INSTANCE_GET_PRIVATE (data_model, E_TYPE_CAL_DATA_MODEL, ECalDataModelPrivate);
|
|
|
|
/* Suppose the data_model is always created in the main/UI thread */
|
|
data_model->priv->main_thread = g_thread_self ();
|
|
data_model->priv->thread_pool = g_thread_pool_new (
|
|
cal_data_model_internal_thread_job_func, data_model, 5, FALSE, NULL);
|
|
|
|
data_model->priv->clients = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
|
data_model->priv->views = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, view_data_unref);
|
|
data_model->priv->subscribers = NULL;
|
|
|
|
data_model->priv->disposing = FALSE;
|
|
data_model->priv->expand_recurrences = FALSE;
|
|
data_model->priv->zone = icaltimezone_get_utc_timezone ();
|
|
|
|
data_model->priv->views_update_freeze = 0;
|
|
data_model->priv->views_update_required = FALSE;
|
|
|
|
g_rec_mutex_init (&data_model->priv->props_lock);
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_new:
|
|
* @func: a function to be called when the data model needs to create
|
|
* a thread job within UI
|
|
*
|
|
* Creates a new instance of #ECalDataModel. The @func is mandatory, because
|
|
* it is used to create new thread jobs with UI feedback.
|
|
*
|
|
* Returns: (transfer full): A new #ECalDataModel instance
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
ECalDataModel *
|
|
e_cal_data_model_new (ECalDataModelSubmitThreadJobFunc func)
|
|
{
|
|
ECalDataModel *data_model;
|
|
|
|
g_return_val_if_fail (func != NULL, NULL);
|
|
|
|
data_model = g_object_new (E_TYPE_CAL_DATA_MODEL, NULL);
|
|
data_model->priv->submit_thread_job_func = func;
|
|
|
|
return data_model;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_new_clone:
|
|
* @src_data_model: an #ECalDataModel to clone
|
|
*
|
|
* Creates a clone of @src_data_model, which means a copy with the same clients, filter and
|
|
* other properties set (not the subscribers).
|
|
*
|
|
* Returns: (transfer full): A new #ECalDataModel instance deriving settings from @src_data_model
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
ECalDataModel *
|
|
e_cal_data_model_new_clone (ECalDataModel *src_data_model)
|
|
{
|
|
ECalDataModel *clone;
|
|
GList *clients, *link;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (src_data_model), NULL);
|
|
|
|
clone = e_cal_data_model_new (src_data_model->priv->submit_thread_job_func);
|
|
|
|
e_cal_data_model_set_expand_recurrences (clone, e_cal_data_model_get_expand_recurrences (src_data_model));
|
|
e_cal_data_model_set_timezone (clone, e_cal_data_model_get_timezone (src_data_model));
|
|
e_cal_data_model_set_filter (clone, src_data_model->priv->filter);
|
|
|
|
clients = e_cal_data_model_get_clients (src_data_model);
|
|
for (link = clients; link; link = g_list_next (link)) {
|
|
ECalClient *client = link->data;
|
|
|
|
e_cal_data_model_add_client (clone, client);
|
|
}
|
|
|
|
g_list_free_full (clients, g_object_unref);
|
|
|
|
return clone;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_get_disposing:
|
|
* @data_model: an #EDataModel instance
|
|
*
|
|
* Obtains whether the @data_model is disposing and will be freed (soon).
|
|
*
|
|
* Returns: Whether the @data_model is disposing.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
gboolean
|
|
e_cal_data_model_get_disposing (ECalDataModel *data_model)
|
|
{
|
|
gboolean disposing;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
disposing = data_model->priv->disposing;
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return disposing;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_set_disposing:
|
|
* @data_model: an #EDataModel instance
|
|
* @disposing: whether the object is disposing
|
|
*
|
|
* Sets whether the @data_model is disposing itself (soon).
|
|
* If set to %TRUE, then no updates are done on changes
|
|
* which would otherwise trigger view and subscriber updates.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_set_disposing (ECalDataModel *data_model,
|
|
gboolean disposing)
|
|
{
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if ((data_model->priv->disposing ? 1 : 0) == (disposing ? 1 : 0)) {
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}
|
|
|
|
data_model->priv->disposing = disposing;
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_get_expand_recurrences:
|
|
* @data_model: an #EDataModel instance
|
|
*
|
|
* Obtains whether the @data_model expands recurrences of recurring
|
|
* components by default. The default value is #FALSE, to not expand
|
|
* recurrences.
|
|
*
|
|
* Returns: Whether the @data_model expands recurrences of recurring
|
|
* components.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
gboolean
|
|
e_cal_data_model_get_expand_recurrences (ECalDataModel *data_model)
|
|
{
|
|
gboolean expand_recurrences;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
expand_recurrences = data_model->priv->expand_recurrences;
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return expand_recurrences;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_set_expand_recurrences:
|
|
* @data_model: an #EDataModel instance
|
|
* @expand_recurrences: whether to expand recurrences
|
|
*
|
|
* Sets whether the @data_model should expand recurrences of recurring
|
|
* components by default.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_set_expand_recurrences (ECalDataModel *data_model,
|
|
gboolean expand_recurrences)
|
|
{
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if ((data_model->priv->expand_recurrences ? 1 : 0) == (expand_recurrences ? 1 : 0)) {
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}
|
|
|
|
data_model->priv->expand_recurrences = expand_recurrences;
|
|
|
|
cal_data_model_rebuild_everything (data_model, TRUE);
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_get_timezone:
|
|
* @data_model: an #EDataModel instance
|
|
*
|
|
* Obtains a timezone being used for calendar views. The returned
|
|
* timezone is owned by the @data_model.
|
|
*
|
|
* Returns: (transfer none): An #icaltimezone being used for calendar views.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
icaltimezone *
|
|
e_cal_data_model_get_timezone (ECalDataModel *data_model)
|
|
{
|
|
icaltimezone *zone;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
zone = data_model->priv->zone;
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return zone;
|
|
}
|
|
/**
|
|
* e_cal_data_model_set_timezone:
|
|
* @data_model: an #EDataModel instance
|
|
* @zone: an #icaltimezone
|
|
*
|
|
* Sets a trimezone to be used for calendar views. This change
|
|
* regenerates all views.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_set_timezone (ECalDataModel *data_model,
|
|
icaltimezone *zone)
|
|
{
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (zone != NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if (data_model->priv->zone != zone) {
|
|
data_model->priv->zone = zone;
|
|
|
|
g_hash_table_foreach (data_model->priv->clients, cal_data_model_set_client_default_zone_cb, zone);
|
|
|
|
if (cal_data_model_update_full_filter (data_model))
|
|
cal_data_model_rebuild_everything (data_model, TRUE);
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_set_filter:
|
|
* @data_model: an #EDataModel instance
|
|
* @sexp: an expression defining a filter
|
|
*
|
|
* Sets an additional filter for the views. The filter should not
|
|
* contain time constraints, these are meant to be defined by
|
|
* subscribers.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_set_filter (ECalDataModel *data_model,
|
|
const gchar *sexp)
|
|
{
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (sexp != NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if (sexp && !*sexp)
|
|
sexp = NULL;
|
|
|
|
if (g_strcmp0 (data_model->priv->filter, sexp) != 0) {
|
|
g_free (data_model->priv->filter);
|
|
data_model->priv->filter = g_strdup (sexp);
|
|
|
|
if (cal_data_model_update_full_filter (data_model))
|
|
cal_data_model_rebuild_everything (data_model, TRUE);
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_dup_filter:
|
|
* @data_model: an #EDataModel instance
|
|
*
|
|
* Obtains currently used filter (an expression) for the views.
|
|
*
|
|
* Returns: (transfer full): A copy of the currently used
|
|
* filter for views. Free it with g_free() when done with it.
|
|
* Returns #NULL when there is no extra filter set.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
gchar *
|
|
e_cal_data_model_dup_filter (ECalDataModel *data_model)
|
|
{
|
|
gchar *filter;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
filter = g_strdup (data_model->priv->filter);
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return filter;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_add_client:
|
|
* @data_model: an #EDataModel instance
|
|
* @client: an #ECalClient
|
|
*
|
|
* Adds a new @client into the set of clients which should be used
|
|
* to populate data for subscribers. Adding the same client multiple
|
|
* times does nothing.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_add_client (ECalDataModel *data_model,
|
|
ECalClient *client)
|
|
{
|
|
ESource *source;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (E_IS_CAL_CLIENT (client));
|
|
|
|
source = e_client_get_source (E_CLIENT (client));
|
|
g_return_if_fail (E_IS_SOURCE (source));
|
|
g_return_if_fail (e_source_get_uid (source) != NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if (g_hash_table_contains (data_model->priv->clients, e_source_get_uid (source))) {
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}
|
|
|
|
g_hash_table_insert (data_model->priv->clients, e_source_dup_uid (source), g_object_ref (client));
|
|
|
|
e_cal_client_set_default_timezone (client, data_model->priv->zone);
|
|
|
|
cal_data_model_update_client_view (data_model, client);
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_remove_client:
|
|
* @uid: a UID of a client to remove
|
|
*
|
|
* Removes a client identified by @uid from a set of clients
|
|
* which populate the data for subscribers. Removing the client
|
|
* which is not used in the @data_model does nothing.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_remove_client (ECalDataModel *data_model,
|
|
const gchar *uid)
|
|
{
|
|
ECalClient *client;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (uid != NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
client = g_hash_table_lookup (data_model->priv->clients, uid);
|
|
if (!client) {
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}
|
|
|
|
cal_data_model_remove_client_view (data_model, client);
|
|
g_hash_table_remove (data_model->priv->clients, uid);
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_ref_client:
|
|
* @data_model: an #EDataModel instance
|
|
* @uid: a UID of a client to return
|
|
*
|
|
* Obtains an #ECalClient with given @uid from the set of clients
|
|
* being used by the @data_modal. Returns #NULL, if no such client
|
|
* is used by the @data_model.
|
|
*
|
|
* Returns: (tranfer full): An #ECalClient with given @uid being
|
|
* used by @data_model, or NULL, when no such is used by
|
|
* the @data_model. Unref returned (non-NULL) client with
|
|
* g_object_unref() when done with it.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
ECalClient *
|
|
e_cal_data_model_ref_client (ECalDataModel *data_model,
|
|
const gchar *uid)
|
|
{
|
|
ECalClient *client;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
client = g_hash_table_lookup (data_model->priv->clients, uid);
|
|
if (client)
|
|
g_object_ref (client);
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return client;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_get_clients:
|
|
* @data_model: an #EDataModel instance
|
|
*
|
|
* Obtains a list of all clients being used by the @data_model.
|
|
* Each client in the returned list is referenced and the list
|
|
* itself is also newly allocated, thus free it with
|
|
* g_list_free_full (list, g_object_unref); when done with it.
|
|
*
|
|
* Returns: (transfer full): A list of currently used #ECalClient-s.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
GList *
|
|
e_cal_data_model_get_clients (ECalDataModel *data_model)
|
|
{
|
|
GList *clients;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
clients = g_hash_table_get_values (data_model->priv->clients);
|
|
g_list_foreach (clients, (GFunc) g_object_ref, NULL);
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return clients;
|
|
}
|
|
|
|
static gboolean
|
|
cal_data_model_prepend_component (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
const ECalComponentId *id,
|
|
ECalComponent *comp,
|
|
time_t instance_start,
|
|
time_t instance_end,
|
|
gpointer user_data)
|
|
{
|
|
GSList **components = user_data;
|
|
|
|
g_return_val_if_fail (components != NULL, FALSE);
|
|
g_return_val_if_fail (comp != NULL, FALSE);
|
|
|
|
*components = g_slist_prepend (*components, g_object_ref (comp));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_get_components:
|
|
* @data_model: an #EDataModel instance
|
|
* @in_range_start: Start of the time range
|
|
* @in_range_end: End of the time range
|
|
*
|
|
* Obtains a list of components from the given time range. The time range is
|
|
* clamp by the actual time range defined by subscribers (if there is no
|
|
* subscriber, or all subscribers define times out of the given time range,
|
|
* then no components are returned).
|
|
*
|
|
* Returns: (transfer full): A #GSList of #ECalComponent-s known for the given
|
|
* time range in the time of the call. The #GSList, togher with the components,
|
|
* is owned by the caller, which should free it with
|
|
* g_slist_free_full (list, g_object_unref); when done with it.
|
|
*
|
|
* Note: A special case when both @in_range_start and @in_range_end are zero
|
|
* is treated as a request for all known components.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
GSList *
|
|
e_cal_data_model_get_components (ECalDataModel *data_model,
|
|
time_t in_range_start,
|
|
time_t in_range_end)
|
|
{
|
|
GSList *components = NULL;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
|
|
|
|
e_cal_data_model_foreach_component (data_model, in_range_start, in_range_end,
|
|
cal_data_model_prepend_component, &components);
|
|
|
|
return g_slist_reverse (components);
|
|
}
|
|
|
|
static gboolean
|
|
cal_data_model_foreach_component (ECalDataModel *data_model,
|
|
time_t in_range_start,
|
|
time_t in_range_end,
|
|
ECalDataModelForeachFunc func,
|
|
gpointer user_data,
|
|
gboolean include_lost_components)
|
|
{
|
|
GHashTableIter viter;
|
|
gpointer key, value;
|
|
gboolean checked_all = TRUE;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
|
|
g_return_val_if_fail (func != NULL, FALSE);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
/* Is the given time range in the currently used time range? */
|
|
if (!(in_range_start == in_range_end && in_range_start == (time_t) 0) &&
|
|
(in_range_start >= data_model->priv->range_end ||
|
|
in_range_end <= data_model->priv->range_start)) {
|
|
UNLOCK_PROPS ();
|
|
return checked_all;
|
|
}
|
|
|
|
g_hash_table_iter_init (&viter, data_model->priv->views);
|
|
while (checked_all && g_hash_table_iter_next (&viter, &key, &value)) {
|
|
ViewData *view_data = value;
|
|
GHashTableIter citer;
|
|
|
|
if (!view_data)
|
|
continue;
|
|
|
|
view_data_lock (view_data);
|
|
|
|
g_hash_table_iter_init (&citer, view_data->components);
|
|
while (checked_all && g_hash_table_iter_next (&citer, &key, &value)) {
|
|
ECalComponentId *id = key;
|
|
ComponentData *comp_data = value;
|
|
|
|
if (!comp_data)
|
|
continue;
|
|
|
|
if ((in_range_start == in_range_end && in_range_start == (time_t) 0) ||
|
|
(comp_data->instance_start < in_range_end &&
|
|
comp_data->instance_end > in_range_start)) {
|
|
if (!func (data_model, view_data->client, id, comp_data->component,
|
|
comp_data->instance_start, comp_data->instance_end, user_data))
|
|
checked_all = FALSE;
|
|
}
|
|
}
|
|
|
|
if (include_lost_components && view_data->lost_components) {
|
|
g_hash_table_iter_init (&citer, view_data->lost_components);
|
|
while (checked_all && g_hash_table_iter_next (&citer, &key, &value)) {
|
|
ECalComponentId *id = key;
|
|
ComponentData *comp_data = value;
|
|
|
|
if (!comp_data)
|
|
continue;
|
|
|
|
if ((in_range_start == in_range_end && in_range_start == (time_t) 0) ||
|
|
(comp_data->instance_start < in_range_end &&
|
|
comp_data->instance_end > in_range_start)) {
|
|
if (!func (data_model, view_data->client, id, comp_data->component,
|
|
comp_data->instance_start, comp_data->instance_end, user_data))
|
|
checked_all = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
view_data_unlock (view_data);
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return checked_all;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_foreach_component:
|
|
* @data_model: an #EDataModel instance
|
|
* @in_range_start: Start of the time range
|
|
* @in_range_end: End of the time range
|
|
* @func: a function to be called for each component in the given time range
|
|
* @user_data: user data being passed into the @func
|
|
*
|
|
* Calls @func for each component in the given time range. The time range is
|
|
* clamp by the actual time range defined by subscribers (if there is no
|
|
* subscriber, or all subscribers define times out of the given time range,
|
|
* then the function is not called at all and a #FALSE is returned).
|
|
*
|
|
* The @func returns #TRUE to continue the traversal. If it wants to stop
|
|
* the traversal earlier, then it returns #FALSE.
|
|
*
|
|
* Returns: Whether all the components were checked. The returned value is
|
|
* usually #TRUE, unless the @func stops traversal earlier.
|
|
*
|
|
* Note: A special case when both @in_range_start and @in_range_end are zero
|
|
* is treated as a request for all known components.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
gboolean
|
|
e_cal_data_model_foreach_component (ECalDataModel *data_model,
|
|
time_t in_range_start,
|
|
time_t in_range_end,
|
|
ECalDataModelForeachFunc func,
|
|
gpointer user_data)
|
|
{
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
|
|
g_return_val_if_fail (func != NULL, FALSE);
|
|
|
|
return cal_data_model_foreach_component (data_model, in_range_start, in_range_end, func, user_data, FALSE);
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_subscribe:
|
|
* @data_model: an #EDataModel instance
|
|
* @subscriber: an #ECalDataModelSubscriber instance
|
|
* @range_start: Start of the time range used by the @subscriber
|
|
* @range_end: End of the time range used by the @subscriber
|
|
*
|
|
* Either adds a new @subscriber to the set of subscribers for this
|
|
* @data_model, or changes a time range used by the @subscriber,
|
|
* in case it was added to the @data_model earlier.
|
|
*
|
|
* Reference count of the @subscriber is increased by one, in case
|
|
* it is newly added. The reference count is decreased by one
|
|
* when e_cal_data_model_unsubscribe() is called.
|
|
*
|
|
* Note: A special case when both @range_start and @range_end are zero
|
|
* is treated as a request with no time constraint. This limits
|
|
* the result only to those components which satisfy given filter.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_subscribe (ECalDataModel *data_model,
|
|
ECalDataModelSubscriber *subscriber,
|
|
time_t range_start,
|
|
time_t range_end)
|
|
{
|
|
SubscriberData *subs_data = NULL;
|
|
GSList *link;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
|
|
SubscriberData *subs_data = link->data;
|
|
|
|
if (!subs_data)
|
|
continue;
|
|
|
|
if (subs_data->subscriber == subscriber)
|
|
break;
|
|
}
|
|
|
|
if (link != NULL) {
|
|
time_t new_range_start = range_start, new_range_end = range_end;
|
|
time_t old_range_start, old_range_end;
|
|
|
|
/* The subscriber updates its time range (it is already known) */
|
|
subs_data = link->data;
|
|
|
|
/* No range change */
|
|
if (range_start == subs_data->range_start &&
|
|
range_end == subs_data->range_end) {
|
|
UNLOCK_PROPS ();
|
|
return;
|
|
}
|
|
|
|
old_range_start = subs_data->range_start;
|
|
old_range_end = subs_data->range_end;
|
|
|
|
if (new_range_start == (time_t) 0 && new_range_end == (time_t) 0) {
|
|
new_range_start = data_model->priv->range_start;
|
|
new_range_end = data_model->priv->range_end;
|
|
}
|
|
|
|
if (new_range_start == (time_t) 0 && new_range_end == (time_t) 0) {
|
|
/* The subscriber is looking for everything and the data_model has everything too */
|
|
e_cal_data_model_subscriber_freeze (subs_data->subscriber);
|
|
cal_data_model_foreach_component (data_model,
|
|
new_range_start, old_range_start,
|
|
cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE);
|
|
e_cal_data_model_subscriber_thaw (subs_data->subscriber);
|
|
} else {
|
|
e_cal_data_model_subscriber_freeze (subs_data->subscriber);
|
|
|
|
if (new_range_start >= old_range_end ||
|
|
new_range_end <= old_range_start) {
|
|
subs_data->range_start = range_start;
|
|
subs_data->range_end = range_end;
|
|
|
|
/* Completely new range, not overlapping with the former range,
|
|
everything previously added can be removed... */
|
|
cal_data_model_foreach_component (data_model,
|
|
old_range_start, old_range_end,
|
|
cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE);
|
|
|
|
subs_data->range_start = old_range_start;
|
|
subs_data->range_end = old_range_end;
|
|
|
|
/* ...and components from the new range can be added */
|
|
cal_data_model_foreach_component (data_model,
|
|
new_range_start, new_range_end,
|
|
cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE);
|
|
} else {
|
|
if (new_range_start < old_range_start) {
|
|
/* Add those known in the new extended range from the start */
|
|
cal_data_model_foreach_component (data_model,
|
|
new_range_start, old_range_start,
|
|
cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE);
|
|
} else if (new_range_start > old_range_start) {
|
|
subs_data->range_start = range_start;
|
|
subs_data->range_end = range_end;
|
|
|
|
/* Remove those out of the new range from the start */
|
|
cal_data_model_foreach_component (data_model,
|
|
old_range_start, new_range_start,
|
|
cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE);
|
|
|
|
subs_data->range_start = old_range_start;
|
|
subs_data->range_end = old_range_end;
|
|
}
|
|
|
|
if (new_range_end > old_range_end) {
|
|
/* Add those known in the new extended range from the end */
|
|
cal_data_model_foreach_component (data_model,
|
|
old_range_end, new_range_end,
|
|
cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE);
|
|
} else if (new_range_end < old_range_end) {
|
|
subs_data->range_start = range_start;
|
|
subs_data->range_end = range_end;
|
|
|
|
/* Remove those out of the new range from the end */
|
|
cal_data_model_foreach_component (data_model,
|
|
new_range_end, old_range_end,
|
|
cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE);
|
|
|
|
subs_data->range_start = old_range_start;
|
|
subs_data->range_end = old_range_end;
|
|
}
|
|
}
|
|
|
|
e_cal_data_model_subscriber_thaw (subs_data->subscriber);
|
|
}
|
|
|
|
subs_data->range_start = range_start;
|
|
subs_data->range_end = range_end;
|
|
} else {
|
|
subs_data = subscriber_data_new (subscriber, range_start, range_end);
|
|
|
|
data_model->priv->subscribers = g_slist_prepend (data_model->priv->subscribers, subs_data);
|
|
|
|
e_cal_data_model_subscriber_freeze (subscriber);
|
|
cal_data_model_foreach_component (data_model, range_start, range_end,
|
|
cal_data_model_add_to_subscriber, subscriber, TRUE);
|
|
e_cal_data_model_subscriber_thaw (subscriber);
|
|
}
|
|
|
|
cal_data_model_update_time_range (data_model);
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_unsubscribe:
|
|
* @data_model: an #EDataModel instance
|
|
* @subscriber: an #ECalDataModelSubscriber instance
|
|
*
|
|
* Removes the @subscriber from the set of subscribers for the @data_model.
|
|
* Remove of the @subscriber, which is not in the set of subscribers for
|
|
* the @data_model does nothing.
|
|
*
|
|
* Note: The @subscriber is not notified about a removal of the components
|
|
* which could be added previously, while it was subscribed for the change
|
|
* notifications.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_unsubscribe (ECalDataModel *data_model,
|
|
ECalDataModelSubscriber *subscriber)
|
|
{
|
|
GSList *link;
|
|
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
|
|
SubscriberData *subs_data = link->data;
|
|
|
|
if (!subs_data)
|
|
continue;
|
|
|
|
if (subs_data->subscriber == subscriber) {
|
|
data_model->priv->subscribers = g_slist_remove (data_model->priv->subscribers, subs_data);
|
|
subscriber_data_free (subs_data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
cal_data_model_update_time_range (data_model);
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_get_subscriber_range:
|
|
* @data_model: an #EDataModel instance
|
|
* @subscriber: an #ECalDataModelSubscriber instance
|
|
* @range_start: (out): time range start for the @subscriber
|
|
* @range_end: (out): time range end for the @subscriber
|
|
*
|
|
* Obtains currently set time range for the @subscriber. In case
|
|
* the subscriber is not found returns #FALSE and both @range_start
|
|
* and @range_end are left untouched.
|
|
*
|
|
* Returns: Whether the @subscriber was found and the @range_start with
|
|
* the @range_end were set to its current time range it uses.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
gboolean
|
|
e_cal_data_model_get_subscriber_range (ECalDataModel *data_model,
|
|
ECalDataModelSubscriber *subscriber,
|
|
time_t *range_start,
|
|
time_t *range_end)
|
|
{
|
|
GSList *link;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber), FALSE);
|
|
g_return_val_if_fail (range_start, FALSE);
|
|
g_return_val_if_fail (range_end, FALSE);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
|
|
SubscriberData *subs_data = link->data;
|
|
|
|
if (!subs_data)
|
|
continue;
|
|
|
|
if (subs_data->subscriber == subscriber) {
|
|
*range_start = subs_data->range_start;
|
|
*range_end = subs_data->range_end;
|
|
break;
|
|
}
|
|
}
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return link != NULL;
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_freeze_views_update:
|
|
* @data_model: an #EDataModel instance
|
|
*
|
|
* Freezes any views updates until e_cal_data_model_thaw_views_update() is
|
|
* called. This can be called nested, then the same count of the calls of
|
|
* e_cal_data_model_thaw_views_update() is expected to unlock the views update.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_freeze_views_update (ECalDataModel *data_model)
|
|
{
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
data_model->priv->views_update_freeze++;
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_thaw_views_update:
|
|
* @data_model: an #EDataModel instance
|
|
*
|
|
* A pair function for e_cal_data_model_freeze_views_update(), to unlock
|
|
* views update.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_cal_data_model_thaw_views_update (ECalDataModel *data_model)
|
|
{
|
|
g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
|
|
|
|
LOCK_PROPS ();
|
|
|
|
if (!data_model->priv->views_update_freeze) {
|
|
UNLOCK_PROPS ();
|
|
g_warn_if_reached ();
|
|
return;
|
|
}
|
|
|
|
data_model->priv->views_update_freeze--;
|
|
if (data_model->priv->views_update_freeze == 0 &&
|
|
data_model->priv->views_update_required)
|
|
cal_data_model_rebuild_everything (data_model, TRUE);
|
|
|
|
UNLOCK_PROPS ();
|
|
}
|
|
|
|
/**
|
|
* e_cal_data_model_is_views_update_frozen:
|
|
* @data_model: an #EDataModel instance
|
|
*
|
|
* Check whether any views updates are currently frozen. This is influenced by
|
|
* e_cal_data_model_freeze_views_update() and e_cal_data_model_thaw_views_update().
|
|
*
|
|
* Returns: Whether any views updates are currently frozen.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
gboolean
|
|
e_cal_data_model_is_views_update_frozen (ECalDataModel *data_model)
|
|
{
|
|
gboolean is_frozen;
|
|
|
|
g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
|
|
|
|
LOCK_PROPS ();
|
|
|
|
is_frozen = data_model->priv->views_update_freeze > 0;
|
|
|
|
UNLOCK_PROPS ();
|
|
|
|
return is_frozen;
|
|
}
|