1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 /**
19 * SECTION: e-source-registry-watcher
20 * @include: libedataserver/libedataserver.h
21 * @short_description: Watch changes in #ESource-s
22 *
23 * #ESourceRegistryWatcher watches for changes in an #ESourceRegistry
24 * and notifies about newly added and enabled #ESource instances, the same
25 * as about removed or disabled. The amount of notifications can be filtered
26 * with #ESourceRegistryWatcher::filter signal.
27 *
28 * The watcher listens only for changes, thus it is not pre-populated after
29 * its creation. That's because the owner usually wants to subscribe to
30 * the #ESourceRegistryWatcher::filter, #ESourceRegistryWatcher::appeared
31 * and #ESourceRegistryWatcher::disappeared signals. The owner should
32 * call e_source_registry_watcher_reclaim() when it has all the needed
33 * signal handlers connected.
34 **/
35
36 #include "evolution-data-server-config.h"
37
38 #include "e-source-registry.h"
39 #include "e-source.h"
40 #include "e-source-collection.h"
41
42 #include "e-source-registry-watcher.h"
43
44 struct _ESourceRegistryWatcherPrivate {
45 ESourceRegistry *registry;
46 gchar *extension_name;
47
48 GHashTable *known_uids; /* gchar * UID ~> ESource */
49
50 GRecMutex lock;
51
52 gulong added_id;
53 gulong enabled_id;
54 gulong disabled_id;
55 gulong removed_id;
56 gulong changed_id;
57 };
58
59 G_DEFINE_TYPE_WITH_PRIVATE (ESourceRegistryWatcher, e_source_registry_watcher, G_TYPE_OBJECT)
60
61 enum {
62 PROP_0,
63 PROP_EXTENSION_NAME,
64 PROP_REGISTRY
65 };
66
67 enum {
68 FILTER,
69 APPEARED,
70 DISAPPEARED,
71 LAST_SIGNAL
72 };
73
74 static guint signals[LAST_SIGNAL];
75
76 static gboolean
source_registry_watcher_try_remove(ESourceRegistryWatcher * watcher,ESource * source)77 source_registry_watcher_try_remove (ESourceRegistryWatcher *watcher,
78 ESource *source)
79 {
80 gboolean removed;
81
82 g_return_val_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher), FALSE);
83 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
84
85 g_rec_mutex_lock (&watcher->priv->lock);
86 removed = g_hash_table_remove (watcher->priv->known_uids, e_source_get_uid (source));
87 g_rec_mutex_unlock (&watcher->priv->lock);
88
89 if (removed)
90 g_signal_emit (watcher, signals[DISAPPEARED], 0, source);
91
92 return removed;
93 }
94
95 static gboolean
source_registry_watcher_try_add(ESourceRegistryWatcher * watcher,ESource * source,gboolean with_remove_check,gboolean skip_appeared_emit)96 source_registry_watcher_try_add (ESourceRegistryWatcher *watcher,
97 ESource *source,
98 gboolean with_remove_check,
99 gboolean skip_appeared_emit)
100 {
101 gboolean can_include = TRUE;
102 gboolean added = FALSE;
103 gchar *uid;
104
105 g_return_val_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher), FALSE);
106 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
107
108 uid = e_source_dup_uid (source);
109 if (!uid)
110 return FALSE;
111
112 /* Check whether anything is listening, because glib overrides the result
113 value with FALSE, when there's nothing listening. */
114 if (g_signal_has_handler_pending (watcher, signals[FILTER], 0, FALSE))
115 g_signal_emit (watcher, signals[FILTER], 0, source, &can_include);
116
117 if (!can_include) {
118 if (with_remove_check)
119 source_registry_watcher_try_remove (watcher, source);
120
121 g_free (uid);
122 return FALSE;
123 }
124
125 g_rec_mutex_lock (&watcher->priv->lock);
126
127 if (!g_hash_table_contains (watcher->priv->known_uids, uid)) {
128 g_hash_table_insert (watcher->priv->known_uids, uid, g_object_ref (source));
129 added = TRUE;
130 } else {
131 g_free (uid);
132 }
133
134 g_rec_mutex_unlock (&watcher->priv->lock);
135
136 if (added && !skip_appeared_emit)
137 g_signal_emit (watcher, signals[APPEARED], 0, source);
138
139 return added;
140 }
141
142 static void
source_registry_watcher_reclaim_internal(ESourceRegistryWatcher * watcher,gboolean merge_like)143 source_registry_watcher_reclaim_internal (ESourceRegistryWatcher *watcher,
144 gboolean merge_like)
145 {
146 GHashTable *old_known_uids = NULL;
147 GList *enabled, *link;
148
149 g_return_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher));
150
151 g_rec_mutex_lock (&watcher->priv->lock);
152 if (merge_like) {
153 old_known_uids = watcher->priv->known_uids;
154 watcher->priv->known_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
155 } else {
156 g_hash_table_remove_all (watcher->priv->known_uids);
157 }
158
159 enabled = e_source_registry_list_enabled (watcher->priv->registry, watcher->priv->extension_name);
160 for (link = enabled; link; link = g_list_next (link)) {
161 ESource *source = link->data;
162 gboolean skip_appeared_emit;
163
164 skip_appeared_emit = old_known_uids && g_hash_table_contains (old_known_uids, e_source_get_uid (source));
165
166 if (source_registry_watcher_try_add (watcher, source, FALSE, skip_appeared_emit) && old_known_uids)
167 g_hash_table_remove (old_known_uids, e_source_get_uid (source));
168 }
169 g_list_free_full (enabled, g_object_unref);
170
171 g_rec_mutex_unlock (&watcher->priv->lock);
172
173 if (old_known_uids) {
174 GHashTableIter iter;
175 gpointer value;
176
177 g_hash_table_iter_init (&iter, old_known_uids);
178 while (g_hash_table_iter_next (&iter, NULL, &value)) {
179 ESource *source = value;
180
181 g_signal_emit (watcher, signals[DISAPPEARED], 0, source);
182 }
183
184 g_hash_table_destroy (old_known_uids);
185 }
186 }
187
188 static void
source_registry_watcher_source_added_or_enabled_cb(ESourceRegistry * registry,ESource * source,gpointer user_data)189 source_registry_watcher_source_added_or_enabled_cb (ESourceRegistry *registry,
190 ESource *source,
191 gpointer user_data)
192 {
193 ESourceRegistryWatcher *watcher = user_data;
194
195 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
196 g_return_if_fail (E_IS_SOURCE (source));
197 g_return_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher));
198
199 if (e_source_registry_check_enabled (registry, source))
200 source_registry_watcher_try_add (watcher, source, TRUE, FALSE);
201 }
202
203 static void
source_registry_watcher_source_removed_or_disabled_cb(ESourceRegistry * registry,ESource * source,gpointer user_data)204 source_registry_watcher_source_removed_or_disabled_cb (ESourceRegistry *registry,
205 ESource *source,
206 gpointer user_data)
207 {
208 ESourceRegistryWatcher *watcher = user_data;
209
210 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
211 g_return_if_fail (E_IS_SOURCE (source));
212 g_return_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher));
213
214 source_registry_watcher_try_remove (watcher, source);
215 }
216
217 static void
source_registry_watcher_source_changed_cb(ESourceRegistry * registry,ESource * source,gpointer user_data)218 source_registry_watcher_source_changed_cb (ESourceRegistry *registry,
219 ESource *source,
220 gpointer user_data)
221 {
222 ESourceRegistryWatcher *watcher = user_data;
223
224 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
225 g_return_if_fail (E_IS_SOURCE (source));
226 g_return_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher));
227
228 if (!watcher->priv->extension_name ||
229 e_source_has_extension (source, watcher->priv->extension_name)) {
230 if (e_source_registry_check_enabled (registry, source))
231 source_registry_watcher_try_add (watcher, source, TRUE, FALSE);
232 else
233 source_registry_watcher_try_remove (watcher, source);
234 }
235 }
236
237 static void
source_registry_watcher_set_registry(ESourceRegistryWatcher * watcher,ESourceRegistry * registry)238 source_registry_watcher_set_registry (ESourceRegistryWatcher *watcher,
239 ESourceRegistry *registry)
240 {
241 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
242 g_return_if_fail (watcher->priv->registry == NULL);
243
244 watcher->priv->registry = g_object_ref (registry);
245 }
246
247 static void
source_registry_watcher_set_extension_name(ESourceRegistryWatcher * watcher,const gchar * extension_name)248 source_registry_watcher_set_extension_name (ESourceRegistryWatcher *watcher,
249 const gchar *extension_name)
250 {
251 if (g_strcmp0 (watcher->priv->extension_name, extension_name) != 0) {
252 g_free (watcher->priv->extension_name);
253 watcher->priv->extension_name = g_strdup (extension_name);
254 }
255 }
256
257 static void
source_registry_watcher_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)258 source_registry_watcher_set_property (GObject *object,
259 guint property_id,
260 const GValue *value,
261 GParamSpec *pspec)
262 {
263 switch (property_id) {
264 case PROP_EXTENSION_NAME:
265 source_registry_watcher_set_extension_name (
266 E_SOURCE_REGISTRY_WATCHER (object),
267 g_value_get_string (value));
268 return;
269
270 case PROP_REGISTRY:
271 source_registry_watcher_set_registry (
272 E_SOURCE_REGISTRY_WATCHER (object),
273 g_value_get_object (value));
274 return;
275 }
276
277 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
278 }
279
280 static void
source_registry_watcher_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)281 source_registry_watcher_get_property (GObject *object,
282 guint property_id,
283 GValue *value,
284 GParamSpec *pspec)
285 {
286 switch (property_id) {
287 case PROP_EXTENSION_NAME:
288 g_value_set_string (
289 value,
290 e_source_registry_watcher_get_extension_name (
291 E_SOURCE_REGISTRY_WATCHER (object)));
292 return;
293
294 case PROP_REGISTRY:
295 g_value_set_object (
296 value,
297 e_source_registry_watcher_get_registry (
298 E_SOURCE_REGISTRY_WATCHER (object)));
299 return;
300 }
301
302 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
303 }
304
305 static void
source_registry_watcher_constructed(GObject * object)306 source_registry_watcher_constructed (GObject *object)
307 {
308 ESourceRegistryWatcher *watcher = E_SOURCE_REGISTRY_WATCHER (object);
309
310 /* Chain up to parent's method. */
311 G_OBJECT_CLASS (e_source_registry_watcher_parent_class)->constructed (object);
312
313 g_return_if_fail (watcher->priv->registry != NULL);
314
315 watcher->priv->added_id = g_signal_connect (watcher->priv->registry, "source-added",
316 G_CALLBACK (source_registry_watcher_source_added_or_enabled_cb), watcher);
317
318 watcher->priv->enabled_id = g_signal_connect (watcher->priv->registry, "source-enabled",
319 G_CALLBACK (source_registry_watcher_source_added_or_enabled_cb), watcher);
320
321 watcher->priv->disabled_id = g_signal_connect (watcher->priv->registry, "source-disabled",
322 G_CALLBACK (source_registry_watcher_source_removed_or_disabled_cb), watcher);
323
324 watcher->priv->removed_id = g_signal_connect (watcher->priv->registry, "source-removed",
325 G_CALLBACK (source_registry_watcher_source_removed_or_disabled_cb), watcher);
326
327 watcher->priv->changed_id = g_signal_connect (watcher->priv->registry, "source-changed",
328 G_CALLBACK (source_registry_watcher_source_changed_cb), watcher);
329 }
330
331 static void
source_registry_watcher_dispose(GObject * object)332 source_registry_watcher_dispose (GObject *object)
333 {
334 ESourceRegistryWatcher *watcher = E_SOURCE_REGISTRY_WATCHER (object);
335
336 #define unset_handler(x) G_STMT_START { \
337 if (x) { \
338 g_signal_handler_disconnect (watcher->priv->registry, x); \
339 x = 0; \
340 } } G_STMT_END
341
342 unset_handler (watcher->priv->added_id);
343 unset_handler (watcher->priv->enabled_id);
344 unset_handler (watcher->priv->disabled_id);
345 unset_handler (watcher->priv->removed_id);
346 unset_handler (watcher->priv->changed_id);
347
348 #undef unset_handler
349
350 g_clear_object (&watcher->priv->registry);
351
352 g_hash_table_remove_all (watcher->priv->known_uids);
353
354 /* Chain up to parent's method. */
355 G_OBJECT_CLASS (e_source_registry_watcher_parent_class)->dispose (object);
356 }
357
358 static void
source_registry_watcher_finalize(GObject * object)359 source_registry_watcher_finalize (GObject *object)
360 {
361 ESourceRegistryWatcher *watcher = E_SOURCE_REGISTRY_WATCHER (object);
362
363 g_hash_table_destroy (watcher->priv->known_uids);
364 g_free (watcher->priv->extension_name);
365 g_rec_mutex_clear (&watcher->priv->lock);
366
367 /* Chain up to parent's method. */
368 G_OBJECT_CLASS (e_source_registry_watcher_parent_class)->finalize (object);
369 }
370
371 static void
e_source_registry_watcher_class_init(ESourceRegistryWatcherClass * klass)372 e_source_registry_watcher_class_init (ESourceRegistryWatcherClass *klass)
373 {
374 GObjectClass *object_class;
375
376 object_class = G_OBJECT_CLASS (klass);
377 object_class->set_property = source_registry_watcher_set_property;
378 object_class->get_property = source_registry_watcher_get_property;
379 object_class->constructed = source_registry_watcher_constructed;
380 object_class->dispose = source_registry_watcher_dispose;
381 object_class->finalize = source_registry_watcher_finalize;
382
383 /**
384 * ESourceRegistryWatcher:extension-name:
385 *
386 * Optional extension name, to consider sources with only.
387 * It can be %NULL, to check for all sources. This is
388 * a complementary filter to #ESourceRegistryWatcher::filter
389 * signal.
390 *
391 * Since: 3.26
392 **/
393 g_object_class_install_property (
394 object_class,
395 PROP_EXTENSION_NAME,
396 g_param_spec_string (
397 "extension-name",
398 "ExtensionName",
399 NULL,
400 NULL,
401 G_PARAM_READWRITE |
402 G_PARAM_CONSTRUCT_ONLY |
403 G_PARAM_STATIC_STRINGS));
404
405 /**
406 * ESourceRegistryWatcher:registry:
407 *
408 * The #ESourceRegistry manages #ESource instances.
409 *
410 * Since: 3.26
411 **/
412 g_object_class_install_property (
413 object_class,
414 PROP_REGISTRY,
415 g_param_spec_object (
416 "registry",
417 "Registry",
418 "Data source registry",
419 E_TYPE_SOURCE_REGISTRY,
420 G_PARAM_READWRITE |
421 G_PARAM_CONSTRUCT_ONLY |
422 G_PARAM_STATIC_STRINGS));
423
424 /**
425 * ESourceRegistryWatcher::filter:
426 * @watcher: the #ESourceRegistryWatcher that received the signal
427 * @source: the #ESource to filter
428 *
429 * A filter signal which verifies whether the @source can be considered
430 * for inclusion in the watcher or not. If none is set then all the sources
431 * are included.
432 *
433 * Returns: %TRUE, when the @source can be included, %FALSE otherwise.
434 *
435 * Since: 3.26
436 **/
437 signals[FILTER] = g_signal_new (
438 "filter",
439 G_TYPE_FROM_CLASS (klass),
440 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
441 G_STRUCT_OFFSET (ESourceRegistryWatcherClass, filter),
442 NULL, NULL, NULL,
443 G_TYPE_BOOLEAN, 1,
444 E_TYPE_SOURCE);
445
446 /**
447 * ESourceRegistryWatcher::appeared:
448 * @watcher: the #ESourceRegistryWatcher that received the signal
449 * @source: the #ESource which appeared
450 *
451 * A signal emitted when the @source is enabled or added and it had been
452 * considered for inclusion with the @ESourceRegistryWatcher::filter signal.
453 *
454 * Since: 3.26
455 **/
456 signals[APPEARED] = g_signal_new (
457 "appeared",
458 G_TYPE_FROM_CLASS (klass),
459 G_SIGNAL_RUN_LAST,
460 G_STRUCT_OFFSET (ESourceRegistryWatcherClass, appeared),
461 NULL, NULL, NULL,
462 G_TYPE_NONE, 1,
463 E_TYPE_SOURCE);
464
465 /**
466 * ESourceRegistryWatcher::disappeared:
467 * @watcher: the #ESourceRegistryWatcher that received the signal
468 * @source: the #ESource which disappeared
469 *
470 * A signal emitted when the @source is disabled or removed and it had been
471 * considered for inclusion with the @ESourceRegistryWatcher::filter signal
472 * earlier.
473 *
474 * Since: 3.26
475 **/
476 signals[DISAPPEARED] = g_signal_new (
477 "disappeared",
478 G_TYPE_FROM_CLASS (klass),
479 G_SIGNAL_RUN_LAST,
480 G_STRUCT_OFFSET (ESourceRegistryWatcherClass, disappeared),
481 NULL, NULL, NULL,
482 G_TYPE_NONE, 1,
483 E_TYPE_SOURCE);
484 }
485
486 static void
e_source_registry_watcher_init(ESourceRegistryWatcher * watcher)487 e_source_registry_watcher_init (ESourceRegistryWatcher *watcher)
488 {
489 watcher->priv = e_source_registry_watcher_get_instance_private (watcher);
490
491 g_rec_mutex_init (&watcher->priv->lock);
492
493 watcher->priv->known_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
494 }
495
496 /**
497 * e_source_registry_watcher_new:
498 * @registry: an #ESourceRegistry
499 * @extension_name: (nullable): optional extension name to filter sources with, or %NULL
500 *
501 * Creates a new #ESourceRegistryWatcher instance.
502 *
503 * The @extension_name can be used as a complementary filter
504 * to #ESourceRegistryWatcher::filter signal.
505 *
506 * Returns: (transfer full): an #ESourceRegistryWatcher
507 *
508 * Since: 3.26
509 **/
510 ESourceRegistryWatcher *
e_source_registry_watcher_new(ESourceRegistry * registry,const gchar * extension_name)511 e_source_registry_watcher_new (ESourceRegistry *registry,
512 const gchar *extension_name)
513 {
514 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
515
516 return g_object_new (E_TYPE_SOURCE_REGISTRY_WATCHER,
517 "registry", registry,
518 "extension-name", extension_name,
519 NULL);
520 }
521
522 /**
523 * e_source_registry_watcher_get_registry:
524 * @watcher: an #ESourceRegistryWatcher
525 *
526 * Returns the #ESourceRegistry passed to e_source_registry_watcher_new().
527 *
528 * Returns: (transfer none): an #ESourceRegistry
529 *
530 * Since: 3.26
531 **/
532 ESourceRegistry *
e_source_registry_watcher_get_registry(ESourceRegistryWatcher * watcher)533 e_source_registry_watcher_get_registry (ESourceRegistryWatcher *watcher)
534 {
535 g_return_val_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher), NULL);
536
537 return watcher->priv->registry;
538 }
539
540 /**
541 * e_source_registry_watcher_get_extension_name:
542 * @watcher: an #ESourceRegistryWatcher
543 *
544 * Returns: (nullable): The extension name passed to e_source_registry_watcher_new().
545 *
546 * Since: 3.26
547 **/
548 const gchar *
e_source_registry_watcher_get_extension_name(ESourceRegistryWatcher * watcher)549 e_source_registry_watcher_get_extension_name (ESourceRegistryWatcher *watcher)
550 {
551 g_return_val_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher), NULL);
552
553 return watcher->priv->extension_name;
554 }
555
556 /**
557 * e_source_registry_watcher_reclaim:
558 * @watcher: an #ESourceRegistryWatcher
559 *
560 * Reclaims all available sources satisfying the #ESourceRegistryWatcher::filter
561 * signal. It doesn't notify about disappeared sources, it notifies only
562 * on those appeared.
563 *
564 * Since: 3.26
565 **/
566 void
e_source_registry_watcher_reclaim(ESourceRegistryWatcher * watcher)567 e_source_registry_watcher_reclaim (ESourceRegistryWatcher *watcher)
568 {
569 g_return_if_fail (E_IS_SOURCE_REGISTRY_WATCHER (watcher));
570
571 source_registry_watcher_reclaim_internal (watcher, FALSE);
572 }
573