This particular changeset is important because it fixes that annoying issue of the event staying when edited out of the range (out of the month in Month view, week in Week view, etc). Commit: https://gitlab.gnome.org/GNOME/evolution/commit/3cea6c53337382330c443f50f858424ead2005b6
3003 lines
82 KiB
C
3003 lines
82 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>
|
|
*/
|
|
|
|
#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_gather_subscribers_cb (ECalDataModel *data_model,
|
|
ECalClient *client,
|
|
ECalDataModelSubscriber *subscriber,
|
|
gpointer user_data)
|
|
{
|
|
GHashTable *subscribers = user_data;
|
|
|
|
g_return_if_fail (subscribers != NULL);
|
|
|
|
g_hash_table_insert (subscribers, g_object_ref (subscriber), 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, *old_id = NULL;
|
|
ComponentData *old_comp_data = NULL;
|
|
time_t old_instance_start = (time_t) 0, old_instance_end = (time_t) 0;
|
|
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 (old_comp_data) {
|
|
old_id = e_cal_component_get_id (old_comp_data->component);
|
|
old_instance_start = old_comp_data->instance_start;
|
|
old_instance_end = old_comp_data->instance_end;
|
|
}
|
|
|
|
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 if (comp_data->instance_start != old_instance_start ||
|
|
comp_data->instance_end != old_instance_end) {
|
|
/* Component moved to a different time; some subscribers may lose it,
|
|
some may just modify it, some may have it added. */
|
|
GHashTable *old_subscribers, *new_subscribers;
|
|
GHashTableIter iter;
|
|
gpointer key;
|
|
|
|
old_subscribers = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
|
|
new_subscribers = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
|
|
|
|
cal_data_model_foreach_subscriber_in_range (data_model, view_data->client,
|
|
old_instance_start, old_instance_end,
|
|
cal_data_model_gather_subscribers_cb, old_subscribers);
|
|
|
|
cal_data_model_foreach_subscriber_in_range (data_model, view_data->client,
|
|
comp_data->instance_start, comp_data->instance_end,
|
|
cal_data_model_gather_subscribers_cb, new_subscribers);
|
|
|
|
g_hash_table_iter_init (&iter, old_subscribers);
|
|
while (g_hash_table_iter_next (&iter, &key, NULL)) {
|
|
ECalDataModelSubscriber *subscriber = key;
|
|
|
|
/* If in both hashes, then the subscriber can be notified with 'modified',
|
|
otherwise the component had been 'removed' for it. */
|
|
if (g_hash_table_remove (new_subscribers, subscriber))
|
|
e_cal_data_model_subscriber_component_modified (subscriber, view_data->client, comp_data->component);
|
|
else if (old_id)
|
|
e_cal_data_model_subscriber_component_removed (subscriber, view_data->client, old_id->uid, old_id->rid);
|
|
}
|
|
|
|
/* Those which left in the new_subscribers have the component added. */
|
|
g_hash_table_iter_init (&iter, new_subscribers);
|
|
while (g_hash_table_iter_next (&iter, &key, NULL)) {
|
|
ECalDataModelSubscriber *subscriber = key;
|
|
|
|
e_cal_data_model_subscriber_component_added (subscriber, view_data->client, comp_data->component);
|
|
}
|
|
|
|
g_hash_table_destroy (old_subscribers);
|
|
g_hash_table_destroy (new_subscribers);
|
|
} 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);
|
|
|
|
if (old_id)
|
|
e_cal_component_free_id (old_id);
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
if (range_end <= range_start) {
|
|
range_start = 0;
|
|
range_end = time (NULL);
|
|
}
|
|
|
|
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 *rclient = 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, rclient);
|
|
}
|
|
}
|
|
|
|
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;
|
|
default:
|
|
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;
|
|
default:
|
|
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)) {
|
|
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;
|
|
}
|