1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* Evolution calendar - Live search view implementation
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  * Copyright (C) 2009 Intel Corporation
6  *
7  * This library is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  * Authors: Federico Mena-Quintero <federico@ximian.com>
20  *          Ross Burton <ross@linux.intel.com>
21  */
22 
23 /**
24  * SECTION: e-data-cal-view
25  * @include: libedata-cal/libedata-cal.h
26  * @short_description: A server side object for issuing view notifications
27  *
28  * This class communicates with #ECalClientViews over the bus.
29  *
30  * Calendar backends can automatically own a number of views requested
31  * by the client, this API can be used by the backend to issue notifications
32  * which will be delivered to the #ECalClientView
33  **/
34 
35 #include "evolution-data-server-config.h"
36 
37 #include <string.h>
38 
39 #include "e-cal-backend.h"
40 #include "e-cal-backend-sexp.h"
41 #include "e-data-cal-view.h"
42 #include "e-dbus-calendar-view.h"
43 
44 /* how many items can be hold in a cache, before propagated to UI */
45 #define THRESHOLD_ITEMS 32
46 
47 /* how long to wait until notifications are propagated to UI; in seconds */
48 #define THRESHOLD_SECONDS 2
49 
50 struct _EDataCalViewPrivate {
51 	GDBusConnection *connection;
52 	EDBusCalendarView *dbus_object;
53 	gchar *object_path;
54 
55 	/* The backend we are monitoring */
56 	GWeakRef backend_weakref; /* ECalBackend * */
57 
58 	gboolean started;
59 	gboolean stopped;
60 	gboolean complete;
61 
62 	/* Sexp that defines the view */
63 	ECalBackendSExp *sexp;
64 
65 	GArray *adds;
66 	GArray *changes;
67 	GArray *removes;
68 
69 	GHashTable *ids;
70 
71 	GMutex pending_mutex;
72 	guint flush_id;
73 
74 	/* view flags */
75 	ECalClientViewFlags flags;
76 
77 	/* which fields is listener interested in */
78 	GHashTable *fields_of_interest;
79 };
80 
81 enum {
82 	PROP_0,
83 	PROP_BACKEND,
84 	PROP_CONNECTION,
85 	PROP_OBJECT_PATH,
86 	PROP_SEXP
87 };
88 
89 /* Forward Declarations */
90 static void	e_data_cal_view_initable_init	(GInitableIface *iface);
91 
G_DEFINE_TYPE_WITH_CODE(EDataCalView,e_data_cal_view,G_TYPE_OBJECT,G_ADD_PRIVATE (EDataCalView)G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,e_data_cal_view_initable_init))92 G_DEFINE_TYPE_WITH_CODE (
93 	EDataCalView,
94 	e_data_cal_view,
95 	G_TYPE_OBJECT,
96 	G_ADD_PRIVATE (EDataCalView)
97 	G_IMPLEMENT_INTERFACE (
98 		G_TYPE_INITABLE,
99 		e_data_cal_view_initable_init))
100 
101 static guint
102 str_ic_hash (gconstpointer key)
103 {
104 	guint32 hash = 5381;
105 	const gchar *str = key;
106 	gint ii;
107 
108 	if (str == NULL)
109 		return hash;
110 
111 	for (ii = 0; str[ii] != '\0'; ii++)
112 		hash = hash * 33 + g_ascii_tolower (str[ii]);
113 
114 	return hash;
115 }
116 
117 static gboolean
str_ic_equal(gconstpointer a,gconstpointer b)118 str_ic_equal (gconstpointer a,
119               gconstpointer b)
120 {
121 	const gchar *stra = a;
122 	const gchar *strb = b;
123 	gint ii;
124 
125 	if (stra == NULL && strb == NULL)
126 		return TRUE;
127 
128 	if (stra == NULL || strb == NULL)
129 		return FALSE;
130 
131 	for (ii = 0; stra[ii] != '\0' && strb[ii] != '\0'; ii++) {
132 		if (g_ascii_tolower (stra[ii]) != g_ascii_tolower (strb[ii]))
133 			return FALSE;
134 	}
135 
136 	return stra[ii] == strb[ii];
137 }
138 
139 static void
reset_array(GArray * array)140 reset_array (GArray *array)
141 {
142 	gint i = 0;
143 	gchar *tmp = NULL;
144 
145 	/* Free stored strings */
146 	for (i = 0; i < array->len; i++) {
147 		tmp = g_array_index (array, gchar *, i);
148 		g_free (tmp);
149 	}
150 
151 	/* Force the array size to 0 */
152 	g_array_set_size (array, 0);
153 }
154 
155 static gpointer
calview_start_thread(gpointer data)156 calview_start_thread (gpointer data)
157 {
158 	EDataCalView *view = data;
159 
160 	if (view->priv->started && !view->priv->stopped) {
161 		ECalBackend *backend = e_data_cal_view_ref_backend (view);
162 
163 		if (backend) {
164 			/* To avoid race condition when one thread is starting the view, while
165 			   another thread wants to notify about created/modified/removed objects. */
166 			e_cal_backend_sexp_lock (view->priv->sexp);
167 
168 			e_cal_backend_start_view (backend, view);
169 
170 			e_cal_backend_sexp_unlock (view->priv->sexp);
171 
172 			g_object_unref (backend);
173 		}
174 	}
175 
176 	g_object_unref (view);
177 
178 	return NULL;
179 }
180 
181 static gboolean
impl_DataCalView_start(EDBusCalendarView * object,GDBusMethodInvocation * invocation,EDataCalView * view)182 impl_DataCalView_start (EDBusCalendarView *object,
183                         GDBusMethodInvocation *invocation,
184                         EDataCalView *view)
185 {
186 	if (!view->priv->started) {
187 		ECalBackend *backend = e_data_cal_view_ref_backend (view);
188 		GThread *thread;
189 
190 		view->priv->started = TRUE;
191 		e_debug_log (
192 			FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES,
193 			"---;%p;VIEW-START;%s;%s", view,
194 			e_cal_backend_sexp_text (view->priv->sexp),
195 			backend ? G_OBJECT_TYPE_NAME (backend) : "null backend");
196 
197 		g_clear_object (&backend);
198 
199 		thread = g_thread_new (
200 			NULL, calview_start_thread, g_object_ref (view));
201 		g_thread_unref (thread);
202 	}
203 
204 	e_dbus_calendar_view_complete_start (object, invocation);
205 
206 	return TRUE;
207 }
208 
209 static gpointer
calview_stop_thread(gpointer data)210 calview_stop_thread (gpointer data)
211 {
212 	EDataCalView *view = data;
213 
214 	if (view->priv->stopped) {
215 		ECalBackend *backend = e_data_cal_view_ref_backend (view);
216 
217 		if (backend) {
218 			e_cal_backend_stop_view (backend, view);
219 			g_object_unref (backend);
220 		}
221 	}
222 	g_object_unref (view);
223 
224 	return NULL;
225 }
226 
227 static gboolean
impl_DataCalView_stop(EDBusCalendarView * object,GDBusMethodInvocation * invocation,EDataCalView * view)228 impl_DataCalView_stop (EDBusCalendarView *object,
229                        GDBusMethodInvocation *invocation,
230                        EDataCalView *view)
231 {
232 	GThread *thread;
233 
234 	view->priv->stopped = TRUE;
235 
236 	thread = g_thread_new (NULL, calview_stop_thread, g_object_ref (view));
237 	g_thread_unref (thread);
238 
239 	e_dbus_calendar_view_complete_stop (object, invocation);
240 
241 	return TRUE;
242 }
243 
244 static gboolean
impl_DataCalView_setFlags(EDBusCalendarView * object,GDBusMethodInvocation * invocation,ECalClientViewFlags flags,EDataCalView * view)245 impl_DataCalView_setFlags (EDBusCalendarView *object,
246                            GDBusMethodInvocation *invocation,
247                            ECalClientViewFlags flags,
248                            EDataCalView *view)
249 {
250 	view->priv->flags = flags;
251 
252 	e_dbus_calendar_view_complete_set_flags (object, invocation);
253 
254 	return TRUE;
255 }
256 
257 static gboolean
impl_DataCalView_dispose(EDBusCalendarView * object,GDBusMethodInvocation * invocation,EDataCalView * view)258 impl_DataCalView_dispose (EDBusCalendarView *object,
259                           GDBusMethodInvocation *invocation,
260                           EDataCalView *view)
261 {
262 	ECalBackend *backend;
263 
264 	e_dbus_calendar_view_complete_dispose (object, invocation);
265 
266 	backend = e_data_cal_view_ref_backend (view);
267 
268 	if (backend) {
269 		e_cal_backend_stop_view (backend, view);
270 		view->priv->stopped = TRUE;
271 		e_cal_backend_remove_view (backend, view);
272 
273 		g_object_unref (backend);
274 	} else {
275 		view->priv->stopped = TRUE;
276 	}
277 
278 	return TRUE;
279 }
280 
281 static gboolean
impl_DataCalView_set_fields_of_interest(EDBusCalendarView * object,GDBusMethodInvocation * invocation,const gchar * const * in_fields_of_interest,EDataCalView * view)282 impl_DataCalView_set_fields_of_interest (EDBusCalendarView *object,
283                                          GDBusMethodInvocation *invocation,
284                                          const gchar * const *in_fields_of_interest,
285                                          EDataCalView *view)
286 {
287 	gint ii;
288 
289 	g_return_val_if_fail (in_fields_of_interest != NULL, TRUE);
290 
291 	g_clear_pointer (&view->priv->fields_of_interest, g_hash_table_destroy);
292 
293 	for (ii = 0; in_fields_of_interest[ii]; ii++) {
294 		const gchar *field = in_fields_of_interest[ii];
295 
296 		if (!*field)
297 			continue;
298 
299 		if (view->priv->fields_of_interest == NULL)
300 			view->priv->fields_of_interest =
301 				g_hash_table_new_full (
302 					(GHashFunc) str_ic_hash,
303 					(GEqualFunc) str_ic_equal,
304 					(GDestroyNotify) g_free,
305 					(GDestroyNotify) NULL);
306 
307 		g_hash_table_insert (
308 			view->priv->fields_of_interest,
309 			g_strdup (field), GINT_TO_POINTER (1));
310 	}
311 
312 	e_dbus_calendar_view_complete_set_fields_of_interest (object, invocation);
313 
314 	return TRUE;
315 }
316 
317 static void
data_cal_view_set_backend(EDataCalView * view,ECalBackend * backend)318 data_cal_view_set_backend (EDataCalView *view,
319                            ECalBackend *backend)
320 {
321 	g_return_if_fail (E_IS_CAL_BACKEND (backend));
322 
323 	g_weak_ref_set (&view->priv->backend_weakref, backend);
324 }
325 
326 static void
data_cal_view_set_connection(EDataCalView * view,GDBusConnection * connection)327 data_cal_view_set_connection (EDataCalView *view,
328                               GDBusConnection *connection)
329 {
330 	g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
331 	g_return_if_fail (view->priv->connection == NULL);
332 
333 	view->priv->connection = g_object_ref (connection);
334 }
335 
336 static void
data_cal_view_set_object_path(EDataCalView * view,const gchar * object_path)337 data_cal_view_set_object_path (EDataCalView *view,
338                                const gchar *object_path)
339 {
340 	g_return_if_fail (object_path != NULL);
341 	g_return_if_fail (view->priv->object_path == NULL);
342 
343 	view->priv->object_path = g_strdup (object_path);
344 }
345 
346 static void
data_cal_view_set_sexp(EDataCalView * view,ECalBackendSExp * sexp)347 data_cal_view_set_sexp (EDataCalView *view,
348                         ECalBackendSExp *sexp)
349 {
350 	g_return_if_fail (E_IS_CAL_BACKEND_SEXP (sexp));
351 	g_return_if_fail (view->priv->sexp == NULL);
352 
353 	view->priv->sexp = g_object_ref (sexp);
354 }
355 
356 static void
data_cal_view_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)357 data_cal_view_set_property (GObject *object,
358                             guint property_id,
359                             const GValue *value,
360                             GParamSpec *pspec)
361 {
362 	switch (property_id) {
363 		case PROP_BACKEND:
364 			data_cal_view_set_backend (
365 				E_DATA_CAL_VIEW (object),
366 				g_value_get_object (value));
367 			return;
368 
369 		case PROP_CONNECTION:
370 			data_cal_view_set_connection (
371 				E_DATA_CAL_VIEW (object),
372 				g_value_get_object (value));
373 			return;
374 
375 		case PROP_OBJECT_PATH:
376 			data_cal_view_set_object_path (
377 				E_DATA_CAL_VIEW (object),
378 				g_value_get_string (value));
379 			return;
380 
381 		case PROP_SEXP:
382 			data_cal_view_set_sexp (
383 				E_DATA_CAL_VIEW (object),
384 				g_value_get_object (value));
385 			return;
386 	}
387 
388 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
389 }
390 
391 static void
data_cal_view_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)392 data_cal_view_get_property (GObject *object,
393                             guint property_id,
394                             GValue *value,
395                             GParamSpec *pspec)
396 {
397 	switch (property_id) {
398 		case PROP_BACKEND:
399 			g_value_take_object (
400 				value,
401 				e_data_cal_view_ref_backend (
402 				E_DATA_CAL_VIEW (object)));
403 			return;
404 
405 		case PROP_CONNECTION:
406 			g_value_set_object (
407 				value,
408 				e_data_cal_view_get_connection (
409 				E_DATA_CAL_VIEW (object)));
410 			return;
411 
412 		case PROP_OBJECT_PATH:
413 			g_value_set_string (
414 				value,
415 				e_data_cal_view_get_object_path (
416 				E_DATA_CAL_VIEW (object)));
417 			return;
418 
419 		case PROP_SEXP:
420 			g_value_set_object (
421 				value,
422 				e_data_cal_view_get_sexp (
423 				E_DATA_CAL_VIEW (object)));
424 			return;
425 	}
426 
427 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
428 }
429 
430 static void
data_cal_view_dispose(GObject * object)431 data_cal_view_dispose (GObject *object)
432 {
433 	EDataCalViewPrivate *priv;
434 
435 	priv = E_DATA_CAL_VIEW (object)->priv;
436 
437 	g_mutex_lock (&priv->pending_mutex);
438 
439 	if (priv->flush_id > 0) {
440 		g_source_remove (priv->flush_id);
441 		priv->flush_id = 0;
442 	}
443 
444 	g_mutex_unlock (&priv->pending_mutex);
445 
446 	g_clear_object (&priv->connection);
447 	g_clear_object (&priv->dbus_object);
448 	g_clear_object (&priv->sexp);
449 
450 	g_weak_ref_set (&priv->backend_weakref, NULL);
451 
452 	/* Chain up to parent's dispose() method. */
453 	G_OBJECT_CLASS (e_data_cal_view_parent_class)->dispose (object);
454 }
455 
456 static void
data_cal_view_finalize(GObject * object)457 data_cal_view_finalize (GObject *object)
458 {
459 	EDataCalViewPrivate *priv;
460 
461 	priv = E_DATA_CAL_VIEW (object)->priv;
462 
463 	g_free (priv->object_path);
464 
465 	reset_array (priv->adds);
466 	reset_array (priv->changes);
467 	reset_array (priv->removes);
468 
469 	g_array_free (priv->adds, TRUE);
470 	g_array_free (priv->changes, TRUE);
471 	g_array_free (priv->removes, TRUE);
472 
473 	g_hash_table_destroy (priv->ids);
474 
475 	if (priv->fields_of_interest != NULL)
476 		g_hash_table_destroy (priv->fields_of_interest);
477 
478 	g_mutex_clear (&priv->pending_mutex);
479 	g_weak_ref_clear (&priv->backend_weakref);
480 
481 	/* Chain up to parent's finalize() method. */
482 	G_OBJECT_CLASS (e_data_cal_view_parent_class)->finalize (object);
483 }
484 
485 static gboolean
data_cal_view_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)486 data_cal_view_initable_init (GInitable *initable,
487                              GCancellable *cancellable,
488                              GError **error)
489 {
490 	EDataCalView *view;
491 
492 	view = E_DATA_CAL_VIEW (initable);
493 
494 	return g_dbus_interface_skeleton_export (
495 		G_DBUS_INTERFACE_SKELETON (view->priv->dbus_object),
496 		view->priv->connection,
497 		view->priv->object_path,
498 		error);
499 }
500 
501 static void
e_data_cal_view_class_init(EDataCalViewClass * class)502 e_data_cal_view_class_init (EDataCalViewClass *class)
503 {
504 	GObjectClass *object_class;
505 
506 	object_class = G_OBJECT_CLASS (class);
507 	object_class->set_property = data_cal_view_set_property;
508 	object_class->get_property = data_cal_view_get_property;
509 	object_class->dispose = data_cal_view_dispose;
510 	object_class->finalize = data_cal_view_finalize;
511 
512 	g_object_class_install_property (
513 		object_class,
514 		PROP_BACKEND,
515 		g_param_spec_object (
516 			"backend",
517 			"Backend",
518 			"The backend being monitored",
519 			E_TYPE_CAL_BACKEND,
520 			G_PARAM_READWRITE |
521 			G_PARAM_CONSTRUCT_ONLY |
522 			G_PARAM_STATIC_STRINGS));
523 
524 	g_object_class_install_property (
525 		object_class,
526 		PROP_CONNECTION,
527 		g_param_spec_object (
528 			"connection",
529 			"Connection",
530 			"The GDBusConnection on which "
531 			"to export the view interface",
532 			G_TYPE_DBUS_CONNECTION,
533 			G_PARAM_READWRITE |
534 			G_PARAM_CONSTRUCT_ONLY |
535 			G_PARAM_STATIC_STRINGS));
536 
537 	g_object_class_install_property (
538 		object_class,
539 		PROP_OBJECT_PATH,
540 		g_param_spec_string (
541 			"object-path",
542 			"Object Path",
543 			"The object path at which to "
544 			"export the view interface",
545 			NULL,
546 			G_PARAM_READWRITE |
547 			G_PARAM_CONSTRUCT_ONLY |
548 			G_PARAM_STATIC_STRINGS));
549 
550 	g_object_class_install_property (
551 		object_class,
552 		PROP_SEXP,
553 		g_param_spec_object (
554 			"sexp",
555 			"S-Expression",
556 			"The query expression for this view",
557 			E_TYPE_CAL_BACKEND_SEXP,
558 			G_PARAM_READWRITE |
559 			G_PARAM_CONSTRUCT_ONLY |
560 			G_PARAM_STATIC_STRINGS));
561 }
562 
563 static void
e_data_cal_view_initable_init(GInitableIface * iface)564 e_data_cal_view_initable_init (GInitableIface *iface)
565 {
566 	iface->init = data_cal_view_initable_init;
567 }
568 
569 static void
e_data_cal_view_init(EDataCalView * view)570 e_data_cal_view_init (EDataCalView *view)
571 {
572 	view->priv = e_data_cal_view_get_instance_private (view);
573 
574 	view->priv->flags = E_CAL_CLIENT_VIEW_FLAGS_NOTIFY_INITIAL;
575 
576 	view->priv->dbus_object = e_dbus_calendar_view_skeleton_new ();
577 	g_signal_connect (
578 		view->priv->dbus_object, "handle-start",
579 		G_CALLBACK (impl_DataCalView_start), view);
580 	g_signal_connect (
581 		view->priv->dbus_object, "handle-stop",
582 		G_CALLBACK (impl_DataCalView_stop), view);
583 	g_signal_connect (
584 		view->priv->dbus_object, "handle-set-flags",
585 		G_CALLBACK (impl_DataCalView_setFlags), view);
586 	g_signal_connect (
587 		view->priv->dbus_object, "handle-dispose",
588 		G_CALLBACK (impl_DataCalView_dispose), view);
589 	g_signal_connect (
590 		view->priv->dbus_object, "handle-set-fields-of-interest",
591 		G_CALLBACK (impl_DataCalView_set_fields_of_interest), view);
592 
593 	g_weak_ref_init (&view->priv->backend_weakref, NULL);
594 	view->priv->started = FALSE;
595 	view->priv->stopped = FALSE;
596 	view->priv->complete = FALSE;
597 	view->priv->sexp = NULL;
598 	view->priv->fields_of_interest = NULL;
599 
600 	view->priv->adds = g_array_sized_new (
601 		TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS);
602 	view->priv->changes = g_array_sized_new (
603 		TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS);
604 	view->priv->removes = g_array_sized_new (
605 		TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS);
606 
607 	view->priv->ids = g_hash_table_new_full (
608 		(GHashFunc) e_cal_component_id_hash,
609 		(GEqualFunc) e_cal_component_id_equal,
610 		(GDestroyNotify) e_cal_component_id_free,
611 		(GDestroyNotify) NULL);
612 
613 	g_mutex_init (&view->priv->pending_mutex);
614 	view->priv->flush_id = 0;
615 }
616 
617 /**
618  * e_data_cal_view_new:
619  * @backend: an #ECalBackend
620  * @sexp: an #ECalBackendSExp
621  * @connection: a #GDBusConnection
622  * @object_path: an object path for the view
623  * @error: return location for a #GError, or %NULL
624  *
625  * Creates a new #EDataCalView and exports its D-Bus interface on
626  * @connection at @object_path.  If an error occurs while exporting,
627  * the function sets @error and returns %NULL.
628  *
629  * Returns: (transfer full) (nullable): a new #EDataCalView, or %NULL on error
630  **/
631 EDataCalView *
e_data_cal_view_new(ECalBackend * backend,ECalBackendSExp * sexp,GDBusConnection * connection,const gchar * object_path,GError ** error)632 e_data_cal_view_new (ECalBackend *backend,
633                      ECalBackendSExp *sexp,
634                      GDBusConnection *connection,
635                      const gchar *object_path,
636                      GError **error)
637 {
638 	g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
639 	g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), NULL);
640 	g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
641 	g_return_val_if_fail (object_path != NULL, NULL);
642 
643 	return g_initable_new (
644 		E_TYPE_DATA_CAL_VIEW, NULL, error,
645 		"backend", backend,
646 		"connection", connection,
647 		"object-path", object_path,
648 		"sexp", sexp,
649 		NULL);
650 }
651 
652 static void
send_pending_adds(EDataCalView * view)653 send_pending_adds (EDataCalView *view)
654 {
655 	if (view->priv->adds->len == 0)
656 		return;
657 
658 	e_dbus_calendar_view_emit_objects_added (
659 		view->priv->dbus_object,
660 		(const gchar * const *) view->priv->adds->data);
661 	reset_array (view->priv->adds);
662 }
663 
664 static void
send_pending_changes(EDataCalView * view)665 send_pending_changes (EDataCalView *view)
666 {
667 	if (view->priv->changes->len == 0)
668 		return;
669 
670 	e_dbus_calendar_view_emit_objects_modified (
671 		view->priv->dbus_object,
672 		(const gchar * const *) view->priv->changes->data);
673 	reset_array (view->priv->changes);
674 }
675 
676 static void
send_pending_removes(EDataCalView * view)677 send_pending_removes (EDataCalView *view)
678 {
679 	if (view->priv->removes->len == 0)
680 		return;
681 
682 	/* send ECalComponentIds as <uid>[\n<rid>], as encoded in notify_remove() */
683 	e_dbus_calendar_view_emit_objects_removed (
684 		view->priv->dbus_object,
685 		(const gchar * const *) view->priv->removes->data);
686 	reset_array (view->priv->removes);
687 }
688 
689 static gboolean
pending_flush_timeout_cb(gpointer data)690 pending_flush_timeout_cb (gpointer data)
691 {
692 	EDataCalView *view = data;
693 
694 	g_mutex_lock (&view->priv->pending_mutex);
695 
696 	view->priv->flush_id = 0;
697 
698 	if (!g_source_is_destroyed (g_main_current_source ())) {
699 		send_pending_adds (view);
700 		send_pending_changes (view);
701 		send_pending_removes (view);
702 	}
703 
704 	g_mutex_unlock (&view->priv->pending_mutex);
705 
706 	return FALSE;
707 }
708 
709 static void
ensure_pending_flush_timeout(EDataCalView * view)710 ensure_pending_flush_timeout (EDataCalView *view)
711 {
712 	if (view->priv->flush_id > 0)
713 		return;
714 
715 	if (e_data_cal_view_is_completed (view)) {
716 		view->priv->flush_id = e_named_timeout_add (
717 			10 /* ms */, pending_flush_timeout_cb, view);
718 	} else {
719 		view->priv->flush_id = e_named_timeout_add_seconds (
720 			THRESHOLD_SECONDS, pending_flush_timeout_cb, view);
721 	}
722 }
723 
724 static void
notify_add_component(EDataCalView * view,ECalComponent * comp)725 notify_add_component (EDataCalView *view,
726                       /* const */ ECalComponent *comp)
727 {
728 	ECalClientViewFlags flags;
729 	ECalComponentId *id;
730 
731 	id = e_cal_component_get_id (comp);
732 
733 	if (!id || g_hash_table_lookup (view->priv->ids, id)) {
734 		e_cal_component_id_free (id);
735 		return;
736 	}
737 
738 	send_pending_changes (view);
739 	send_pending_removes (view);
740 
741 	/* Do not send component add notifications during initial stage */
742 	flags = e_data_cal_view_get_flags (view);
743 	if (view->priv->complete || (flags & E_CAL_CLIENT_VIEW_FLAGS_NOTIFY_INITIAL) != 0) {
744 		gchar *obj;
745 
746 		if (view->priv->adds->len == THRESHOLD_ITEMS)
747 			send_pending_adds (view);
748 
749 		obj = e_data_cal_view_get_component_string (view, comp);
750 
751 		g_array_append_val (view->priv->adds, obj);
752 
753 		ensure_pending_flush_timeout (view);
754 	}
755 
756 	g_hash_table_insert (view->priv->ids, id, GUINT_TO_POINTER (1));
757 }
758 
759 static void
notify_change(EDataCalView * view,gchar * obj)760 notify_change (EDataCalView *view,
761                gchar *obj)
762 {
763 	send_pending_adds (view);
764 	send_pending_removes (view);
765 
766 	if (view->priv->changes->len == THRESHOLD_ITEMS)
767 		send_pending_changes (view);
768 
769 	g_array_append_val (view->priv->changes, obj);
770 
771 	ensure_pending_flush_timeout (view);
772 }
773 
774 static void
notify_change_component(EDataCalView * view,ECalComponent * comp)775 notify_change_component (EDataCalView *view,
776                          ECalComponent *comp)
777 {
778 	gchar *obj;
779 
780 	obj = e_data_cal_view_get_component_string (view, comp);
781 
782 	notify_change (view, obj);
783 }
784 
785 static void
notify_remove(EDataCalView * view,ECalComponentId * id)786 notify_remove (EDataCalView *view,
787                ECalComponentId *id)
788 {
789 	gchar *ids;
790 	gsize ids_len, ids_offset;
791 	gchar *uid, *rid;
792 	gsize uid_len, rid_len;
793 
794 	send_pending_adds (view);
795 	send_pending_changes (view);
796 
797 	if (view->priv->removes->len == THRESHOLD_ITEMS)
798 		send_pending_removes (view);
799 
800 	/* store ECalComponentId as <uid>[\n<rid>] (matches D-Bus API) */
801 	if (e_cal_component_id_get_uid (id)) {
802 		uid = e_util_utf8_make_valid (e_cal_component_id_get_uid (id));
803 		uid_len = strlen (uid);
804 	} else {
805 		uid = NULL;
806 		uid_len = 0;
807 	}
808 	if (e_cal_component_id_get_rid (id)) {
809 		rid = e_util_utf8_make_valid (e_cal_component_id_get_rid (id));
810 		rid_len = strlen (rid);
811 	} else {
812 		rid = NULL;
813 		rid_len = 0;
814 	}
815 	if (uid_len && !rid_len) {
816 		/* shortcut */
817 		ids = uid;
818 		uid = NULL;
819 	} else {
820 		/* concatenate */
821 		ids_len = uid_len + rid_len + (rid_len ? 2 : 1);
822 		ids = g_malloc (ids_len);
823 		if (uid_len)
824 			g_strlcpy (ids, uid, ids_len);
825 		if (rid_len) {
826 			ids_offset = uid_len + 1;
827 			g_strlcpy (ids + ids_offset, rid, ids_len - ids_offset);
828 		}
829 	}
830 	g_array_append_val (view->priv->removes, ids);
831 	g_free (uid);
832 	g_free (rid);
833 
834 	g_hash_table_remove (view->priv->ids, id);
835 
836 	ensure_pending_flush_timeout (view);
837 }
838 
839 /**
840  * e_data_cal_view_ref_backend:
841  * @view: an #EDataCalView
842  *
843  * Refs the backend that @view is querying. Unref the returned backend,
844  * if not %NULL, with g_object_unref(), when no longer needed.
845  *
846  * Returns: (type ECalBackend) (transfer full) (nullable): The associated #ECalBackend.
847  *
848  * Since: 3.34
849  **/
850 ECalBackend *
e_data_cal_view_ref_backend(EDataCalView * view)851 e_data_cal_view_ref_backend (EDataCalView *view)
852 {
853 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
854 
855 	return g_weak_ref_get (&view->priv->backend_weakref);
856 }
857 
858 /**
859  * e_data_cal_view_get_backend:
860  * @view: an #EDataCalView
861  *
862  * Gets the backend that @view is querying.
863  *
864  * Returns: (transfer none): The associated #ECalBackend.
865  *
866  * Since: 3.8
867  *
868  * Deprecated: 3.34: Use e_data_cal_view_ref_backend() instead.
869  **/
870 ECalBackend *
e_data_cal_view_get_backend(EDataCalView * view)871 e_data_cal_view_get_backend (EDataCalView *view)
872 {
873 	ECalBackend *backend;
874 
875 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
876 
877 	backend = e_data_cal_view_ref_backend (view);
878 	if (backend)
879 		g_object_unref (backend);
880 
881 	return backend;
882 }
883 
884 /**
885  * e_data_cal_view_get_connection:
886  * @view: an #EDataCalView
887  *
888  * Returns the #GDBusConnection on which the CalendarView D-Bus
889  * interface is exported.
890  *
891  * Returns: (transfer none): the #GDBusConnection
892  *
893  * Since: 3.8
894  **/
895 GDBusConnection *
e_data_cal_view_get_connection(EDataCalView * view)896 e_data_cal_view_get_connection (EDataCalView *view)
897 {
898 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
899 
900 	return view->priv->connection;
901 }
902 
903 /**
904  * e_data_cal_view_get_object_path:
905  * @view: an #EDataCalView
906  *
907  * Return the object path at which the CalendarView D-Bus inteface is
908  * exported.
909  *
910  * Returns: the object path
911  *
912  * Since: 3.8
913  **/
914 const gchar *
e_data_cal_view_get_object_path(EDataCalView * view)915 e_data_cal_view_get_object_path (EDataCalView *view)
916 {
917 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
918 
919 	return view->priv->object_path;
920 }
921 
922 /**
923  * e_data_cal_view_get_sexp:
924  * @view: an #EDataCalView
925  *
926  * Get the #ECalBackendSExp object used for the given view.
927  *
928  * Returns: (transfer none): The expression object used to search.
929  *
930  * Since: 3.8
931  */
932 ECalBackendSExp *
e_data_cal_view_get_sexp(EDataCalView * view)933 e_data_cal_view_get_sexp (EDataCalView *view)
934 {
935 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
936 
937 	return view->priv->sexp;
938 }
939 
940 /**
941  * e_data_cal_view_object_matches:
942  * @view: an #EDataCalView
943  * @object: Object to match.
944  *
945  * Compares the given @object to the regular expression used for the
946  * given view.
947  *
948  * Returns: TRUE if the object matches the expression, FALSE if not.
949  */
950 gboolean
e_data_cal_view_object_matches(EDataCalView * view,const gchar * object)951 e_data_cal_view_object_matches (EDataCalView *view,
952                                 const gchar *object)
953 {
954 	ECalBackend *backend;
955 	ECalBackendSExp *sexp;
956 	gboolean res;
957 
958 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), FALSE);
959 	g_return_val_if_fail (object != NULL, FALSE);
960 
961 	sexp = e_data_cal_view_get_sexp (view);
962 	backend = e_data_cal_view_ref_backend (view);
963 
964 	res = e_cal_backend_sexp_match_object (sexp, object, backend ? E_TIMEZONE_CACHE (backend) : NULL);
965 
966 	g_clear_object (&backend);
967 
968 	return res;
969 }
970 
971 /**
972  * e_data_cal_view_component_matches:
973  * @view: an #EDataCalView
974  * @component: the #ECalComponent object to match.
975  *
976  * Compares the given @component to the regular expression used for the
977  * given view.
978  *
979  * Returns: TRUE if the object matches the expression, FALSE if not.
980  *
981  * Since: 3.4
982  */
983 gboolean
e_data_cal_view_component_matches(EDataCalView * view,ECalComponent * component)984 e_data_cal_view_component_matches (EDataCalView *view,
985                                    ECalComponent *component)
986 {
987 	ECalBackend *backend;
988 	ECalBackendSExp *sexp;
989 	gboolean res;
990 
991 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), FALSE);
992 	g_return_val_if_fail (E_IS_CAL_COMPONENT (component), FALSE);
993 
994 	sexp = e_data_cal_view_get_sexp (view);
995 	backend = e_data_cal_view_ref_backend (view);
996 
997 	res = e_cal_backend_sexp_match_comp (sexp, component, backend ? E_TIMEZONE_CACHE (backend) : NULL);
998 
999 	g_clear_object (&backend);
1000 
1001 	return res;
1002 }
1003 
1004 /**
1005  * e_data_cal_view_is_started:
1006  * @view: an #EDataCalView
1007  *
1008  * Checks whether the given view has already been started.
1009  *
1010  * Returns: TRUE if the view has already been started, FALSE otherwise.
1011  */
1012 gboolean
e_data_cal_view_is_started(EDataCalView * view)1013 e_data_cal_view_is_started (EDataCalView *view)
1014 {
1015 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), FALSE);
1016 
1017 	return view->priv->started;
1018 }
1019 
1020 /**
1021  * e_data_cal_view_is_stopped:
1022  * @view: an #EDataCalView
1023  *
1024  * Checks whether the given view has been stopped.
1025  *
1026  * Returns: TRUE if the view has been stopped, FALSE otherwise.
1027  *
1028  * Since: 2.32
1029  */
1030 gboolean
e_data_cal_view_is_stopped(EDataCalView * view)1031 e_data_cal_view_is_stopped (EDataCalView *view)
1032 {
1033 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), FALSE);
1034 
1035 	return view->priv->stopped;
1036 }
1037 
1038 /**
1039  * e_data_cal_view_is_completed:
1040  * @view: an #EDataCalView
1041  *
1042  * Checks whether the given view is already completed. Being completed means the initial
1043  * matching of objects have been finished, not that no more notifications about
1044  * changes will be sent. In fact, even after completed, notifications will still be sent
1045  * if there are changes in the objects matching the view search expression.
1046  *
1047  * Returns: TRUE if the view is completed, FALSE if still in progress.
1048  *
1049  * Since: 3.2
1050  */
1051 gboolean
e_data_cal_view_is_completed(EDataCalView * view)1052 e_data_cal_view_is_completed (EDataCalView *view)
1053 {
1054 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), FALSE);
1055 
1056 	return view->priv->complete;
1057 }
1058 
1059 /**
1060  * e_data_cal_view_get_fields_of_interest:
1061  * @view: an #EDataCalView
1062  *
1063  * Returns: (transfer none): Hash table of field names which the listener is interested in.
1064  * Backends can return fully populated objects, but the listener advertised
1065  * that it will use only these. Returns %NULL for all available fields.
1066  *
1067  * Note: The data pointer in the hash table has no special meaning, it's
1068  * only GINT_TO_POINTER(1) for easier checking. Also, field names are
1069  * compared case insensitively.
1070  *
1071  * Since: 3.2
1072  **/
1073 /* const */ GHashTable *
e_data_cal_view_get_fields_of_interest(EDataCalView * view)1074 e_data_cal_view_get_fields_of_interest (EDataCalView *view)
1075 {
1076 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
1077 
1078 	return view->priv->fields_of_interest;
1079 }
1080 
1081 /**
1082  * e_data_cal_view_get_flags:
1083  * @view: an #EDataCalView
1084  *
1085  * Gets the #ECalClientViewFlags that control the behaviour of @view.
1086  *
1087  * Returns: the flags for @view.
1088  *
1089  * Since: 3.6
1090  **/
1091 ECalClientViewFlags
e_data_cal_view_get_flags(EDataCalView * view)1092 e_data_cal_view_get_flags (EDataCalView *view)
1093 {
1094 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), 0);
1095 
1096 	return view->priv->flags;
1097 }
1098 
1099 static gboolean
filter_component(ICalComponent * icomponent,GHashTable * fields_of_interest,GString * string)1100 filter_component (ICalComponent *icomponent,
1101                   GHashTable *fields_of_interest,
1102                   GString *string)
1103 {
1104 	gchar             *str;
1105 
1106 	/* RFC 2445 explicitly says that the newline is *ALWAYS* a \r\n (CRLF)!!!! */
1107 	const gchar        newline[] = "\r\n";
1108 
1109 	ICalComponentKind  kind;
1110 	const gchar       *kind_string;
1111 	ICalProperty      *prop;
1112 	ICalComponent     *icomp;
1113 	gboolean           fail = FALSE;
1114 
1115 	g_return_val_if_fail (icomponent != NULL, FALSE);
1116 
1117 	/* Open iCalendar string */
1118 	g_string_append (string, "BEGIN:");
1119 
1120 	kind = i_cal_component_isa (icomponent);
1121 
1122 	/* if (kind != I_CAL_X_COMPONENT) { */
1123 	/* 	kind_string  = i_cal_component_kind_to_string (kind); */
1124 	/* } else { */
1125 	/* 	kind_string = icomponent->x_name; */
1126 	/* } */
1127 
1128 	kind_string = i_cal_component_kind_to_string (kind);
1129 
1130 	g_string_append (string, kind_string);
1131 	g_string_append (string, newline);
1132 
1133 	for (prop = i_cal_component_get_first_property (icomponent, I_CAL_ANY_PROPERTY);
1134 	     prop;
1135 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icomponent, I_CAL_ANY_PROPERTY)) {
1136 		gchar *name;
1137 		gboolean is_field_of_interest;
1138 
1139 		name = i_cal_property_get_property_name (prop);
1140 
1141 		if (!name) {
1142 			g_warning ("NULL iCal property name encountered while serializing component");
1143 			fail = TRUE;
1144 			break;
1145 		}
1146 
1147 		is_field_of_interest = GPOINTER_TO_INT (g_hash_table_lookup (fields_of_interest, name));
1148 
1149 		/* Append any name that is mentioned in the fields-of-interest */
1150 		if (is_field_of_interest) {
1151 			str = i_cal_property_as_ical_string (prop);
1152 			g_string_append (string, str);
1153 			g_free (str);
1154 		}
1155 
1156 		g_free (name);
1157 	}
1158 
1159 	for (icomp = i_cal_component_get_first_component (icomponent, I_CAL_ANY_COMPONENT);
1160 	     fail == FALSE && icomp;
1161 	     g_object_unref (icomp), icomp = i_cal_component_get_next_component (icomponent, I_CAL_ANY_COMPONENT)) {
1162 
1163 		if (!filter_component (icomp, fields_of_interest, string)) {
1164 			fail = TRUE;
1165 			break;
1166 		}
1167 	}
1168 
1169 	g_clear_object (&icomp);
1170 
1171 	g_string_append (string, "END:");
1172 	g_string_append (string, i_cal_component_kind_to_string (kind));
1173 	g_string_append (string, newline);
1174 
1175 	return fail == FALSE;
1176 }
1177 
1178 /**
1179  * e_data_cal_view_get_component_string:
1180  * @view: an #EDataCalView
1181  * @component: The #ECalComponent to get the string for.
1182  *
1183  * This function is similar to e_cal_component_get_as_string() except
1184  * that it takes into account the fields-of-interest that @view is
1185  * configured with and filters out any unneeded fields.
1186  *
1187  * Returns: (transfer full): A newly allocated string representation of
1188  * @component suitable for @view.
1189  *
1190  * Since: 3.4
1191  */
1192 gchar *
e_data_cal_view_get_component_string(EDataCalView * view,ECalComponent * component)1193 e_data_cal_view_get_component_string (EDataCalView *view,
1194                                       ECalComponent *component)
1195 {
1196 	gchar *str = NULL, *res = NULL;
1197 
1198 	g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
1199 	g_return_val_if_fail (E_IS_CAL_COMPONENT (component), NULL);
1200 
1201 	if (view->priv->fields_of_interest) {
1202 		GString *string = g_string_new ("");
1203 		ICalComponent *icomp = e_cal_component_get_icalcomponent (component);
1204 
1205 		if (filter_component (icomp, view->priv->fields_of_interest, string))
1206 			str = g_string_free (string, FALSE);
1207 		else
1208 			g_string_free (string, TRUE);
1209 	}
1210 
1211 	if (!str)
1212 		str = e_cal_component_get_as_string (component);
1213 
1214 	if (e_util_ensure_gdbus_string (str, &res) == str)
1215 		res = str;
1216 	else
1217 		g_free (str);
1218 
1219 	return res;
1220 }
1221 
1222 /**
1223  * e_data_cal_view_notify_components_added:
1224  * @view: an #EDataCalView
1225  * @ecalcomponents: (element-type ECalComponent): List of #ECalComponent-s that have been added.
1226  *
1227  * Notifies all view listeners of the addition of a list of components.
1228  *
1229  * Uses the #EDataCalView's fields-of-interest to filter out unwanted
1230  * information from iCalendar strings sent over the bus.
1231  *
1232  * Since: 3.4
1233  */
1234 void
e_data_cal_view_notify_components_added(EDataCalView * view,const GSList * ecalcomponents)1235 e_data_cal_view_notify_components_added (EDataCalView *view,
1236                                          const GSList *ecalcomponents)
1237 {
1238 	const GSList *l;
1239 
1240 	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
1241 
1242 	if (ecalcomponents == NULL)
1243 		return;
1244 
1245 	g_mutex_lock (&view->priv->pending_mutex);
1246 
1247 	for (l = ecalcomponents; l; l = l->next) {
1248 		ECalComponent *comp = l->data;
1249 
1250 		g_warn_if_fail (E_IS_CAL_COMPONENT (comp));
1251 
1252 		notify_add_component (view, comp);
1253 	}
1254 
1255 	g_mutex_unlock (&view->priv->pending_mutex);
1256 }
1257 
1258 /**
1259  * e_data_cal_view_notify_components_added_1:
1260  * @view: an #EDataCalView
1261  * @component: The #ECalComponent that has been added.
1262  *
1263  * Notifies all the view listeners of the addition of a single object.
1264  *
1265  * Uses the #EDataCalView's fields-of-interest to filter out unwanted
1266  * information from iCalendar strings sent over the bus.
1267  *
1268  * Since: 3.4
1269  */
1270 void
e_data_cal_view_notify_components_added_1(EDataCalView * view,ECalComponent * component)1271 e_data_cal_view_notify_components_added_1 (EDataCalView *view,
1272                                            ECalComponent *component)
1273 {
1274 	GSList l = {NULL,};
1275 
1276 	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
1277 	g_return_if_fail (E_IS_CAL_COMPONENT (component));
1278 
1279 	l.data = (gpointer) component;
1280 	e_data_cal_view_notify_components_added (view, &l);
1281 }
1282 
1283 /**
1284  * e_data_cal_view_notify_components_modified:
1285  * @view: an #EDataCalView
1286  * @ecalcomponents: (element-type ECalComponent): List of modified #ECalComponent-s.
1287  *
1288  * Notifies all view listeners of the modification of a list of components.
1289  *
1290  * Uses the #EDataCalView's fields-of-interest to filter out unwanted
1291  * information from iCalendar strings sent over the bus.
1292  *
1293  * Since: 3.4
1294  */
1295 void
e_data_cal_view_notify_components_modified(EDataCalView * view,const GSList * ecalcomponents)1296 e_data_cal_view_notify_components_modified (EDataCalView *view,
1297                                             const GSList *ecalcomponents)
1298 {
1299 	const GSList *l;
1300 
1301 	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
1302 
1303 	if (ecalcomponents == NULL)
1304 		return;
1305 
1306 	g_mutex_lock (&view->priv->pending_mutex);
1307 
1308 	for (l = ecalcomponents; l; l = l->next) {
1309 		ECalComponent *comp = l->data;
1310 
1311 		g_warn_if_fail (E_IS_CAL_COMPONENT (comp));
1312 
1313 		notify_change_component (view, comp);
1314 	}
1315 
1316 	g_mutex_unlock (&view->priv->pending_mutex);
1317 }
1318 
1319 /**
1320  * e_data_cal_view_notify_components_modified_1:
1321  * @view: an #EDataCalView
1322  * @component: The modified #ECalComponent.
1323  *
1324  * Notifies all view listeners of the modification of @component.
1325  *
1326  * Uses the #EDataCalView's fields-of-interest to filter out unwanted
1327  * information from iCalendar strings sent over the bus.
1328  *
1329  * Since: 3.4
1330  */
1331 void
e_data_cal_view_notify_components_modified_1(EDataCalView * view,ECalComponent * component)1332 e_data_cal_view_notify_components_modified_1 (EDataCalView *view,
1333                                               ECalComponent *component)
1334 {
1335 	GSList l = {NULL,};
1336 
1337 	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
1338 	g_return_if_fail (E_IS_CAL_COMPONENT (component));
1339 
1340 	l.data = (gpointer) component;
1341 	e_data_cal_view_notify_components_modified (view, &l);
1342 }
1343 
1344 /**
1345  * e_data_cal_view_notify_objects_removed:
1346  * @view: an #EDataCalView
1347  * @ids: (element-type ECalComponentId): List of IDs for the objects that have been removed.
1348  *
1349  * Notifies all view listener of the removal of a list of objects.
1350  */
1351 void
e_data_cal_view_notify_objects_removed(EDataCalView * view,const GSList * ids)1352 e_data_cal_view_notify_objects_removed (EDataCalView *view,
1353                                         const GSList *ids)
1354 {
1355 	const GSList *l;
1356 
1357 	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
1358 
1359 	if (ids == NULL)
1360 		return;
1361 
1362 	g_mutex_lock (&view->priv->pending_mutex);
1363 
1364 	for (l = ids; l; l = l->next) {
1365 		ECalComponentId *id = l->data;
1366 		if (g_hash_table_lookup (view->priv->ids, id))
1367 		    notify_remove (view, id);
1368 	}
1369 
1370 	g_mutex_unlock (&view->priv->pending_mutex);
1371 }
1372 
1373 /**
1374  * e_data_cal_view_notify_objects_removed_1:
1375  * @view: an #EDataCalView
1376  * @id: ID of the removed object.
1377  *
1378  * Notifies all view listener of the removal of a single object.
1379  */
1380 void
e_data_cal_view_notify_objects_removed_1(EDataCalView * view,const ECalComponentId * id)1381 e_data_cal_view_notify_objects_removed_1 (EDataCalView *view,
1382                                           const ECalComponentId *id)
1383 {
1384 	GSList l = {NULL,};
1385 
1386 	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
1387 	g_return_if_fail (id != NULL);
1388 
1389 	l.data = (gpointer) id;
1390 	e_data_cal_view_notify_objects_removed (view, &l);
1391 }
1392 
1393 /**
1394  * e_data_cal_view_notify_progress:
1395  * @view: an #EDataCalView
1396  * @percent: Percentage completed.
1397  * @message: Progress message to send to listeners.
1398  *
1399  * Notifies all view listeners of progress messages.
1400  */
1401 void
e_data_cal_view_notify_progress(EDataCalView * view,gint percent,const gchar * message)1402 e_data_cal_view_notify_progress (EDataCalView *view,
1403                                  gint percent,
1404                                  const gchar *message)
1405 {
1406 	gchar *dbus_message = NULL;
1407 
1408 	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
1409 
1410 	if (!view->priv->started || view->priv->stopped)
1411 		return;
1412 
1413 	e_dbus_calendar_view_emit_progress (
1414 		view->priv->dbus_object, percent,
1415 		e_util_ensure_gdbus_string (message, &dbus_message));
1416 
1417 	g_free (dbus_message);
1418 }
1419 
1420 /**
1421  * e_data_cal_view_notify_complete:
1422  * @view: an #EDataCalView
1423  * @error: View completion error, if any.
1424  *
1425  * Notifies all view listeners of the completion of the view, including a
1426  * status code.
1427  *
1428  * Since: 3.2
1429  **/
1430 void
e_data_cal_view_notify_complete(EDataCalView * view,const GError * error)1431 e_data_cal_view_notify_complete (EDataCalView *view,
1432                                  const GError *error)
1433 {
1434 	gchar *error_name, *error_message;
1435 
1436 	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
1437 
1438 	if (!view->priv->started || view->priv->stopped)
1439 		return;
1440 
1441 	g_mutex_lock (&view->priv->pending_mutex);
1442 
1443 	view->priv->complete = TRUE;
1444 
1445 	send_pending_adds (view);
1446 	send_pending_changes (view);
1447 	send_pending_removes (view);
1448 
1449 	if (error) {
1450 		gchar *dbus_error_name = g_dbus_error_encode_gerror (error);
1451 
1452 		error_name = e_util_utf8_make_valid (dbus_error_name ? dbus_error_name : "");
1453 		error_message = e_util_utf8_make_valid (error->message);
1454 
1455 		g_free (dbus_error_name);
1456 	} else {
1457 		error_name = g_strdup ("");
1458 		error_message = g_strdup ("");
1459 	}
1460 
1461 	e_dbus_calendar_view_emit_complete (
1462 		view->priv->dbus_object,
1463 		error_name,
1464 		error_message);
1465 
1466 	g_free (error_name);
1467 	g_free (error_message);
1468 
1469 	g_mutex_unlock (&view->priv->pending_mutex);
1470 }
1471 
1472