Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / e-cal-data-model.c
blob9215edbb0a17a48c0d024d619405e49b30401a95
1 /*
2 * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 * Authors: Milan Crha <mcrha@redhat.com>
19 #include "evolution-config.h"
21 #include <glib.h>
22 #include <glib/gi18n-lib.h>
24 #include "comp-util.h"
25 #include "e-cal-data-model.h"
27 #define LOCK_PROPS() g_rec_mutex_lock (&data_model->priv->props_lock)
28 #define UNLOCK_PROPS() g_rec_mutex_unlock (&data_model->priv->props_lock)
30 struct _ECalDataModelPrivate {
31 GThread *main_thread;
32 ECalDataModelSubmitThreadJobFunc submit_thread_job_func;
33 GWeakRef *submit_thread_job_responder;
34 GThreadPool *thread_pool;
36 GRecMutex props_lock; /* to guard all the below members */
38 gboolean disposing;
39 gboolean expand_recurrences;
40 gchar *filter;
41 gchar *full_filter; /* to be used with views */
42 icaltimezone *zone;
43 time_t range_start;
44 time_t range_end;
46 GHashTable *clients; /* ESource::uid ~> ECalClient */
47 GHashTable *views; /* ECalClient ~> ViewData */
48 GSList *subscribers; /* ~> SubscriberData */
50 guint32 views_update_freeze;
51 gboolean views_update_required;
54 enum {
55 PROP_0,
56 PROP_EXPAND_RECURRENCES,
57 PROP_TIMEZONE
60 enum {
61 VIEW_STATE_CHANGED,
62 LAST_SIGNAL
65 static guint signals[LAST_SIGNAL];
67 G_DEFINE_TYPE (ECalDataModel, e_cal_data_model, G_TYPE_OBJECT)
69 typedef struct _ComponentData {
70 ECalComponent *component;
71 time_t instance_start;
72 time_t instance_end;
73 gboolean is_detached;
74 } ComponentData;
76 typedef struct _ViewData {
77 gint ref_count;
78 GRecMutex lock;
79 gboolean is_used;
81 ECalClient *client;
82 ECalClientView *view;
83 gulong objects_added_id;
84 gulong objects_modified_id;
85 gulong objects_removed_id;
86 gulong progress_id;
87 gulong complete_id;
89 GHashTable *components; /* ECalComponentId ~> ComponentData */
90 GHashTable *lost_components; /* ECalComponentId ~> ComponentData; when re-running view, valid till 'complete' is received */
91 gboolean received_complete;
92 GSList *to_expand_recurrences; /* icalcomponent */
93 GSList *expanded_recurrences; /* ComponentData */
94 gint pending_expand_recurrences; /* how many is waiting to be processed */
96 GCancellable *cancellable;
97 } ViewData;
99 typedef struct _SubscriberData {
100 ECalDataModelSubscriber *subscriber;
101 time_t range_start;
102 time_t range_end;
103 } SubscriberData;
105 static ComponentData *
106 component_data_new (ECalComponent *comp,
107 time_t instance_start,
108 time_t instance_end,
109 gboolean is_detached)
111 ComponentData *comp_data;
113 g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
115 comp_data = g_new0 (ComponentData, 1);
116 comp_data->component = g_object_ref (comp);
117 comp_data->instance_start = instance_start;
118 comp_data->instance_end = instance_end;
119 comp_data->is_detached = is_detached;
121 return comp_data;
124 static void
125 component_data_free (gpointer ptr)
127 ComponentData *comp_data = ptr;
129 if (comp_data) {
130 g_object_unref (comp_data->component);
131 g_free (comp_data);
135 static gboolean
136 component_data_equal (ComponentData *comp_data1,
137 ComponentData *comp_data2)
139 icalcomponent *icomp1, *icomp2;
140 struct icaltimetype tt1, tt2;
141 gchar *as_str1, *as_str2;
142 gboolean equal;
144 if (comp_data1 == comp_data2)
145 return TRUE;
147 if (!comp_data1 || !comp_data2 || !comp_data1->component || !comp_data2->component)
148 return FALSE;
150 if (comp_data1->instance_start != comp_data2->instance_start ||
151 comp_data1->instance_end != comp_data2->instance_end)
152 return FALSE;
154 icomp1 = e_cal_component_get_icalcomponent (comp_data1->component);
155 icomp2 = e_cal_component_get_icalcomponent (comp_data2->component);
157 if (!icomp1 || !icomp2 ||
158 icalcomponent_get_sequence (icomp1) != icalcomponent_get_sequence (icomp2) ||
159 g_strcmp0 (icalcomponent_get_uid (icomp1), icalcomponent_get_uid (icomp2)) != 0)
160 return FALSE;
162 tt1 = icalcomponent_get_recurrenceid (icomp1);
163 tt2 = icalcomponent_get_recurrenceid (icomp2);
164 if ((icaltime_is_valid_time (tt1) ? 1 : 0) != (icaltime_is_valid_time (tt2) ? 1 : 0) ||
165 (icaltime_is_null_time (tt1) ? 1 : 0) != (icaltime_is_null_time (tt2) ? 1 : 0) ||
166 icaltime_compare (tt1, tt2) != 0)
167 return FALSE;
169 tt1 = icalcomponent_get_dtstamp (icomp1);
170 tt2 = icalcomponent_get_dtstamp (icomp2);
171 if ((icaltime_is_valid_time (tt1) ? 1 : 0) != (icaltime_is_valid_time (tt2) ? 1 : 0) ||
172 (icaltime_is_null_time (tt1) ? 1 : 0) != (icaltime_is_null_time (tt2) ? 1 : 0) ||
173 icaltime_compare (tt1, tt2) != 0)
174 return FALSE;
176 /* Maybe not so effective compare, but might be still more effective
177 than updating whole UI with false notifications */
178 as_str1 = icalcomponent_as_ical_string_r (icomp1);
179 as_str2 = icalcomponent_as_ical_string_r (icomp2);
181 equal = g_strcmp0 (as_str1, as_str2) == 0;
183 g_free (as_str1);
184 g_free (as_str2);
186 return equal;
189 static ViewData *
190 view_data_new (ECalClient *client)
192 ViewData *view_data;
194 g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
196 view_data = g_new0 (ViewData, 1);
197 view_data->ref_count = 1;
198 g_rec_mutex_init (&view_data->lock);
199 view_data->is_used = TRUE;
200 view_data->client = g_object_ref (client);
201 view_data->components = g_hash_table_new_full (
202 (GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal,
203 (GDestroyNotify) e_cal_component_free_id, component_data_free);
205 return view_data;
208 static void
209 view_data_disconnect_view (ViewData *view_data)
211 if (view_data && view_data->view) {
212 #define disconnect(x) G_STMT_START { \
213 if (view_data->x) { \
214 g_signal_handler_disconnect (view_data->view, view_data->x); \
215 view_data->x = 0; \
217 } G_STMT_END
219 disconnect (objects_added_id);
220 disconnect (objects_modified_id);
221 disconnect (objects_removed_id);
222 disconnect (progress_id);
223 disconnect (complete_id);
225 #undef disconnect
229 static ViewData *
230 view_data_ref (ViewData *view_data)
232 g_return_val_if_fail (view_data != NULL, NULL);
234 g_atomic_int_inc (&view_data->ref_count);
236 return view_data;
239 static void
240 view_data_unref (gpointer ptr)
242 ViewData *view_data = ptr;
244 if (view_data) {
245 if (g_atomic_int_dec_and_test (&view_data->ref_count)) {
246 view_data_disconnect_view (view_data);
247 if (view_data->cancellable)
248 g_cancellable_cancel (view_data->cancellable);
249 g_clear_object (&view_data->cancellable);
250 g_clear_object (&view_data->client);
251 g_clear_object (&view_data->view);
252 g_hash_table_destroy (view_data->components);
253 if (view_data->lost_components)
254 g_hash_table_destroy (view_data->lost_components);
255 g_slist_free_full (view_data->to_expand_recurrences, (GDestroyNotify) icalcomponent_free);
256 g_slist_free_full (view_data->expanded_recurrences, component_data_free);
257 g_rec_mutex_clear (&view_data->lock);
258 g_free (view_data);
263 static void
264 view_data_lock (ViewData *view_data)
266 g_return_if_fail (view_data != NULL);
268 g_rec_mutex_lock (&view_data->lock);
271 static void
272 view_data_unlock (ViewData *view_data)
274 g_return_if_fail (view_data != NULL);
276 g_rec_mutex_unlock (&view_data->lock);
279 static SubscriberData *
280 subscriber_data_new (ECalDataModelSubscriber *subscriber,
281 time_t range_start,
282 time_t range_end)
284 SubscriberData *subs_data;
286 g_return_val_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber), NULL);
288 subs_data = g_new0 (SubscriberData, 1);
289 subs_data->subscriber = g_object_ref (subscriber);
290 subs_data->range_start = range_start;
291 subs_data->range_end = range_end;
293 return subs_data;
296 static void
297 subscriber_data_free (gpointer ptr)
299 SubscriberData *subs_data = ptr;
301 if (subs_data) {
302 g_clear_object (&subs_data->subscriber);
303 g_free (subs_data);
307 typedef struct _ViewStateChangedData {
308 ECalDataModel *data_model;
309 ECalClientView *view;
310 ECalDataModelViewState state;
311 guint percent;
312 gchar *message;
313 GError *error;
314 } ViewStateChangedData;
316 static void
317 view_state_changed_data_free (gpointer ptr)
319 ViewStateChangedData *vscd = ptr;
321 if (vscd) {
322 g_clear_object (&vscd->data_model);
323 g_clear_object (&vscd->view);
324 g_clear_error (&vscd->error);
325 g_free (vscd->message);
326 g_free (vscd);
330 static gboolean
331 cal_data_model_emit_view_state_changed_timeout_cb (gpointer user_data)
333 ViewStateChangedData *vscd = user_data;
335 g_return_val_if_fail (vscd != NULL, FALSE);
336 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (vscd->data_model), FALSE);
337 g_return_val_if_fail (E_IS_CAL_CLIENT_VIEW (vscd->view), FALSE);
339 g_signal_emit (vscd->data_model, signals[VIEW_STATE_CHANGED], 0,
340 vscd->view, vscd->state, vscd->percent, vscd->message, vscd->error);
342 return FALSE;
345 static void
346 cal_data_model_emit_view_state_changed (ECalDataModel *data_model,
347 ECalClientView *view,
348 ECalDataModelViewState state,
349 guint percent,
350 const gchar *message,
351 const GError *error)
353 ViewStateChangedData *vscd;
355 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
356 g_return_if_fail (E_IS_CAL_CLIENT_VIEW (view));
358 if (e_cal_data_model_get_disposing (data_model))
359 return;
361 vscd = g_new0 (ViewStateChangedData, 1);
362 vscd->data_model = g_object_ref (data_model);
363 vscd->view = g_object_ref (view);
364 vscd->state = state;
365 vscd->percent = percent;
366 vscd->message = g_strdup (message);
367 vscd->error = error ? g_error_copy (error) : NULL;
369 g_timeout_add_full (G_PRIORITY_DEFAULT, 1,
370 cal_data_model_emit_view_state_changed_timeout_cb,
371 vscd, view_state_changed_data_free);
374 typedef void (* InternalThreadJobFunc) (ECalDataModel *data_model, gpointer user_data);
376 typedef struct _InternalThreadJobData {
377 InternalThreadJobFunc func;
378 gpointer user_data;
379 } InternalThreadJobData;
381 static void
382 cal_data_model_internal_thread_job_func (gpointer data,
383 gpointer user_data)
385 ECalDataModel *data_model = user_data;
386 InternalThreadJobData *job_data = data;
388 g_return_if_fail (job_data != NULL);
389 g_return_if_fail (job_data->func != NULL);
391 job_data->func (data_model, job_data->user_data);
393 g_free (job_data);
396 static void
397 cal_data_model_submit_internal_thread_job (ECalDataModel *data_model,
398 InternalThreadJobFunc func,
399 gpointer user_data)
401 InternalThreadJobData *job_data;
403 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
404 g_return_if_fail (func != NULL);
406 job_data = g_new0 (InternalThreadJobData, 1);
407 job_data->func = func;
408 job_data->user_data = user_data;
410 g_thread_pool_push (data_model->priv->thread_pool, job_data, NULL);
413 typedef struct _SubmitThreadJobData {
414 ECalDataModel *data_model;
415 const gchar *description;
416 const gchar *alert_ident;
417 const gchar *alert_arg_0;
418 EAlertSinkThreadJobFunc func;
419 gpointer user_data;
420 GDestroyNotify free_user_data;
422 GCancellable *cancellable;
423 gboolean finished;
424 GMutex mutex;
425 GCond cond;
426 } SubmitThreadJobData;
428 static gboolean
429 cal_data_model_call_submit_thread_job (gpointer user_data)
431 SubmitThreadJobData *stj_data = user_data;
432 GObject *responder;
434 g_return_val_if_fail (stj_data != NULL, FALSE);
436 g_mutex_lock (&stj_data->mutex);
437 responder = g_weak_ref_get (stj_data->data_model->priv->submit_thread_job_responder);
439 stj_data->cancellable = stj_data->data_model->priv->submit_thread_job_func (
440 responder, stj_data->description, stj_data->alert_ident, stj_data->alert_arg_0,
441 stj_data->func, stj_data->user_data, stj_data->free_user_data);
443 g_clear_object (&responder);
445 stj_data->finished = TRUE;
446 g_cond_signal (&stj_data->cond);
447 g_mutex_unlock (&stj_data->mutex);
449 return FALSE;
453 * e_cal_data_model_submit_thread_job:
454 * @data_model: an #ECalDataModel
455 * @description: user-friendly description of the job, to be shown in UI
456 * @alert_ident: in case of an error, this alert identificator is used
457 * for EAlert construction
458 * @alert_arg_0: (allow-none): in case of an error, use this string as
459 * the first argument to the EAlert construction; the second argument
460 * is the actual error message; can be #NULL, in which case only
461 * the error message is passed to the EAlert construction
462 * @func: function to be run in a dedicated thread
463 * @user_data: (allow-none): custom data passed into @func; can be #NULL
464 * @free_user_data: (allow-none): function to be called on @user_data,
465 * when the job is over; can be #NULL
467 * Runs the @func in a dedicated thread. Any error is propagated to UI.
468 * The cancellable passed into the @func is a #CamelOperation, thus
469 * the caller can overwrite progress and description message on it.
471 * Returns: (transfer full): Newly created #GCancellable on success.
472 * The caller is responsible to g_object_unref() it when done with it.
474 * Note: The @free_user_data, if set, is called in the main thread.
476 * Note: This is a blocking call, it waits until the thread job is submitted.
478 * Since: 3.16
480 GCancellable *
481 e_cal_data_model_submit_thread_job (ECalDataModel *data_model,
482 const gchar *description,
483 const gchar *alert_ident,
484 const gchar *alert_arg_0,
485 EAlertSinkThreadJobFunc func,
486 gpointer user_data,
487 GDestroyNotify free_user_data)
489 SubmitThreadJobData stj_data;
491 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
492 g_return_val_if_fail (data_model->priv->submit_thread_job_func != NULL, NULL);
494 if (g_thread_self () == data_model->priv->main_thread) {
495 GCancellable *cancellable;
496 GObject *responder;
498 responder = g_weak_ref_get (data_model->priv->submit_thread_job_responder);
499 cancellable = data_model->priv->submit_thread_job_func (
500 responder, description, alert_ident, alert_arg_0,
501 func, user_data, free_user_data);
502 g_clear_object (&responder);
504 return cancellable;
507 stj_data.data_model = data_model;
508 stj_data.description = description;
509 stj_data.alert_ident = alert_ident;
510 stj_data.alert_arg_0 = alert_arg_0;
511 stj_data.func = func;
512 stj_data.user_data = user_data;
513 stj_data.free_user_data = free_user_data;
514 stj_data.cancellable = NULL;
515 stj_data.finished = FALSE;
516 g_mutex_init (&stj_data.mutex);
517 g_cond_init (&stj_data.cond);
519 g_timeout_add (1, cal_data_model_call_submit_thread_job, &stj_data);
521 g_mutex_lock (&stj_data.mutex);
522 while (!stj_data.finished) {
523 g_cond_wait (&stj_data.cond, &stj_data.mutex);
525 g_mutex_unlock (&stj_data.mutex);
527 g_cond_clear (&stj_data.cond);
528 g_mutex_clear (&stj_data.mutex);
530 return stj_data.cancellable;
533 typedef void (* ECalDataModelForeachSubscriberFunc) (ECalDataModel *data_model,
534 ECalClient *client,
535 ECalDataModelSubscriber *subscriber,
536 gpointer user_data);
538 static void
539 cal_data_model_foreach_subscriber_in_range (ECalDataModel *data_model,
540 ECalClient *client,
541 time_t in_range_start,
542 time_t in_range_end,
543 ECalDataModelForeachSubscriberFunc func,
544 gpointer user_data)
546 GSList *link;
548 g_return_if_fail (func != NULL);
550 LOCK_PROPS ();
552 if (in_range_end == (time_t) 0) {
553 in_range_end = in_range_start;
556 for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
557 SubscriberData *subs_data = link->data;
559 if ((in_range_start == (time_t) 0 && in_range_end == (time_t) 0) ||
560 (subs_data->range_start == (time_t) 0 && subs_data->range_end == (time_t) 0) ||
561 (subs_data->range_start <= in_range_end && subs_data->range_end >= in_range_start))
562 func (data_model, client, subs_data->subscriber, user_data);
565 UNLOCK_PROPS ();
568 static void
569 cal_data_model_foreach_subscriber (ECalDataModel *data_model,
570 ECalClient *client,
571 ECalDataModelForeachSubscriberFunc func,
572 gpointer user_data)
574 g_return_if_fail (func != NULL);
576 cal_data_model_foreach_subscriber_in_range (data_model, client, (time_t) 0, (time_t) 0, func, user_data);
579 static void
580 cal_data_model_freeze_subscriber_cb (ECalDataModel *data_model,
581 ECalClient *client,
582 ECalDataModelSubscriber *subscriber,
583 gpointer user_data)
585 e_cal_data_model_subscriber_freeze (subscriber);
588 static void
589 cal_data_model_thaw_subscriber_cb (ECalDataModel *data_model,
590 ECalClient *client,
591 ECalDataModelSubscriber *subscriber,
592 gpointer user_data)
594 e_cal_data_model_subscriber_thaw (subscriber);
597 static void
598 cal_data_model_freeze_all_subscribers (ECalDataModel *data_model)
600 cal_data_model_foreach_subscriber (data_model, NULL, cal_data_model_freeze_subscriber_cb, NULL);
603 static void
604 cal_data_model_thaw_all_subscribers (ECalDataModel *data_model)
606 cal_data_model_foreach_subscriber (data_model, NULL, cal_data_model_thaw_subscriber_cb, NULL);
609 static void
610 cal_data_model_add_component_cb (ECalDataModel *data_model,
611 ECalClient *client,
612 ECalDataModelSubscriber *subscriber,
613 gpointer user_data)
615 ECalComponent *comp = user_data;
617 g_return_if_fail (comp != NULL);
619 e_cal_data_model_subscriber_component_added (subscriber, client, comp);
622 static void
623 cal_data_model_modify_component_cb (ECalDataModel *data_model,
624 ECalClient *client,
625 ECalDataModelSubscriber *subscriber,
626 gpointer user_data)
628 ECalComponent *comp = user_data;
630 g_return_if_fail (comp != NULL);
632 e_cal_data_model_subscriber_component_modified (subscriber, client, comp);
635 static void
636 cal_data_model_remove_one_view_component_cb (ECalDataModel *data_model,
637 ECalClient *client,
638 ECalDataModelSubscriber *subscriber,
639 gpointer user_data)
641 const ECalComponentId *id = user_data;
643 g_return_if_fail (id != NULL);
645 e_cal_data_model_subscriber_component_removed (subscriber, client, id->uid, id->rid);
648 static void
649 cal_data_model_remove_components (ECalDataModel *data_model,
650 ECalClient *client,
651 GHashTable *components,
652 GHashTable *also_remove_from)
654 GList *ids, *ilink;
656 g_return_if_fail (data_model != NULL);
657 g_return_if_fail (components != NULL);
659 cal_data_model_freeze_all_subscribers (data_model);
661 ids = g_hash_table_get_keys (components);
663 for (ilink = ids; ilink; ilink = g_list_next (ilink)) {
664 ECalComponentId *id = ilink->data;
665 ComponentData *comp_data;
666 time_t instance_start = (time_t) 0, instance_end = (time_t) 0;
668 if (!id)
669 continue;
671 /* Try to limit which subscribers will be notified about removal */
672 comp_data = g_hash_table_lookup (components, id);
673 if (comp_data) {
674 instance_start = comp_data->instance_start;
675 instance_end = comp_data->instance_end;
678 cal_data_model_foreach_subscriber_in_range (data_model, client,
679 instance_start, instance_end,
680 cal_data_model_remove_one_view_component_cb, id);
682 if (also_remove_from)
683 g_hash_table_remove (also_remove_from, id);
686 g_list_free (ids);
688 cal_data_model_thaw_all_subscribers (data_model);
691 static void
692 cal_data_model_calc_range (ECalDataModel *data_model,
693 time_t *range_start,
694 time_t *range_end)
696 GSList *link;
698 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
699 g_return_if_fail (range_start != NULL);
700 g_return_if_fail (range_end != NULL);
702 *range_start = (time_t) 0;
703 *range_end = (time_t) 0;
705 LOCK_PROPS ();
707 for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
708 SubscriberData *subs_data = link->data;
710 if (!subs_data)
711 continue;
713 if (subs_data->range_start == (time_t) 0 && subs_data->range_end == (time_t) 0) {
714 *range_start = (time_t) 0;
715 *range_end = (time_t) 0;
716 break;
719 if (link == data_model->priv->subscribers) {
720 *range_start = subs_data->range_start;
721 *range_end = subs_data->range_end;
722 } else {
723 if (*range_start > subs_data->range_start)
724 *range_start = subs_data->range_start;
725 if (*range_end < subs_data->range_end)
726 *range_end = subs_data->range_end;
730 UNLOCK_PROPS ();
733 static gboolean
734 cal_data_model_update_full_filter (ECalDataModel *data_model)
736 gchar *filter;
737 time_t range_start, range_end;
738 gboolean changed;
740 LOCK_PROPS ();
742 cal_data_model_calc_range (data_model, &range_start, &range_end);
744 if (range_start != (time_t) 0 || range_end != (time_t) 0) {
745 gchar *iso_start, *iso_end;
746 const gchar *default_tzloc = NULL;
748 iso_start = isodate_from_time_t (range_start);
749 iso_end = isodate_from_time_t (range_end);
751 if (data_model->priv->zone && data_model->priv->zone != icaltimezone_get_utc_timezone ())
752 default_tzloc = icaltimezone_get_location (data_model->priv->zone);
753 if (!default_tzloc)
754 default_tzloc = "";
756 if (data_model->priv->filter) {
757 filter = g_strdup_printf (
758 "(and (occur-in-time-range? (make-time \"%s\") (make-time \"%s\") \"%s\") %s)",
759 iso_start, iso_end, default_tzloc, data_model->priv->filter);
760 } else {
761 filter = g_strdup_printf (
762 "(occur-in-time-range? (make-time \"%s\") (make-time \"%s\") \"%s\")",
763 iso_start, iso_end, default_tzloc);
766 g_free (iso_start);
767 g_free (iso_end);
768 } else if (data_model->priv->filter) {
769 filter = g_strdup (data_model->priv->filter);
770 } else {
771 filter = g_strdup ("#t");
774 changed = g_strcmp0 (data_model->priv->full_filter, filter) != 0;
776 if (changed) {
777 g_free (data_model->priv->full_filter);
778 data_model->priv->full_filter = filter;
779 } else {
780 g_free (filter);
783 UNLOCK_PROPS ();
785 return changed;
788 /* This consumes the comp_data - not so nice, but simpler
789 than adding reference counter for the structure */
790 static void
791 cal_data_model_process_added_component (ECalDataModel *data_model,
792 ViewData *view_data,
793 ComponentData *comp_data,
794 GHashTable *known_instances)
796 ECalComponentId *id;
797 ComponentData *old_comp_data = NULL;
798 gboolean comp_data_equal;
800 g_return_if_fail (data_model != NULL);
801 g_return_if_fail (view_data != NULL);
802 g_return_if_fail (comp_data != NULL);
804 id = e_cal_component_get_id (comp_data->component);
805 g_return_if_fail (id != NULL);
807 view_data_lock (view_data);
809 if (!old_comp_data && view_data->lost_components)
810 old_comp_data = g_hash_table_lookup (view_data->lost_components, id);
812 if (!old_comp_data && known_instances)
813 old_comp_data = g_hash_table_lookup (known_instances, id);
815 if (!old_comp_data)
816 old_comp_data = g_hash_table_lookup (view_data->components, id);
818 if (old_comp_data) {
819 /* It can be a previously added detached instance received
820 during recurrences expand */
821 if (!comp_data->is_detached)
822 comp_data->is_detached = old_comp_data->is_detached;
825 comp_data_equal = component_data_equal (comp_data, old_comp_data);
827 if (view_data->lost_components)
828 g_hash_table_remove (view_data->lost_components, id);
830 if (known_instances)
831 g_hash_table_remove (known_instances, id);
833 /* Note: old_comp_data is freed or NULL now */
835 /* 'id' is stolen by view_data->components */
836 g_hash_table_insert (view_data->components, id, comp_data);
838 if (!comp_data_equal) {
839 if (!old_comp_data)
840 cal_data_model_foreach_subscriber_in_range (data_model, view_data->client,
841 comp_data->instance_start, comp_data->instance_end,
842 cal_data_model_add_component_cb, comp_data->component);
843 else
844 cal_data_model_foreach_subscriber_in_range (data_model, view_data->client,
845 comp_data->instance_start, comp_data->instance_end,
846 cal_data_model_modify_component_cb, comp_data->component);
849 view_data_unlock (view_data);
852 typedef struct _GatherComponentsData {
853 const gchar *uid;
854 GList **pcomponent_ids; /* ECalComponentId, can be owned by the hash table */
855 GHashTable *component_ids_hash;
856 gboolean copy_ids;
857 gboolean all_instances; /* FALSE to get only nondetached component instances */
858 } GatherComponentsData;
860 static void
861 cal_data_model_gather_components (gpointer key,
862 gpointer value,
863 gpointer user_data)
865 ECalComponentId *id = key;
866 ComponentData *comp_data = value;
867 GatherComponentsData *gather_data = user_data;
869 g_return_if_fail (id != NULL);
870 g_return_if_fail (comp_data != NULL);
871 g_return_if_fail (gather_data != NULL);
872 g_return_if_fail (gather_data->pcomponent_ids != NULL || gather_data->component_ids_hash != NULL);
873 g_return_if_fail (gather_data->pcomponent_ids == NULL || gather_data->component_ids_hash == NULL);
875 if ((gather_data->all_instances || !comp_data->is_detached) && g_strcmp0 (id->uid, gather_data->uid) == 0) {
876 if (gather_data->component_ids_hash) {
877 ComponentData *comp_data_copy;
879 comp_data_copy = component_data_new (comp_data->component,
880 comp_data->instance_start, comp_data->instance_end,
881 comp_data->is_detached);
883 if (gather_data->copy_ids) {
884 g_hash_table_insert (gather_data->component_ids_hash,
885 e_cal_component_id_copy (id), comp_data_copy);
886 } else {
887 g_hash_table_insert (gather_data->component_ids_hash, id, comp_data_copy);
889 } else if (gather_data->copy_ids) {
890 *gather_data->pcomponent_ids = g_list_prepend (*gather_data->pcomponent_ids,
891 e_cal_component_id_copy (id));
892 } else {
893 *gather_data->pcomponent_ids = g_list_prepend (*gather_data->pcomponent_ids, id);
898 typedef struct _NotifyRecurrencesData {
899 ECalDataModel *data_model;
900 ECalClient *client;
901 } NotifyRecurrencesData;
903 static gboolean
904 cal_data_model_notify_recurrences_cb (gpointer user_data)
906 NotifyRecurrencesData *notif_data = user_data;
907 ECalDataModel *data_model;
908 ViewData *view_data;
910 g_return_val_if_fail (notif_data != NULL, FALSE);
912 data_model = notif_data->data_model;
914 LOCK_PROPS ();
916 view_data = g_hash_table_lookup (data_model->priv->views, notif_data->client);
917 if (view_data)
918 view_data_ref (view_data);
920 UNLOCK_PROPS ();
922 if (view_data) {
923 GHashTable *gathered_uids;
924 GHashTable *known_instances;
925 GSList *expanded_recurrences, *link;
927 view_data_lock (view_data);
928 expanded_recurrences = view_data->expanded_recurrences;
929 view_data->expanded_recurrences = NULL;
931 cal_data_model_freeze_all_subscribers (data_model);
933 gathered_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
934 known_instances = g_hash_table_new_full (
935 (GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal,
936 (GDestroyNotify) e_cal_component_free_id, component_data_free);
938 for (link = expanded_recurrences; link && view_data->is_used; link = g_slist_next (link)) {
939 ComponentData *comp_data = link->data;
940 icalcomponent *icomp;
941 const gchar *uid;
943 if (!comp_data)
944 continue;
946 icomp = e_cal_component_get_icalcomponent (comp_data->component);
947 if (!icomp || !icalcomponent_get_uid (icomp))
948 continue;
950 uid = icalcomponent_get_uid (icomp);
952 if (!g_hash_table_contains (gathered_uids, uid)) {
953 GatherComponentsData gather_data;
955 gather_data.uid = uid;
956 gather_data.pcomponent_ids = NULL;
957 gather_data.component_ids_hash = known_instances;
958 gather_data.copy_ids = TRUE;
959 gather_data.all_instances = FALSE;
961 g_hash_table_foreach (view_data->components,
962 cal_data_model_gather_components, &gather_data);
964 g_hash_table_insert (gathered_uids, g_strdup (uid), GINT_TO_POINTER (1));
967 /* Steal the comp_data */
968 link->data = NULL;
970 cal_data_model_process_added_component (data_model, view_data, comp_data, known_instances);
973 if (view_data->is_used && g_hash_table_size (known_instances) > 0) {
974 cal_data_model_remove_components (data_model, view_data->client, known_instances, view_data->components);
975 g_hash_table_remove_all (known_instances);
978 if (g_atomic_int_dec_and_test (&view_data->pending_expand_recurrences) &&
979 view_data->is_used && view_data->lost_components && view_data->received_complete) {
980 cal_data_model_remove_components (data_model, view_data->client, view_data->lost_components, NULL);
981 g_hash_table_destroy (view_data->lost_components);
982 view_data->lost_components = NULL;
985 g_hash_table_destroy (gathered_uids);
986 g_hash_table_destroy (known_instances);
988 view_data_unlock (view_data);
990 cal_data_model_thaw_all_subscribers (data_model);
992 view_data_unref (view_data);
994 g_slist_free_full (expanded_recurrences, component_data_free);
997 g_clear_object (&notif_data->client);
998 g_clear_object (&notif_data->data_model);
999 g_free (notif_data);
1001 return FALSE;
1004 typedef struct
1006 ECalClient *client;
1007 icaltimezone *zone;
1008 GSList **pexpanded_recurrences;
1009 } GenerateInstancesData;
1011 static gboolean
1012 cal_data_model_instance_generated (ECalComponent *comp,
1013 time_t instance_start,
1014 time_t instance_end,
1015 gpointer data)
1017 GenerateInstancesData *gid = data;
1018 ComponentData *comp_data;
1019 ECalComponent *comp_copy;
1020 icaltimetype tt, tt2;
1022 g_return_val_if_fail (gid != NULL, FALSE);
1024 comp_copy = e_cal_component_clone (comp);
1025 g_return_val_if_fail (comp_copy != NULL, FALSE);
1027 tt = icalcomponent_get_dtstart (e_cal_component_get_icalcomponent (comp_copy));
1028 tt2 = icaltime_from_timet_with_zone (instance_start, tt.is_date, gid->zone);
1029 if (tt.is_date || !tt.zone || tt.zone == icaltimezone_get_utc_timezone ())
1030 tt2.zone = NULL;
1031 else
1032 tt2.zone = gid->zone;
1033 icalcomponent_set_dtstart (e_cal_component_get_icalcomponent (comp_copy), tt2);
1035 tt = icalcomponent_get_dtend (e_cal_component_get_icalcomponent (comp_copy));
1036 tt2 = icaltime_from_timet_with_zone (instance_end, tt.is_date, gid->zone);
1037 if (tt.is_date || !tt.zone || tt.zone == icaltimezone_get_utc_timezone ())
1038 tt2.zone = NULL;
1039 else
1040 tt2.zone = gid->zone;
1041 icalcomponent_set_dtend (e_cal_component_get_icalcomponent (comp_copy), tt2);
1043 e_cal_component_rescan (comp_copy);
1045 cal_comp_get_instance_times (gid->client, e_cal_component_get_icalcomponent (comp_copy),
1046 gid->zone, &instance_start, NULL, &instance_end, NULL, NULL);
1048 if (instance_end > instance_start)
1049 instance_end--;
1051 comp_data = component_data_new (comp_copy, instance_start, instance_end, FALSE);
1052 *gid->pexpanded_recurrences = g_slist_prepend (*gid->pexpanded_recurrences, comp_data);
1054 g_object_unref (comp_copy);
1056 return TRUE;
1059 static void
1060 cal_data_model_expand_recurrences_thread (ECalDataModel *data_model,
1061 gpointer user_data)
1063 ECalClient *client = user_data;
1064 GSList *to_expand_recurrences, *link;
1065 GSList *expanded_recurrences = NULL;
1066 time_t range_start, range_end;
1067 ViewData *view_data;
1069 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
1071 LOCK_PROPS ();
1073 view_data = g_hash_table_lookup (data_model->priv->views, client);
1074 if (view_data)
1075 view_data_ref (view_data);
1077 range_start = data_model->priv->range_start;
1078 range_end = data_model->priv->range_end;
1080 UNLOCK_PROPS ();
1082 if (!view_data) {
1083 g_object_unref (client);
1084 return;
1087 view_data_lock (view_data);
1089 if (!view_data->is_used) {
1090 view_data_unlock (view_data);
1091 view_data_unref (view_data);
1092 g_object_unref (client);
1093 return;
1096 to_expand_recurrences = view_data->to_expand_recurrences;
1097 view_data->to_expand_recurrences = NULL;
1099 view_data_unlock (view_data);
1101 for (link = to_expand_recurrences; link && view_data->is_used; link = g_slist_next (link)) {
1102 icalcomponent *icomp = link->data;
1103 GenerateInstancesData gid;
1105 if (!icomp)
1106 continue;
1108 gid.client = client;
1109 gid.pexpanded_recurrences = &expanded_recurrences;
1110 gid.zone = data_model->priv->zone;
1112 e_cal_client_generate_instances_for_object_sync (client, icomp, range_start, range_end,
1113 cal_data_model_instance_generated, &gid);
1116 g_slist_free_full (to_expand_recurrences, (GDestroyNotify) icalcomponent_free);
1118 view_data_lock (view_data);
1119 if (expanded_recurrences)
1120 view_data->expanded_recurrences = g_slist_concat (view_data->expanded_recurrences, expanded_recurrences);
1121 if (view_data->is_used) {
1122 NotifyRecurrencesData *notif_data;
1124 notif_data = g_new0 (NotifyRecurrencesData, 1);
1125 notif_data->data_model = g_object_ref (data_model);
1126 notif_data->client = g_object_ref (client);
1128 g_timeout_add (1, cal_data_model_notify_recurrences_cb, notif_data);
1131 view_data_unlock (view_data);
1132 view_data_unref (view_data);
1133 g_object_unref (client);
1136 static void
1137 cal_data_model_process_modified_or_added_objects (ECalClientView *view,
1138 const GSList *objects,
1139 ECalDataModel *data_model,
1140 gboolean is_add)
1142 ViewData *view_data;
1143 ECalClient *client;
1145 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
1147 LOCK_PROPS ();
1149 client = e_cal_client_view_ref_client (view);
1150 if (!client) {
1151 UNLOCK_PROPS ();
1152 return;
1155 view_data = g_hash_table_lookup (data_model->priv->views, client);
1156 if (view_data) {
1157 view_data_ref (view_data);
1158 g_warn_if_fail (view_data->view == view);
1161 UNLOCK_PROPS ();
1163 if (!view_data) {
1164 g_clear_object (&client);
1165 return;
1168 view_data_lock (view_data);
1170 if (view_data->is_used) {
1171 const GSList *link;
1172 GSList *to_expand_recurrences = NULL;
1174 if (!is_add) {
1175 /* Received a modify before the view was claimed as being complete,
1176 aka fully populated, thus drop any previously known components,
1177 because there is no hope for a merge. */
1178 if (view_data->lost_components) {
1179 cal_data_model_remove_components (data_model, client, view_data->lost_components, NULL);
1180 g_hash_table_destroy (view_data->lost_components);
1181 view_data->lost_components = NULL;
1185 cal_data_model_freeze_all_subscribers (data_model);
1187 for (link = objects; link; link = g_slist_next (link)) {
1188 icalcomponent *icomp = link->data;
1190 if (!icomp || !icalcomponent_get_uid (icomp))
1191 continue;
1193 if (data_model->priv->expand_recurrences &&
1194 !e_cal_util_component_is_instance (icomp) &&
1195 e_cal_util_component_has_recurrences (icomp)) {
1196 /* This component requires an expand of recurrences, which
1197 will be done in a dedicated thread, thus remember it */
1198 to_expand_recurrences = g_slist_prepend (to_expand_recurrences,
1199 icalcomponent_new_clone (icomp));
1200 } else {
1201 /* Single or detached instance, the simple case */
1202 ECalComponent *comp;
1203 ComponentData *comp_data;
1204 time_t instance_start, instance_end;
1206 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (icomp));
1207 if (!comp)
1208 continue;
1210 cal_comp_get_instance_times (client, icomp, data_model->priv->zone, &instance_start, NULL, &instance_end, NULL, NULL);
1212 if (instance_end > instance_start)
1213 instance_end--;
1215 comp_data = component_data_new (comp, instance_start, instance_end,
1216 e_cal_util_component_is_instance (icomp));
1218 cal_data_model_process_added_component (data_model, view_data, comp_data, NULL);
1220 g_object_unref (comp);
1224 cal_data_model_thaw_all_subscribers (data_model);
1226 if (to_expand_recurrences) {
1227 view_data_lock (view_data);
1228 view_data->to_expand_recurrences = g_slist_concat (
1229 view_data->to_expand_recurrences, to_expand_recurrences);
1230 g_atomic_int_inc (&view_data->pending_expand_recurrences);
1231 view_data_unlock (view_data);
1233 cal_data_model_submit_internal_thread_job (data_model,
1234 cal_data_model_expand_recurrences_thread, g_object_ref (client));
1238 view_data_unlock (view_data);
1239 view_data_unref (view_data);
1241 g_clear_object (&client);
1244 static void
1245 cal_data_model_view_objects_added (ECalClientView *view,
1246 const GSList *objects,
1247 ECalDataModel *data_model)
1249 cal_data_model_process_modified_or_added_objects (view, objects, data_model, TRUE);
1252 static void
1253 cal_data_model_view_objects_modified (ECalClientView *view,
1254 const GSList *objects,
1255 ECalDataModel *data_model)
1257 cal_data_model_process_modified_or_added_objects (view, objects, data_model, FALSE);
1260 static void
1261 cal_data_model_view_objects_removed (ECalClientView *view,
1262 const GSList *uids,
1263 ECalDataModel *data_model)
1265 ViewData *view_data;
1266 ECalClient *client;
1267 const GSList *link;
1269 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
1271 LOCK_PROPS ();
1273 client = e_cal_client_view_ref_client (view);
1274 if (!client) {
1275 UNLOCK_PROPS ();
1276 return;
1279 view_data = g_hash_table_lookup (data_model->priv->views, client);
1281 g_clear_object (&client);
1283 if (view_data) {
1284 view_data_ref (view_data);
1285 g_warn_if_fail (view_data->view == view);
1288 UNLOCK_PROPS ();
1290 if (!view_data)
1291 return;
1293 view_data_lock (view_data);
1294 if (view_data->is_used) {
1295 GHashTable *gathered_uids;
1296 GList *removed = NULL, *rlink;
1298 gathered_uids = g_hash_table_new (g_str_hash, g_str_equal);
1300 for (link = uids; link; link = g_slist_next (link)) {
1301 const ECalComponentId *id = link->data;
1303 if (id) {
1304 if (!id->rid || !*id->rid) {
1305 if (!g_hash_table_contains (gathered_uids, id->uid)) {
1306 GatherComponentsData gather_data;
1308 gather_data.uid = id->uid;
1309 gather_data.pcomponent_ids = &removed;
1310 gather_data.component_ids_hash = NULL;
1311 gather_data.copy_ids = TRUE;
1312 gather_data.all_instances = TRUE;
1314 g_hash_table_foreach (view_data->components,
1315 cal_data_model_gather_components, &gather_data);
1316 if (view_data->lost_components)
1317 g_hash_table_foreach (view_data->lost_components,
1318 cal_data_model_gather_components, &gather_data);
1320 g_hash_table_insert (gathered_uids, id->uid, GINT_TO_POINTER (1));
1322 } else {
1323 removed = g_list_prepend (removed, e_cal_component_id_copy (id));
1328 cal_data_model_freeze_all_subscribers (data_model);
1330 for (rlink = removed; rlink; rlink = g_list_next (rlink)) {
1331 ECalComponentId *id = rlink->data;
1333 if (id) {
1334 ComponentData *comp_data;
1335 time_t instance_start = (time_t) 0, instance_end = (time_t) 0;
1337 /* Try to limit which subscribers will be notified about removal */
1338 comp_data = g_hash_table_lookup (view_data->components, id);
1339 if (comp_data) {
1340 instance_start = comp_data->instance_start;
1341 instance_end = comp_data->instance_end;
1342 } else if (view_data->lost_components) {
1343 comp_data = g_hash_table_lookup (view_data->lost_components, id);
1344 if (comp_data) {
1345 instance_start = comp_data->instance_start;
1346 instance_end = comp_data->instance_end;
1350 g_hash_table_remove (view_data->components, id);
1351 if (view_data->lost_components)
1352 g_hash_table_remove (view_data->lost_components, id);
1354 cal_data_model_foreach_subscriber_in_range (data_model, view_data->client,
1355 instance_start, instance_end,
1356 cal_data_model_remove_one_view_component_cb, id);
1360 cal_data_model_thaw_all_subscribers (data_model);
1362 g_list_free_full (removed, (GDestroyNotify) e_cal_component_free_id);
1363 g_hash_table_destroy (gathered_uids);
1365 view_data_unlock (view_data);
1366 view_data_unref (view_data);
1369 static void
1370 cal_data_model_view_progress (ECalClientView *view,
1371 guint percent,
1372 const gchar *message,
1373 ECalDataModel *data_model)
1375 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
1377 cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_PROGRESS, percent, message, NULL);
1380 static void
1381 cal_data_model_view_complete (ECalClientView *view,
1382 const GError *error,
1383 ECalDataModel *data_model)
1385 ViewData *view_data;
1386 ECalClient *client;
1388 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
1390 LOCK_PROPS ();
1392 client = e_cal_client_view_ref_client (view);
1393 if (!client) {
1394 UNLOCK_PROPS ();
1395 return;
1398 view_data = g_hash_table_lookup (data_model->priv->views, client);
1400 g_clear_object (&client);
1402 if (view_data) {
1403 view_data_ref (view_data);
1404 g_warn_if_fail (view_data->view == view);
1407 UNLOCK_PROPS ();
1409 if (!view_data)
1410 return;
1412 view_data_lock (view_data);
1414 view_data->received_complete = TRUE;
1415 if (view_data->is_used &&
1416 view_data->lost_components &&
1417 !view_data->pending_expand_recurrences) {
1418 cal_data_model_remove_components (data_model, view_data->client, view_data->lost_components, NULL);
1419 g_hash_table_destroy (view_data->lost_components);
1420 view_data->lost_components = NULL;
1423 cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_COMPLETE, 0, NULL, error);
1425 view_data_unlock (view_data);
1426 view_data_unref (view_data);
1429 typedef struct _CreateViewData {
1430 ECalDataModel *data_model;
1431 ECalClient *client;
1432 } CreateViewData;
1434 static void
1435 create_view_data_free (gpointer ptr)
1437 CreateViewData *cv_data = ptr;
1439 if (cv_data) {
1440 g_clear_object (&cv_data->data_model);
1441 g_clear_object (&cv_data->client);
1442 g_free (cv_data);
1446 static void
1447 cal_data_model_create_view_thread (EAlertSinkThreadJobData *job_data,
1448 gpointer user_data,
1449 GCancellable *cancellable,
1450 GError **error)
1452 CreateViewData *cv_data = user_data;
1453 ViewData *view_data;
1454 ECalDataModel *data_model;
1455 ECalClient *client;
1456 ECalClientView *view;
1457 gchar *filter;
1459 g_return_if_fail (cv_data != NULL);
1461 data_model = cv_data->data_model;
1462 client = cv_data->client;
1463 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
1464 g_return_if_fail (E_IS_CAL_CLIENT (client));
1466 LOCK_PROPS ();
1468 if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
1469 UNLOCK_PROPS ();
1470 return;
1473 view_data = g_hash_table_lookup (data_model->priv->views, client);
1474 if (!view_data) {
1475 UNLOCK_PROPS ();
1476 g_warn_if_reached ();
1477 return;
1480 filter = g_strdup (data_model->priv->full_filter);
1482 view_data_ref (view_data);
1483 UNLOCK_PROPS ();
1485 view_data_lock (view_data);
1486 g_warn_if_fail (view_data->view == NULL);
1488 if (!e_cal_client_get_view_sync (client, filter, &view_data->view, cancellable, error)) {
1489 view_data_unlock (view_data);
1490 view_data_unref (view_data);
1491 g_free (filter);
1492 return;
1495 g_warn_if_fail (view_data->view != NULL);
1497 view_data->objects_added_id = g_signal_connect (view_data->view, "objects-added",
1498 G_CALLBACK (cal_data_model_view_objects_added), data_model);
1499 view_data->objects_modified_id = g_signal_connect (view_data->view, "objects-modified",
1500 G_CALLBACK (cal_data_model_view_objects_modified), data_model);
1501 view_data->objects_removed_id = g_signal_connect (view_data->view, "objects-removed",
1502 G_CALLBACK (cal_data_model_view_objects_removed), data_model);
1503 view_data->progress_id = g_signal_connect (view_data->view, "progress",
1504 G_CALLBACK (cal_data_model_view_progress), data_model);
1505 view_data->complete_id = g_signal_connect (view_data->view, "complete",
1506 G_CALLBACK (cal_data_model_view_complete), data_model);
1508 view = g_object_ref (view_data->view);
1510 view_data_unlock (view_data);
1511 view_data_unref (view_data);
1513 g_free (filter);
1515 if (!g_cancellable_is_cancelled (cancellable)) {
1516 cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_START, 0, NULL, NULL);
1517 e_cal_client_view_start (view, error);
1520 g_clear_object (&view);
1523 typedef struct _NotifyRemoveComponentsData {
1524 ECalDataModel *data_model;
1525 ECalClient *client;
1526 } NotifyRemoveComponentsData;
1528 static void
1529 cal_data_model_notify_remove_components_cb (gpointer key,
1530 gpointer value,
1531 gpointer user_data)
1533 ECalComponentId *id = key;
1534 ComponentData *comp_data = value;
1535 NotifyRemoveComponentsData *nrc_data = user_data;
1537 g_return_if_fail (id != NULL);
1538 g_return_if_fail (comp_data != NULL);
1539 g_return_if_fail (nrc_data != NULL);
1541 cal_data_model_foreach_subscriber_in_range (nrc_data->data_model, nrc_data->client,
1542 comp_data->instance_start, comp_data->instance_end,
1543 cal_data_model_remove_one_view_component_cb, id);
1546 static void
1547 cal_data_model_update_client_view (ECalDataModel *data_model,
1548 ECalClient *client)
1550 ESource *source;
1551 ViewData *view_data;
1552 CreateViewData *cv_data;
1553 const gchar *alert_ident = NULL;
1554 gchar *description = NULL;
1556 LOCK_PROPS ();
1558 view_data = g_hash_table_lookup (data_model->priv->views, client);
1559 if (!view_data) {
1560 view_data = view_data_new (client);
1561 g_hash_table_insert (data_model->priv->views, client, view_data);
1564 view_data_lock (view_data);
1566 if (view_data->cancellable)
1567 g_cancellable_cancel (view_data->cancellable);
1568 g_clear_object (&view_data->cancellable);
1570 if (view_data->view) {
1571 view_data_disconnect_view (view_data);
1572 cal_data_model_emit_view_state_changed (data_model, view_data->view, E_CAL_DATA_MODEL_VIEW_STATE_STOP, 0, NULL, NULL);
1573 g_clear_object (&view_data->view);
1576 if (!view_data->received_complete) {
1577 NotifyRemoveComponentsData nrc_data;
1579 nrc_data.data_model = data_model;
1580 nrc_data.client = client;
1582 cal_data_model_freeze_all_subscribers (data_model);
1584 g_hash_table_foreach (view_data->components,
1585 cal_data_model_notify_remove_components_cb, &nrc_data);
1587 g_hash_table_remove_all (view_data->components);
1588 if (view_data->lost_components) {
1589 g_hash_table_foreach (view_data->lost_components,
1590 cal_data_model_notify_remove_components_cb, &nrc_data);
1592 g_hash_table_destroy (view_data->lost_components);
1593 view_data->lost_components = NULL;
1596 cal_data_model_thaw_all_subscribers (data_model);
1597 } else {
1598 if (view_data->lost_components) {
1599 NotifyRemoveComponentsData nrc_data;
1601 nrc_data.data_model = data_model;
1602 nrc_data.client = client;
1604 cal_data_model_freeze_all_subscribers (data_model);
1605 g_hash_table_foreach (view_data->lost_components,
1606 cal_data_model_notify_remove_components_cb, &nrc_data);
1607 cal_data_model_thaw_all_subscribers (data_model);
1609 g_hash_table_destroy (view_data->lost_components);
1610 view_data->lost_components = NULL;
1613 view_data->lost_components = view_data->components;
1614 view_data->components = g_hash_table_new_full (
1615 (GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal,
1616 (GDestroyNotify) e_cal_component_free_id, component_data_free);
1619 view_data_unlock (view_data);
1621 if (!data_model->priv->full_filter) {
1622 UNLOCK_PROPS ();
1623 return;
1626 source = e_client_get_source (E_CLIENT (client));
1628 switch (e_cal_client_get_source_type (client)) {
1629 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1630 alert_ident = "calendar:failed-create-view-calendar";
1631 description = g_strdup_printf (_("Creating view for calendar “%s”"), e_source_get_display_name (source));
1632 break;
1633 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1634 alert_ident = "calendar:failed-create-view-tasks";
1635 description = g_strdup_printf (_("Creating view for task list “%s”"), e_source_get_display_name (source));
1636 break;
1637 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1638 alert_ident = "calendar:failed-create-view-memos";
1639 description = g_strdup_printf (_("Creating view for memo list “%s”"), e_source_get_display_name (source));
1640 break;
1641 case E_CAL_CLIENT_SOURCE_TYPE_LAST:
1642 g_warn_if_reached ();
1643 UNLOCK_PROPS ();
1644 return;
1647 cv_data = g_new0 (CreateViewData, 1);
1648 cv_data->data_model = g_object_ref (data_model);
1649 cv_data->client = g_object_ref (client);
1651 view_data->received_complete = FALSE;
1652 view_data->cancellable = e_cal_data_model_submit_thread_job (data_model,
1653 description, alert_ident, e_source_get_display_name (source),
1654 cal_data_model_create_view_thread, cv_data, create_view_data_free);
1656 g_free (description);
1658 UNLOCK_PROPS ();
1661 static void
1662 cal_data_model_remove_client_view (ECalDataModel *data_model,
1663 ECalClient *client)
1665 ViewData *view_data;
1667 LOCK_PROPS ();
1669 view_data = g_hash_table_lookup (data_model->priv->views, client);
1671 if (view_data) {
1672 NotifyRemoveComponentsData nrc_data;
1674 view_data_lock (view_data);
1676 nrc_data.data_model = data_model;
1677 nrc_data.client = client;
1679 cal_data_model_freeze_all_subscribers (data_model);
1681 g_hash_table_foreach (view_data->components,
1682 cal_data_model_notify_remove_components_cb, &nrc_data);
1683 g_hash_table_remove_all (view_data->components);
1685 if (view_data->lost_components) {
1686 g_hash_table_foreach (view_data->lost_components,
1687 cal_data_model_notify_remove_components_cb, &nrc_data);
1688 g_hash_table_remove_all (view_data->lost_components);
1691 cal_data_model_thaw_all_subscribers (data_model);
1693 if (view_data->view)
1694 cal_data_model_emit_view_state_changed (data_model, view_data->view, E_CAL_DATA_MODEL_VIEW_STATE_STOP, 0, NULL, NULL);
1696 view_data->is_used = FALSE;
1697 view_data_unlock (view_data);
1699 g_hash_table_remove (data_model->priv->views, client);
1702 UNLOCK_PROPS ();
1705 static gboolean
1706 cal_data_model_add_to_subscriber (ECalDataModel *data_model,
1707 ECalClient *client,
1708 const ECalComponentId *id,
1709 ECalComponent *component,
1710 time_t instance_start,
1711 time_t instance_end,
1712 gpointer user_data)
1714 ECalDataModelSubscriber *subscriber = user_data;
1716 g_return_val_if_fail (subscriber != NULL, FALSE);
1717 g_return_val_if_fail (id != NULL, FALSE);
1719 e_cal_data_model_subscriber_component_added (subscriber, client, component);
1721 return TRUE;
1724 static gboolean
1725 cal_data_model_add_to_subscriber_except_its_range (ECalDataModel *data_model,
1726 ECalClient *client,
1727 const ECalComponentId *id,
1728 ECalComponent *component,
1729 time_t instance_start,
1730 time_t instance_end,
1731 gpointer user_data)
1733 SubscriberData *subs_data = user_data;
1735 g_return_val_if_fail (subs_data != NULL, FALSE);
1736 g_return_val_if_fail (id != NULL, FALSE);
1738 /* subs_data should have set the old time range, which
1739 means only components which didn't fit into the old
1740 time range will be added */
1741 if (!(instance_start <= subs_data->range_end &&
1742 instance_end >= subs_data->range_start))
1743 e_cal_data_model_subscriber_component_added (subs_data->subscriber, client, component);
1745 return TRUE;
1748 static gboolean
1749 cal_data_model_remove_from_subscriber_except_its_range (ECalDataModel *data_model,
1750 ECalClient *client,
1751 const ECalComponentId *id,
1752 ECalComponent *component,
1753 time_t instance_start,
1754 time_t instance_end,
1755 gpointer user_data)
1757 SubscriberData *subs_data = user_data;
1759 g_return_val_if_fail (subs_data != NULL, FALSE);
1760 g_return_val_if_fail (id != NULL, FALSE);
1762 /* subs_data should have set the new time range, which
1763 means only components which don't fit into this new
1764 time range will be removed */
1765 if (!(instance_start <= subs_data->range_end &&
1766 instance_end >= subs_data->range_start))
1767 e_cal_data_model_subscriber_component_removed (subs_data->subscriber, client, id->uid, id->rid);
1769 return TRUE;
1772 static void
1773 cal_data_model_set_client_default_zone_cb (gpointer key,
1774 gpointer value,
1775 gpointer user_data)
1777 ECalClient *client = value;
1778 icaltimezone *zone = user_data;
1780 g_return_if_fail (E_IS_CAL_CLIENT (client));
1781 g_return_if_fail (zone != NULL);
1783 e_cal_client_set_default_timezone (client, zone);
1786 static void
1787 cal_data_model_rebuild_everything (ECalDataModel *data_model,
1788 gboolean complete_rebuild)
1790 GHashTableIter iter;
1791 gpointer key, value;
1793 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
1795 LOCK_PROPS ();
1797 if (data_model->priv->views_update_freeze > 0) {
1798 data_model->priv->views_update_required = TRUE;
1799 UNLOCK_PROPS ();
1800 return;
1803 data_model->priv->views_update_required = FALSE;
1805 g_hash_table_iter_init (&iter, data_model->priv->clients);
1806 while (g_hash_table_iter_next (&iter, &key, &value)) {
1807 ECalClient *client = value;
1809 if (complete_rebuild)
1810 cal_data_model_remove_client_view (data_model, client);
1811 cal_data_model_update_client_view (data_model, client);
1814 UNLOCK_PROPS ();
1817 static void
1818 cal_data_model_update_time_range (ECalDataModel *data_model)
1820 time_t range_start, range_end;
1822 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
1824 LOCK_PROPS ();
1826 if (data_model->priv->disposing) {
1827 UNLOCK_PROPS ();
1829 return;
1832 range_start = data_model->priv->range_start;
1833 range_end = data_model->priv->range_end;
1835 cal_data_model_calc_range (data_model, &range_start, &range_end);
1837 if (data_model->priv->range_start != range_start ||
1838 data_model->priv->range_end != range_end) {
1839 data_model->priv->range_start = range_start;
1840 data_model->priv->range_end = range_end;
1842 if (cal_data_model_update_full_filter (data_model))
1843 cal_data_model_rebuild_everything (data_model, FALSE);
1846 UNLOCK_PROPS ();
1849 static void
1850 cal_data_model_set_property (GObject *object,
1851 guint property_id,
1852 const GValue *value,
1853 GParamSpec *pspec)
1855 switch (property_id) {
1856 case PROP_EXPAND_RECURRENCES:
1857 e_cal_data_model_set_expand_recurrences (
1858 E_CAL_DATA_MODEL (object),
1859 g_value_get_boolean (value));
1860 return;
1862 case PROP_TIMEZONE:
1863 e_cal_data_model_set_timezone (
1864 E_CAL_DATA_MODEL (object),
1865 g_value_get_pointer (value));
1866 return;
1869 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1872 static void
1873 cal_data_model_get_property (GObject *object,
1874 guint property_id,
1875 GValue *value,
1876 GParamSpec *pspec)
1878 switch (property_id) {
1879 case PROP_EXPAND_RECURRENCES:
1880 g_value_set_boolean (
1881 value,
1882 e_cal_data_model_get_expand_recurrences (
1883 E_CAL_DATA_MODEL (object)));
1884 return;
1886 case PROP_TIMEZONE:
1887 g_value_set_pointer (
1888 value,
1889 e_cal_data_model_get_timezone (
1890 E_CAL_DATA_MODEL (object)));
1891 return;
1894 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1897 static void
1898 cal_data_model_dispose (GObject *object)
1900 ECalDataModel *data_model = E_CAL_DATA_MODEL (object);
1902 data_model->priv->disposing = TRUE;
1904 /* Chain up to parent's method. */
1905 G_OBJECT_CLASS (e_cal_data_model_parent_class)->dispose (object);
1908 static void
1909 cal_data_model_finalize (GObject *object)
1911 ECalDataModel *data_model = E_CAL_DATA_MODEL (object);
1913 g_thread_pool_free (data_model->priv->thread_pool, TRUE, FALSE);
1914 g_hash_table_destroy (data_model->priv->clients);
1915 g_hash_table_destroy (data_model->priv->views);
1916 g_slist_free_full (data_model->priv->subscribers, subscriber_data_free);
1917 g_free (data_model->priv->filter);
1918 g_free (data_model->priv->full_filter);
1920 e_weak_ref_free (data_model->priv->submit_thread_job_responder);
1921 g_rec_mutex_clear (&data_model->priv->props_lock);
1923 /* Chain up to parent's method. */
1924 G_OBJECT_CLASS (e_cal_data_model_parent_class)->finalize (object);
1927 static void
1928 e_cal_data_model_class_init (ECalDataModelClass *class)
1930 GObjectClass *object_class;
1932 g_type_class_add_private (class, sizeof (ECalDataModelPrivate));
1934 object_class = G_OBJECT_CLASS (class);
1935 object_class->set_property = cal_data_model_set_property;
1936 object_class->get_property = cal_data_model_get_property;
1937 object_class->dispose = cal_data_model_dispose;
1938 object_class->finalize = cal_data_model_finalize;
1940 g_object_class_install_property (
1941 object_class,
1942 PROP_EXPAND_RECURRENCES,
1943 g_param_spec_boolean (
1944 "expand-recurrences",
1945 "Expand Recurrences",
1946 NULL,
1947 FALSE,
1948 G_PARAM_READWRITE));
1950 g_object_class_install_property (
1951 object_class,
1952 PROP_TIMEZONE,
1953 g_param_spec_pointer (
1954 "timezone",
1955 "Time Zone",
1956 NULL,
1957 G_PARAM_READWRITE));
1959 signals[VIEW_STATE_CHANGED] = g_signal_new (
1960 "view-state-changed",
1961 G_TYPE_FROM_CLASS (class),
1962 G_SIGNAL_RUN_LAST,
1963 G_STRUCT_OFFSET (ECalDataModelClass, view_state_changed),
1964 NULL, NULL,
1965 NULL,
1966 G_TYPE_NONE, 5, E_TYPE_CAL_CLIENT_VIEW, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_ERROR);
1969 static void
1970 e_cal_data_model_init (ECalDataModel *data_model)
1972 data_model->priv = G_TYPE_INSTANCE_GET_PRIVATE (data_model, E_TYPE_CAL_DATA_MODEL, ECalDataModelPrivate);
1974 /* Suppose the data_model is always created in the main/UI thread */
1975 data_model->priv->main_thread = g_thread_self ();
1976 data_model->priv->thread_pool = g_thread_pool_new (
1977 cal_data_model_internal_thread_job_func, data_model, 5, FALSE, NULL);
1979 data_model->priv->clients = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
1980 data_model->priv->views = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, view_data_unref);
1981 data_model->priv->subscribers = NULL;
1983 data_model->priv->disposing = FALSE;
1984 data_model->priv->expand_recurrences = FALSE;
1985 data_model->priv->zone = icaltimezone_get_utc_timezone ();
1987 data_model->priv->views_update_freeze = 0;
1988 data_model->priv->views_update_required = FALSE;
1990 g_rec_mutex_init (&data_model->priv->props_lock);
1994 * e_cal_data_model_new:
1995 * @func: a function to be called when the data model needs to create
1996 * a thread job within UI
1997 * @func_responder: (allow none): a responder for @func, which is passed
1998 * as the first paramter; can be #NULL
2000 * Creates a new instance of #ECalDataModel. The @func is mandatory, because
2001 * it is used to create new thread jobs with UI feedback.
2003 * Returns: (transfer full): A new #ECalDataModel instance
2005 * Since: 3.16
2007 ECalDataModel *
2008 e_cal_data_model_new (ECalDataModelSubmitThreadJobFunc func,
2009 GObject *func_responder)
2011 ECalDataModel *data_model;
2013 g_return_val_if_fail (func != NULL, NULL);
2015 data_model = g_object_new (E_TYPE_CAL_DATA_MODEL, NULL);
2016 data_model->priv->submit_thread_job_func = func;
2017 data_model->priv->submit_thread_job_responder = e_weak_ref_new (func_responder);
2019 return data_model;
2023 * e_cal_data_model_new_clone:
2024 * @src_data_model: an #ECalDataModel to clone
2026 * Creates a clone of @src_data_model, which means a copy with the same clients, filter and
2027 * other properties set (not the subscribers).
2029 * Returns: (transfer full): A new #ECalDataModel instance deriving settings from @src_data_model
2031 * Since: 3.16
2033 ECalDataModel *
2034 e_cal_data_model_new_clone (ECalDataModel *src_data_model)
2036 ECalDataModel *clone;
2037 GObject *func_responder;
2038 GList *clients, *link;
2040 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (src_data_model), NULL);
2042 func_responder = g_weak_ref_get (src_data_model->priv->submit_thread_job_responder);
2043 g_return_val_if_fail (func_responder != NULL, NULL);
2045 clone = e_cal_data_model_new (src_data_model->priv->submit_thread_job_func, func_responder);
2047 g_clear_object (&func_responder);
2049 e_cal_data_model_set_expand_recurrences (clone, e_cal_data_model_get_expand_recurrences (src_data_model));
2050 e_cal_data_model_set_timezone (clone, e_cal_data_model_get_timezone (src_data_model));
2051 e_cal_data_model_set_filter (clone, src_data_model->priv->filter);
2053 clients = e_cal_data_model_get_clients (src_data_model);
2054 for (link = clients; link; link = g_list_next (link)) {
2055 ECalClient *client = link->data;
2057 e_cal_data_model_add_client (clone, client);
2060 g_list_free_full (clients, g_object_unref);
2062 return clone;
2066 * e_cal_data_model_get_disposing:
2067 * @data_model: an #EDataModel instance
2069 * Obtains whether the @data_model is disposing and will be freed (soon).
2071 * Returns: Whether the @data_model is disposing.
2073 * Since: 3.16
2075 gboolean
2076 e_cal_data_model_get_disposing (ECalDataModel *data_model)
2078 gboolean disposing;
2080 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
2082 LOCK_PROPS ();
2084 disposing = data_model->priv->disposing;
2086 UNLOCK_PROPS ();
2088 return disposing;
2092 * e_cal_data_model_set_disposing:
2093 * @data_model: an #EDataModel instance
2094 * @disposing: whether the object is disposing
2096 * Sets whether the @data_model is disposing itself (soon).
2097 * If set to %TRUE, then no updates are done on changes
2098 * which would otherwise trigger view and subscriber updates.
2100 * Since: 3.16
2102 void
2103 e_cal_data_model_set_disposing (ECalDataModel *data_model,
2104 gboolean disposing)
2106 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2108 LOCK_PROPS ();
2110 if ((data_model->priv->disposing ? 1 : 0) == (disposing ? 1 : 0)) {
2111 UNLOCK_PROPS ();
2112 return;
2115 data_model->priv->disposing = disposing;
2117 UNLOCK_PROPS ();
2121 * e_cal_data_model_get_expand_recurrences:
2122 * @data_model: an #EDataModel instance
2124 * Obtains whether the @data_model expands recurrences of recurring
2125 * components by default. The default value is #FALSE, to not expand
2126 * recurrences.
2128 * Returns: Whether the @data_model expands recurrences of recurring
2129 * components.
2131 * Since: 3.16
2133 gboolean
2134 e_cal_data_model_get_expand_recurrences (ECalDataModel *data_model)
2136 gboolean expand_recurrences;
2138 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
2140 LOCK_PROPS ();
2142 expand_recurrences = data_model->priv->expand_recurrences;
2144 UNLOCK_PROPS ();
2146 return expand_recurrences;
2150 * e_cal_data_model_set_expand_recurrences:
2151 * @data_model: an #EDataModel instance
2152 * @expand_recurrences: whether to expand recurrences
2154 * Sets whether the @data_model should expand recurrences of recurring
2155 * components by default.
2157 * Since: 3.16
2159 void
2160 e_cal_data_model_set_expand_recurrences (ECalDataModel *data_model,
2161 gboolean expand_recurrences)
2163 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2165 LOCK_PROPS ();
2167 if ((data_model->priv->expand_recurrences ? 1 : 0) == (expand_recurrences ? 1 : 0)) {
2168 UNLOCK_PROPS ();
2169 return;
2172 data_model->priv->expand_recurrences = expand_recurrences;
2174 cal_data_model_rebuild_everything (data_model, TRUE);
2176 UNLOCK_PROPS ();
2180 * e_cal_data_model_get_timezone:
2181 * @data_model: an #EDataModel instance
2183 * Obtains a timezone being used for calendar views. The returned
2184 * timezone is owned by the @data_model.
2186 * Returns: (transfer none): An #icaltimezone being used for calendar views.
2188 * Since: 3.16
2190 icaltimezone *
2191 e_cal_data_model_get_timezone (ECalDataModel *data_model)
2193 icaltimezone *zone;
2195 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
2197 LOCK_PROPS ();
2199 zone = data_model->priv->zone;
2201 UNLOCK_PROPS ();
2203 return zone;
2206 * e_cal_data_model_set_timezone:
2207 * @data_model: an #EDataModel instance
2208 * @zone: an #icaltimezone
2210 * Sets a trimezone to be used for calendar views. This change
2211 * regenerates all views.
2213 * Since: 3.16
2215 void
2216 e_cal_data_model_set_timezone (ECalDataModel *data_model,
2217 icaltimezone *zone)
2219 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2220 g_return_if_fail (zone != NULL);
2222 LOCK_PROPS ();
2224 if (data_model->priv->zone != zone) {
2225 data_model->priv->zone = zone;
2227 g_hash_table_foreach (data_model->priv->clients, cal_data_model_set_client_default_zone_cb, zone);
2229 if (cal_data_model_update_full_filter (data_model))
2230 cal_data_model_rebuild_everything (data_model, TRUE);
2233 UNLOCK_PROPS ();
2237 * e_cal_data_model_set_filter:
2238 * @data_model: an #EDataModel instance
2239 * @sexp: an expression defining a filter
2241 * Sets an additional filter for the views. The filter should not
2242 * contain time constraints, these are meant to be defined by
2243 * subscribers.
2245 * Since: 3.16
2247 void
2248 e_cal_data_model_set_filter (ECalDataModel *data_model,
2249 const gchar *sexp)
2251 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2252 g_return_if_fail (sexp != NULL);
2254 LOCK_PROPS ();
2256 if (sexp && !*sexp)
2257 sexp = NULL;
2259 if (g_strcmp0 (data_model->priv->filter, sexp) != 0) {
2260 g_free (data_model->priv->filter);
2261 data_model->priv->filter = g_strdup (sexp);
2263 if (cal_data_model_update_full_filter (data_model))
2264 cal_data_model_rebuild_everything (data_model, TRUE);
2267 UNLOCK_PROPS ();
2271 * e_cal_data_model_dup_filter:
2272 * @data_model: an #EDataModel instance
2274 * Obtains currently used filter (an expression) for the views.
2276 * Returns: (transfer full): A copy of the currently used
2277 * filter for views. Free it with g_free() when done with it.
2278 * Returns #NULL when there is no extra filter set.
2280 * Since: 3.16
2282 gchar *
2283 e_cal_data_model_dup_filter (ECalDataModel *data_model)
2285 gchar *filter;
2287 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
2289 LOCK_PROPS ();
2291 filter = g_strdup (data_model->priv->filter);
2293 UNLOCK_PROPS ();
2295 return filter;
2299 * e_cal_data_model_add_client:
2300 * @data_model: an #EDataModel instance
2301 * @client: an #ECalClient
2303 * Adds a new @client into the set of clients which should be used
2304 * to populate data for subscribers. Adding the same client multiple
2305 * times does nothing.
2307 * Since: 3.16
2309 void
2310 e_cal_data_model_add_client (ECalDataModel *data_model,
2311 ECalClient *client)
2313 ESource *source;
2315 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2316 g_return_if_fail (E_IS_CAL_CLIENT (client));
2318 source = e_client_get_source (E_CLIENT (client));
2319 g_return_if_fail (E_IS_SOURCE (source));
2320 g_return_if_fail (e_source_get_uid (source) != NULL);
2322 LOCK_PROPS ();
2324 if (g_hash_table_contains (data_model->priv->clients, e_source_get_uid (source))) {
2325 UNLOCK_PROPS ();
2326 return;
2329 g_hash_table_insert (data_model->priv->clients, e_source_dup_uid (source), g_object_ref (client));
2331 e_cal_client_set_default_timezone (client, data_model->priv->zone);
2333 cal_data_model_update_client_view (data_model, client);
2335 UNLOCK_PROPS ();
2339 * e_cal_data_model_remove_client:
2340 * @uid: a UID of a client to remove
2342 * Removes a client identified by @uid from a set of clients
2343 * which populate the data for subscribers. Removing the client
2344 * which is not used in the @data_model does nothing.
2346 * Since: 3.16
2348 void
2349 e_cal_data_model_remove_client (ECalDataModel *data_model,
2350 const gchar *uid)
2352 ECalClient *client;
2354 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2355 g_return_if_fail (uid != NULL);
2357 LOCK_PROPS ();
2359 client = g_hash_table_lookup (data_model->priv->clients, uid);
2360 if (!client) {
2361 UNLOCK_PROPS ();
2362 return;
2365 cal_data_model_remove_client_view (data_model, client);
2366 g_hash_table_remove (data_model->priv->clients, uid);
2368 UNLOCK_PROPS ();
2372 * e_cal_data_model_ref_client:
2373 * @data_model: an #EDataModel instance
2374 * @uid: a UID of a client to return
2376 * Obtains an #ECalClient with given @uid from the set of clients
2377 * being used by the @data_modal. Returns #NULL, if no such client
2378 * is used by the @data_model.
2380 * Returns: (tranfer full): An #ECalClient with given @uid being
2381 * used by @data_model, or NULL, when no such is used by
2382 * the @data_model. Unref returned (non-NULL) client with
2383 * g_object_unref() when done with it.
2385 * Since: 3.16
2387 ECalClient *
2388 e_cal_data_model_ref_client (ECalDataModel *data_model,
2389 const gchar *uid)
2391 ECalClient *client;
2393 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
2395 LOCK_PROPS ();
2397 client = g_hash_table_lookup (data_model->priv->clients, uid);
2398 if (client)
2399 g_object_ref (client);
2401 UNLOCK_PROPS ();
2403 return client;
2407 * e_cal_data_model_get_clients:
2408 * @data_model: an #EDataModel instance
2410 * Obtains a list of all clients being used by the @data_model.
2411 * Each client in the returned list is referenced and the list
2412 * itself is also newly allocated, thus free it with
2413 * g_list_free_full (list, g_object_unref); when done with it.
2415 * Returns: (transfer full): A list of currently used #ECalClient-s.
2417 * Since: 3.16
2419 GList *
2420 e_cal_data_model_get_clients (ECalDataModel *data_model)
2422 GList *clients;
2424 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
2426 LOCK_PROPS ();
2428 clients = g_hash_table_get_values (data_model->priv->clients);
2429 g_list_foreach (clients, (GFunc) g_object_ref, NULL);
2431 UNLOCK_PROPS ();
2433 return clients;
2436 static gboolean
2437 cal_data_model_prepend_component (ECalDataModel *data_model,
2438 ECalClient *client,
2439 const ECalComponentId *id,
2440 ECalComponent *comp,
2441 time_t instance_start,
2442 time_t instance_end,
2443 gpointer user_data)
2445 GSList **components = user_data;
2447 g_return_val_if_fail (components != NULL, FALSE);
2448 g_return_val_if_fail (comp != NULL, FALSE);
2450 *components = g_slist_prepend (*components, g_object_ref (comp));
2452 return TRUE;
2456 * e_cal_data_model_get_components:
2457 * @data_model: an #EDataModel instance
2458 * @in_range_start: Start of the time range
2459 * @in_range_end: End of the time range
2461 * Obtains a list of components from the given time range. The time range is
2462 * clamp by the actual time range defined by subscribers (if there is no
2463 * subscriber, or all subscribers define times out of the given time range,
2464 * then no components are returned).
2466 * Returns: (transfer full): A #GSList of #ECalComponent-s known for the given
2467 * time range in the time of the call. The #GSList, togher with the components,
2468 * is owned by the caller, which should free it with
2469 * g_slist_free_full (list, g_object_unref); when done with it.
2471 * Note: A special case when both @in_range_start and @in_range_end are zero
2472 * is treated as a request for all known components.
2474 * Since: 3.16
2476 GSList *
2477 e_cal_data_model_get_components (ECalDataModel *data_model,
2478 time_t in_range_start,
2479 time_t in_range_end)
2481 GSList *components = NULL;
2483 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), NULL);
2485 e_cal_data_model_foreach_component (data_model, in_range_start, in_range_end,
2486 cal_data_model_prepend_component, &components);
2488 return g_slist_reverse (components);
2491 static gboolean
2492 cal_data_model_foreach_component (ECalDataModel *data_model,
2493 time_t in_range_start,
2494 time_t in_range_end,
2495 ECalDataModelForeachFunc func,
2496 gpointer user_data,
2497 gboolean include_lost_components)
2499 GHashTableIter viter;
2500 gpointer key, value;
2501 gboolean checked_all = TRUE;
2503 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
2504 g_return_val_if_fail (func != NULL, FALSE);
2506 LOCK_PROPS ();
2508 /* Is the given time range in the currently used time range? */
2509 if (!(in_range_start == in_range_end && in_range_start == (time_t) 0) &&
2510 (in_range_start >= data_model->priv->range_end ||
2511 in_range_end <= data_model->priv->range_start)) {
2512 UNLOCK_PROPS ();
2513 return checked_all;
2516 g_hash_table_iter_init (&viter, data_model->priv->views);
2517 while (checked_all && g_hash_table_iter_next (&viter, &key, &value)) {
2518 ViewData *view_data = value;
2519 GHashTableIter citer;
2521 if (!view_data)
2522 continue;
2524 view_data_lock (view_data);
2526 g_hash_table_iter_init (&citer, view_data->components);
2527 while (checked_all && g_hash_table_iter_next (&citer, &key, &value)) {
2528 ECalComponentId *id = key;
2529 ComponentData *comp_data = value;
2531 if (!comp_data)
2532 continue;
2534 if ((in_range_start == in_range_end && in_range_start == (time_t) 0) ||
2535 (comp_data->instance_start < in_range_end && comp_data->instance_end > in_range_start) ||
2536 (comp_data->instance_start == comp_data->instance_end && comp_data->instance_end == in_range_start)) {
2537 if (!func (data_model, view_data->client, id, comp_data->component,
2538 comp_data->instance_start, comp_data->instance_end, user_data))
2539 checked_all = FALSE;
2543 if (include_lost_components && view_data->lost_components) {
2544 g_hash_table_iter_init (&citer, view_data->lost_components);
2545 while (checked_all && g_hash_table_iter_next (&citer, &key, &value)) {
2546 ECalComponentId *id = key;
2547 ComponentData *comp_data = value;
2549 if (!comp_data)
2550 continue;
2552 if ((in_range_start == in_range_end && in_range_start == (time_t) 0) ||
2553 (comp_data->instance_start < in_range_end && comp_data->instance_end > in_range_start) ||
2554 (comp_data->instance_start == comp_data->instance_end && comp_data->instance_end == in_range_start)) {
2555 if (!func (data_model, view_data->client, id, comp_data->component,
2556 comp_data->instance_start, comp_data->instance_end, user_data))
2557 checked_all = FALSE;
2562 view_data_unlock (view_data);
2565 UNLOCK_PROPS ();
2567 return checked_all;
2571 * e_cal_data_model_foreach_component:
2572 * @data_model: an #EDataModel instance
2573 * @in_range_start: Start of the time range
2574 * @in_range_end: End of the time range
2575 * @func: a function to be called for each component in the given time range
2576 * @user_data: user data being passed into the @func
2578 * Calls @func for each component in the given time range. The time range is
2579 * clamp by the actual time range defined by subscribers (if there is no
2580 * subscriber, or all subscribers define times out of the given time range,
2581 * then the function is not called at all and a #FALSE is returned).
2583 * The @func returns #TRUE to continue the traversal. If it wants to stop
2584 * the traversal earlier, then it returns #FALSE.
2586 * Returns: Whether all the components were checked. The returned value is
2587 * usually #TRUE, unless the @func stops traversal earlier.
2589 * Note: A special case when both @in_range_start and @in_range_end are zero
2590 * is treated as a request for all known components.
2592 * Since: 3.16
2594 gboolean
2595 e_cal_data_model_foreach_component (ECalDataModel *data_model,
2596 time_t in_range_start,
2597 time_t in_range_end,
2598 ECalDataModelForeachFunc func,
2599 gpointer user_data)
2601 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
2602 g_return_val_if_fail (func != NULL, FALSE);
2604 return cal_data_model_foreach_component (data_model, in_range_start, in_range_end, func, user_data, FALSE);
2608 * e_cal_data_model_subscribe:
2609 * @data_model: an #EDataModel instance
2610 * @subscriber: an #ECalDataModelSubscriber instance
2611 * @range_start: Start of the time range used by the @subscriber
2612 * @range_end: End of the time range used by the @subscriber
2614 * Either adds a new @subscriber to the set of subscribers for this
2615 * @data_model, or changes a time range used by the @subscriber,
2616 * in case it was added to the @data_model earlier.
2618 * Reference count of the @subscriber is increased by one, in case
2619 * it is newly added. The reference count is decreased by one
2620 * when e_cal_data_model_unsubscribe() is called.
2622 * Note: A special case when both @range_start and @range_end are zero
2623 * is treated as a request with no time constraint. This limits
2624 * the result only to those components which satisfy given filter.
2626 * Since: 3.16
2628 void
2629 e_cal_data_model_subscribe (ECalDataModel *data_model,
2630 ECalDataModelSubscriber *subscriber,
2631 time_t range_start,
2632 time_t range_end)
2634 SubscriberData *subs_data = NULL;
2635 GSList *link;
2637 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2638 g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber));
2640 LOCK_PROPS ();
2642 for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
2643 SubscriberData *subs_data = link->data;
2645 if (!subs_data)
2646 continue;
2648 if (subs_data->subscriber == subscriber)
2649 break;
2652 if (link != NULL) {
2653 time_t new_range_start = range_start, new_range_end = range_end;
2654 time_t old_range_start, old_range_end;
2656 /* The subscriber updates its time range (it is already known) */
2657 subs_data = link->data;
2659 /* No range change */
2660 if (range_start == subs_data->range_start &&
2661 range_end == subs_data->range_end) {
2662 UNLOCK_PROPS ();
2663 return;
2666 old_range_start = subs_data->range_start;
2667 old_range_end = subs_data->range_end;
2669 if (new_range_start == (time_t) 0 && new_range_end == (time_t) 0) {
2670 new_range_start = data_model->priv->range_start;
2671 new_range_end = data_model->priv->range_end;
2674 if (new_range_start == (time_t) 0 && new_range_end == (time_t) 0) {
2675 /* The subscriber is looking for everything and the data_model has everything too */
2676 e_cal_data_model_subscriber_freeze (subs_data->subscriber);
2677 cal_data_model_foreach_component (data_model,
2678 new_range_start, old_range_start,
2679 cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE);
2680 e_cal_data_model_subscriber_thaw (subs_data->subscriber);
2681 } else {
2682 e_cal_data_model_subscriber_freeze (subs_data->subscriber);
2684 if (new_range_start >= old_range_end ||
2685 new_range_end <= old_range_start) {
2686 subs_data->range_start = range_start;
2687 subs_data->range_end = range_end;
2689 /* Completely new range, not overlapping with the former range,
2690 everything previously added can be removed... */
2691 cal_data_model_foreach_component (data_model,
2692 old_range_start, old_range_end,
2693 cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE);
2695 subs_data->range_start = old_range_start;
2696 subs_data->range_end = old_range_end;
2698 /* ...and components from the new range can be added */
2699 cal_data_model_foreach_component (data_model,
2700 new_range_start, new_range_end,
2701 cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE);
2702 } else {
2703 if (new_range_start < old_range_start) {
2704 /* Add those known in the new extended range from the start */
2705 cal_data_model_foreach_component (data_model,
2706 new_range_start, old_range_start,
2707 cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE);
2708 } else if (new_range_start > old_range_start) {
2709 subs_data->range_start = range_start;
2710 subs_data->range_end = range_end;
2712 /* Remove those out of the new range from the start */
2713 cal_data_model_foreach_component (data_model,
2714 old_range_start, new_range_start,
2715 cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE);
2717 subs_data->range_start = old_range_start;
2718 subs_data->range_end = old_range_end;
2721 if (new_range_end > old_range_end) {
2722 /* Add those known in the new extended range from the end */
2723 cal_data_model_foreach_component (data_model,
2724 old_range_end, new_range_end,
2725 cal_data_model_add_to_subscriber_except_its_range, subs_data, TRUE);
2726 } else if (new_range_end < old_range_end) {
2727 subs_data->range_start = range_start;
2728 subs_data->range_end = range_end;
2730 /* Remove those out of the new range from the end */
2731 cal_data_model_foreach_component (data_model,
2732 new_range_end, old_range_end,
2733 cal_data_model_remove_from_subscriber_except_its_range, subs_data, TRUE);
2735 subs_data->range_start = old_range_start;
2736 subs_data->range_end = old_range_end;
2740 e_cal_data_model_subscriber_thaw (subs_data->subscriber);
2743 subs_data->range_start = range_start;
2744 subs_data->range_end = range_end;
2745 } else {
2746 subs_data = subscriber_data_new (subscriber, range_start, range_end);
2748 data_model->priv->subscribers = g_slist_prepend (data_model->priv->subscribers, subs_data);
2750 e_cal_data_model_subscriber_freeze (subscriber);
2751 cal_data_model_foreach_component (data_model, range_start, range_end,
2752 cal_data_model_add_to_subscriber, subscriber, TRUE);
2753 e_cal_data_model_subscriber_thaw (subscriber);
2756 cal_data_model_update_time_range (data_model);
2758 UNLOCK_PROPS ();
2762 * e_cal_data_model_unsubscribe:
2763 * @data_model: an #EDataModel instance
2764 * @subscriber: an #ECalDataModelSubscriber instance
2766 * Removes the @subscriber from the set of subscribers for the @data_model.
2767 * Remove of the @subscriber, which is not in the set of subscribers for
2768 * the @data_model does nothing.
2770 * Note: The @subscriber is not notified about a removal of the components
2771 * which could be added previously, while it was subscribed for the change
2772 * notifications.
2774 * Since: 3.16
2776 void
2777 e_cal_data_model_unsubscribe (ECalDataModel *data_model,
2778 ECalDataModelSubscriber *subscriber)
2780 GSList *link;
2782 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2783 g_return_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber));
2785 LOCK_PROPS ();
2787 for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
2788 SubscriberData *subs_data = link->data;
2790 if (!subs_data)
2791 continue;
2793 if (subs_data->subscriber == subscriber) {
2794 data_model->priv->subscribers = g_slist_remove (data_model->priv->subscribers, subs_data);
2795 subscriber_data_free (subs_data);
2796 break;
2800 cal_data_model_update_time_range (data_model);
2802 UNLOCK_PROPS ();
2806 * e_cal_data_model_get_subscriber_range:
2807 * @data_model: an #EDataModel instance
2808 * @subscriber: an #ECalDataModelSubscriber instance
2809 * @range_start: (out): time range start for the @subscriber
2810 * @range_end: (out): time range end for the @subscriber
2812 * Obtains currently set time range for the @subscriber. In case
2813 * the subscriber is not found returns #FALSE and both @range_start
2814 * and @range_end are left untouched.
2816 * Returns: Whether the @subscriber was found and the @range_start with
2817 * the @range_end were set to its current time range it uses.
2819 * Since: 3.16
2821 gboolean
2822 e_cal_data_model_get_subscriber_range (ECalDataModel *data_model,
2823 ECalDataModelSubscriber *subscriber,
2824 time_t *range_start,
2825 time_t *range_end)
2827 GSList *link;
2829 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
2830 g_return_val_if_fail (E_IS_CAL_DATA_MODEL_SUBSCRIBER (subscriber), FALSE);
2831 g_return_val_if_fail (range_start, FALSE);
2832 g_return_val_if_fail (range_end, FALSE);
2834 LOCK_PROPS ();
2836 for (link = data_model->priv->subscribers; link; link = g_slist_next (link)) {
2837 SubscriberData *subs_data = link->data;
2839 if (!subs_data)
2840 continue;
2842 if (subs_data->subscriber == subscriber) {
2843 *range_start = subs_data->range_start;
2844 *range_end = subs_data->range_end;
2845 break;
2849 UNLOCK_PROPS ();
2851 return link != NULL;
2855 * e_cal_data_model_freeze_views_update:
2856 * @data_model: an #EDataModel instance
2858 * Freezes any views updates until e_cal_data_model_thaw_views_update() is
2859 * called. This can be called nested, then the same count of the calls of
2860 * e_cal_data_model_thaw_views_update() is expected to unlock the views update.
2862 * Since: 3.16
2864 void
2865 e_cal_data_model_freeze_views_update (ECalDataModel *data_model)
2867 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2869 LOCK_PROPS ();
2871 data_model->priv->views_update_freeze++;
2873 UNLOCK_PROPS ();
2877 * e_cal_data_model_thaw_views_update:
2878 * @data_model: an #EDataModel instance
2880 * A pair function for e_cal_data_model_freeze_views_update(), to unlock
2881 * views update.
2883 * Since: 3.16
2885 void
2886 e_cal_data_model_thaw_views_update (ECalDataModel *data_model)
2888 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
2890 LOCK_PROPS ();
2892 if (!data_model->priv->views_update_freeze) {
2893 UNLOCK_PROPS ();
2894 g_warn_if_reached ();
2895 return;
2898 data_model->priv->views_update_freeze--;
2899 if (data_model->priv->views_update_freeze == 0 &&
2900 data_model->priv->views_update_required)
2901 cal_data_model_rebuild_everything (data_model, TRUE);
2903 UNLOCK_PROPS ();
2907 * e_cal_data_model_is_views_update_frozen:
2908 * @data_model: an #EDataModel instance
2910 * Check whether any views updates are currently frozen. This is influenced by
2911 * e_cal_data_model_freeze_views_update() and e_cal_data_model_thaw_views_update().
2913 * Returns: Whether any views updates are currently frozen.
2915 * Since: 3.16
2917 gboolean
2918 e_cal_data_model_is_views_update_frozen (ECalDataModel *data_model)
2920 gboolean is_frozen;
2922 g_return_val_if_fail (E_IS_CAL_DATA_MODEL (data_model), FALSE);
2924 LOCK_PROPS ();
2926 is_frozen = data_model->priv->views_update_freeze > 0;
2928 UNLOCK_PROPS ();
2930 return is_frozen;