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