Updated Spanish translation
[evolution.git] / e-util / e-client-cache.c
blob2c084c33122af337d81f50c4e45e7f19eb7221e6
1 /*
2 * e-client-cache.c
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 /**
19 * SECTION: e-client-cache
20 * @include: e-util/e-util.h
21 * @short_description: Shared #EClient instances
23 * #EClientCache provides for application-wide sharing of #EClient
24 * instances and centralized rebroadcasting of #EClient::backend-died,
25 * #EClient::backend-error and #GObject::notify signals from cached
26 * #EClient instances.
28 * #EClientCache automatically invalidates cache entries in response to
29 * #EClient::backend-died signals. The #EClient instance is discarded,
30 * and a new instance is created on the next request.
31 **/
33 #include "e-client-cache.h"
35 #include <config.h>
36 #include <glib/gi18n-lib.h>
38 #include <libecal/libecal.h>
39 #include <libebook/libebook.h>
40 #include <libebackend/libebackend.h>
42 #define E_CLIENT_CACHE_GET_PRIVATE(obj) \
43 (G_TYPE_INSTANCE_GET_PRIVATE \
44 ((obj), E_TYPE_CLIENT_CACHE, EClientCachePrivate))
46 typedef struct _ClientData ClientData;
47 typedef struct _SignalClosure SignalClosure;
49 struct _EClientCachePrivate {
50 ESourceRegistry *registry;
51 gulong source_removed_handler_id;
52 gulong source_disabled_handler_id;
54 GHashTable *client_ht;
55 GMutex client_ht_lock;
57 /* For signal emissions. */
58 GMainContext *main_context;
61 struct _ClientData {
62 volatile gint ref_count;
63 GMutex lock;
64 GWeakRef client_cache;
65 EClient *client;
66 GQueue connecting;
67 gboolean dead_backend;
68 gulong backend_died_handler_id;
69 gulong backend_error_handler_id;
70 gulong notify_handler_id;
73 struct _SignalClosure {
74 EClientCache *client_cache;
75 EClient *client;
76 GParamSpec *pspec;
77 gchar *error_message;
80 G_DEFINE_TYPE_WITH_CODE (
81 EClientCache,
82 e_client_cache,
83 G_TYPE_OBJECT,
84 G_IMPLEMENT_INTERFACE (
85 E_TYPE_EXTENSIBLE, NULL))
87 enum {
88 PROP_0,
89 PROP_REGISTRY
92 enum {
93 BACKEND_DIED,
94 BACKEND_ERROR,
95 CLIENT_CONNECTED,
96 CLIENT_CREATED,
97 CLIENT_NOTIFY,
98 ALLOW_AUTH_PROMPT,
99 LAST_SIGNAL
102 static guint signals[LAST_SIGNAL];
104 static ClientData *
105 client_data_new (EClientCache *client_cache)
107 ClientData *client_data;
109 client_data = g_slice_new0 (ClientData);
110 client_data->ref_count = 1;
111 g_mutex_init (&client_data->lock);
112 g_weak_ref_set (&client_data->client_cache, client_cache);
114 return client_data;
117 static ClientData *
118 client_data_ref (ClientData *client_data)
120 g_return_val_if_fail (client_data != NULL, NULL);
121 g_return_val_if_fail (client_data->ref_count > 0, NULL);
123 g_atomic_int_inc (&client_data->ref_count);
125 return client_data;
128 static void
129 client_data_unref (ClientData *client_data)
131 g_return_if_fail (client_data != NULL);
132 g_return_if_fail (client_data->ref_count > 0);
134 if (g_atomic_int_dec_and_test (&client_data->ref_count)) {
136 /* The signal handlers hold a reference on client_data,
137 * so we should not be here unless the signal handlers
138 * have already been disconnected. */
139 g_warn_if_fail (client_data->backend_died_handler_id == 0);
140 g_warn_if_fail (client_data->backend_error_handler_id == 0);
141 g_warn_if_fail (client_data->notify_handler_id == 0);
143 g_mutex_clear (&client_data->lock);
144 g_clear_object (&client_data->client);
145 g_weak_ref_set (&client_data->client_cache, NULL);
147 /* There should be no connect() operations in progress. */
148 g_warn_if_fail (g_queue_is_empty (&client_data->connecting));
150 g_slice_free (ClientData, client_data);
154 static void
155 client_data_dispose (ClientData *client_data)
157 g_mutex_lock (&client_data->lock);
159 if (client_data->client != NULL) {
160 g_signal_handler_disconnect (
161 client_data->client,
162 client_data->backend_died_handler_id);
163 client_data->backend_died_handler_id = 0;
165 g_signal_handler_disconnect (
166 client_data->client,
167 client_data->backend_error_handler_id);
168 client_data->backend_error_handler_id = 0;
170 g_signal_handler_disconnect (
171 client_data->client,
172 client_data->notify_handler_id);
173 client_data->notify_handler_id = 0;
175 g_clear_object (&client_data->client);
178 g_mutex_unlock (&client_data->lock);
180 client_data_unref (client_data);
183 static void
184 signal_closure_free (SignalClosure *signal_closure)
186 g_clear_object (&signal_closure->client_cache);
187 g_clear_object (&signal_closure->client);
189 if (signal_closure->pspec != NULL)
190 g_param_spec_unref (signal_closure->pspec);
192 g_free (signal_closure->error_message);
194 g_slice_free (SignalClosure, signal_closure);
197 static ClientData *
198 client_ht_lookup (EClientCache *client_cache,
199 ESource *source,
200 const gchar *extension_name)
202 GHashTable *client_ht;
203 GHashTable *inner_ht;
204 ClientData *client_data = NULL;
206 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
207 g_return_val_if_fail (extension_name != NULL, NULL);
209 client_ht = client_cache->priv->client_ht;
211 g_mutex_lock (&client_cache->priv->client_ht_lock);
213 /* We pre-load the hash table with supported extension names,
214 * so lookup failures indicate an unsupported extension name. */
215 inner_ht = g_hash_table_lookup (client_ht, extension_name);
216 if (inner_ht != NULL) {
217 client_data = g_hash_table_lookup (inner_ht, source);
218 if (client_data == NULL) {
219 g_object_ref (source);
220 client_data = client_data_new (client_cache);
221 g_hash_table_insert (inner_ht, source, client_data);
223 client_data_ref (client_data);
226 g_mutex_unlock (&client_cache->priv->client_ht_lock);
228 return client_data;
231 static gboolean
232 client_ht_remove (EClientCache *client_cache,
233 ESource *source)
235 GHashTable *client_ht;
236 GHashTableIter client_ht_iter;
237 gpointer inner_ht;
238 gboolean removed = FALSE;
240 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
242 client_ht = client_cache->priv->client_ht;
244 g_mutex_lock (&client_cache->priv->client_ht_lock);
246 g_hash_table_iter_init (&client_ht_iter, client_ht);
248 while (g_hash_table_iter_next (&client_ht_iter, NULL, &inner_ht))
249 removed |= g_hash_table_remove (inner_ht, source);
251 g_mutex_unlock (&client_cache->priv->client_ht_lock);
253 return removed;
256 static gboolean
257 client_cache_emit_backend_died_idle_cb (gpointer user_data)
259 SignalClosure *signal_closure = user_data;
260 ESourceRegistry *registry;
261 EAlert *alert;
262 ESource *source;
263 const gchar *alert_id = NULL;
264 const gchar *extension_name;
265 gchar *display_name = NULL;
267 source = e_client_get_source (signal_closure->client);
268 registry = e_client_cache_ref_registry (signal_closure->client_cache);
270 extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
271 if (e_source_has_extension (source, extension_name)) {
272 alert_id = "system:address-book-backend-died";
273 display_name = e_source_registry_dup_unique_display_name (
274 registry, source, extension_name);
277 extension_name = E_SOURCE_EXTENSION_CALENDAR;
278 if (e_source_has_extension (source, extension_name)) {
279 alert_id = "system:calendar-backend-died";
280 display_name = e_source_registry_dup_unique_display_name (
281 registry, source, extension_name);
284 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
285 if (e_source_has_extension (source, extension_name)) {
286 alert_id = "system:memo-list-backend-died";
287 display_name = e_source_registry_dup_unique_display_name (
288 registry, source, extension_name);
291 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
292 if (e_source_has_extension (source, extension_name)) {
293 alert_id = "system:task-list-backend-died";
294 display_name = e_source_registry_dup_unique_display_name (
295 registry, source, extension_name);
298 g_object_unref (registry);
300 g_return_val_if_fail (alert_id != NULL, FALSE);
301 g_return_val_if_fail (display_name != NULL, FALSE);
303 alert = e_alert_new (alert_id, display_name, NULL);
305 g_signal_emit (
306 signal_closure->client_cache,
307 signals[BACKEND_DIED], 0,
308 signal_closure->client,
309 alert);
311 g_object_unref (alert);
313 g_free (display_name);
315 return FALSE;
318 static gboolean
319 client_cache_emit_backend_error_idle_cb (gpointer user_data)
321 SignalClosure *signal_closure = user_data;
322 ESourceRegistry *registry;
323 EAlert *alert;
324 ESource *source;
325 const gchar *alert_id = NULL;
326 const gchar *extension_name;
327 gchar *display_name = NULL;
329 source = e_client_get_source (signal_closure->client);
330 registry = e_client_cache_ref_registry (signal_closure->client_cache);
332 extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
333 if (e_source_has_extension (source, extension_name)) {
334 alert_id = "system:address-book-backend-error";
335 display_name = e_source_registry_dup_unique_display_name (
336 registry, source, extension_name);
339 extension_name = E_SOURCE_EXTENSION_CALENDAR;
340 if (e_source_has_extension (source, extension_name)) {
341 alert_id = "system:calendar-backend-error";
342 display_name = e_source_registry_dup_unique_display_name (
343 registry, source, extension_name);
346 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
347 if (e_source_has_extension (source, extension_name)) {
348 alert_id = "system:memo-list-backend-error";
349 display_name = e_source_registry_dup_unique_display_name (
350 registry, source, extension_name);
353 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
354 if (e_source_has_extension (source, extension_name)) {
355 alert_id = "system:task-list-backend-error";
356 display_name = e_source_registry_dup_unique_display_name (
357 registry, source, extension_name);
360 g_object_unref (registry);
362 g_return_val_if_fail (alert_id != NULL, FALSE);
363 g_return_val_if_fail (display_name != NULL, FALSE);
365 alert = e_alert_new (
366 alert_id, display_name,
367 signal_closure->error_message, NULL);
369 g_signal_emit (
370 signal_closure->client_cache,
371 signals[BACKEND_ERROR], 0,
372 signal_closure->client,
373 alert);
375 g_object_unref (alert);
377 g_free (display_name);
379 return FALSE;
382 static gboolean
383 client_cache_emit_client_notify_idle_cb (gpointer user_data)
385 SignalClosure *signal_closure = user_data;
386 const gchar *name;
388 name = g_param_spec_get_name (signal_closure->pspec);
390 g_signal_emit (
391 signal_closure->client_cache,
392 signals[CLIENT_NOTIFY],
393 g_quark_from_string (name),
394 signal_closure->client,
395 signal_closure->pspec);
397 return FALSE;
400 static gboolean
401 client_cache_emit_client_created_idle_cb (gpointer user_data)
403 SignalClosure *signal_closure = user_data;
405 g_signal_emit (
406 signal_closure->client_cache,
407 signals[CLIENT_CREATED], 0,
408 signal_closure->client);
410 return FALSE;
413 static void
414 client_cache_backend_died_cb (EClient *client,
415 ClientData *client_data)
417 EClientCache *client_cache;
419 client_cache = g_weak_ref_get (&client_data->client_cache);
421 if (client_cache != NULL) {
422 GSource *idle_source;
423 SignalClosure *signal_closure;
425 signal_closure = g_slice_new0 (SignalClosure);
426 signal_closure->client_cache = g_object_ref (client_cache);
427 signal_closure->client = g_object_ref (client);
429 idle_source = g_idle_source_new ();
430 g_source_set_callback (
431 idle_source,
432 client_cache_emit_backend_died_idle_cb,
433 signal_closure,
434 (GDestroyNotify) signal_closure_free);
435 g_source_attach (
436 idle_source, client_cache->priv->main_context);
437 g_source_unref (idle_source);
439 g_object_unref (client_cache);
442 /* Discard the EClient and tag the backend as
443 * dead until we create a replacement EClient. */
444 g_mutex_lock (&client_data->lock);
445 g_clear_object (&client_data->client);
446 client_data->dead_backend = TRUE;
447 g_mutex_unlock (&client_data->lock);
451 static void
452 client_cache_backend_error_cb (EClient *client,
453 const gchar *error_message,
454 ClientData *client_data)
456 EClientCache *client_cache;
458 client_cache = g_weak_ref_get (&client_data->client_cache);
460 if (client_cache != NULL) {
461 GSource *idle_source;
462 SignalClosure *signal_closure;
464 signal_closure = g_slice_new0 (SignalClosure);
465 signal_closure->client_cache = g_object_ref (client_cache);
466 signal_closure->client = g_object_ref (client);
467 signal_closure->error_message = g_strdup (error_message);
469 idle_source = g_idle_source_new ();
470 g_source_set_callback (
471 idle_source,
472 client_cache_emit_backend_error_idle_cb,
473 signal_closure,
474 (GDestroyNotify) signal_closure_free);
475 g_source_attach (
476 idle_source, client_cache->priv->main_context);
477 g_source_unref (idle_source);
479 g_object_unref (client_cache);
483 static void
484 client_cache_notify_cb (EClient *client,
485 GParamSpec *pspec,
486 ClientData *client_data)
488 EClientCache *client_cache;
490 client_cache = g_weak_ref_get (&client_data->client_cache);
492 if (client_cache != NULL) {
493 GSource *idle_source;
494 SignalClosure *signal_closure;
496 signal_closure = g_slice_new0 (SignalClosure);
497 signal_closure->client_cache = g_object_ref (client_cache);
498 signal_closure->client = g_object_ref (client);
499 signal_closure->pspec = g_param_spec_ref (pspec);
501 idle_source = g_idle_source_new ();
502 g_source_set_callback (
503 idle_source,
504 client_cache_emit_client_notify_idle_cb,
505 signal_closure,
506 (GDestroyNotify) signal_closure_free);
507 g_source_attach (
508 idle_source, client_cache->priv->main_context);
509 g_source_unref (idle_source);
511 g_object_unref (client_cache);
515 static void
516 client_cache_process_results (ClientData *client_data,
517 EClient *client,
518 const GError *error)
520 GQueue queue = G_QUEUE_INIT;
522 /* Sanity check. */
523 g_return_if_fail (
524 ((client != NULL) && (error == NULL)) ||
525 ((client == NULL) && (error != NULL)));
527 g_mutex_lock (&client_data->lock);
529 /* Complete async operations outside the lock. */
530 e_queue_transfer (&client_data->connecting, &queue);
532 if (client != NULL) {
533 EClientCache *client_cache;
535 /* Make sure we're not leaking a reference. This can happen when
536 a synchronous and an asynchronous open are interleaving. The
537 synchronous open bypasses pending openings, thus can eventually
538 overwrite, or preset, the client.
540 g_clear_object (&client_data->client);
542 client_data->client = g_object_ref (client);
543 client_data->dead_backend = FALSE;
545 client_cache = g_weak_ref_get (&client_data->client_cache);
547 /* If the EClientCache has been disposed already,
548 * there's no point in connecting signal handlers. */
549 if (client_cache != NULL) {
550 GSource *idle_source;
551 SignalClosure *signal_closure;
552 gulong handler_id;
554 /* client_data_dispose() will break the
555 * reference cycles we're creating here. */
557 handler_id = g_signal_connect_data (
558 client, "backend-died",
559 G_CALLBACK (client_cache_backend_died_cb),
560 client_data_ref (client_data),
561 (GClosureNotify) client_data_unref,
563 client_data->backend_died_handler_id = handler_id;
565 handler_id = g_signal_connect_data (
566 client, "backend-error",
567 G_CALLBACK (client_cache_backend_error_cb),
568 client_data_ref (client_data),
569 (GClosureNotify) client_data_unref,
571 client_data->backend_error_handler_id = handler_id;
573 handler_id = g_signal_connect_data (
574 client, "notify",
575 G_CALLBACK (client_cache_notify_cb),
576 client_data_ref (client_data),
577 (GClosureNotify) client_data_unref,
579 client_data->notify_handler_id = handler_id;
581 g_signal_emit (client_cache, signals[CLIENT_CONNECTED], 0, client);
583 signal_closure = g_slice_new0 (SignalClosure);
584 signal_closure->client_cache =
585 g_object_ref (client_cache);
586 signal_closure->client = g_object_ref (client);
588 idle_source = g_idle_source_new ();
589 g_source_set_callback (
590 idle_source,
591 client_cache_emit_client_created_idle_cb,
592 signal_closure,
593 (GDestroyNotify) signal_closure_free);
594 g_source_attach (
595 idle_source, client_cache->priv->main_context);
596 g_source_unref (idle_source);
598 g_object_unref (client_cache);
602 g_mutex_unlock (&client_data->lock);
604 while (!g_queue_is_empty (&queue)) {
605 GSimpleAsyncResult *simple;
607 simple = g_queue_pop_head (&queue);
608 if (client != NULL)
609 g_simple_async_result_set_op_res_gpointer (
610 simple, g_object_ref (client),
611 (GDestroyNotify) g_object_unref);
612 if (error != NULL)
613 g_simple_async_result_set_from_error (simple, error);
614 g_simple_async_result_complete_in_idle (simple);
615 g_object_unref (simple);
619 static void
620 client_cache_book_connect_cb (GObject *source_object,
621 GAsyncResult *result,
622 gpointer user_data)
624 ClientData *client_data = user_data;
625 EClient *client;
626 GError *error = NULL;
628 client = e_book_client_connect_finish (result, &error);
630 client_cache_process_results (client_data, client, error);
632 if (client != NULL)
633 g_object_unref (client);
635 if (error != NULL)
636 g_error_free (error);
638 client_data_unref (client_data);
641 static void
642 client_cache_cal_connect_cb (GObject *source_object,
643 GAsyncResult *result,
644 gpointer user_data)
646 ClientData *client_data = user_data;
647 EClient *client;
648 GError *error = NULL;
650 client = e_cal_client_connect_finish (result, &error);
652 client_cache_process_results (client_data, client, error);
654 if (client != NULL)
655 g_object_unref (client);
657 if (error != NULL)
658 g_error_free (error);
660 client_data_unref (client_data);
663 static void
664 client_cache_source_removed_cb (ESourceRegistry *registry,
665 ESource *source,
666 GWeakRef *weak_ref)
668 EClientCache *client_cache;
670 client_cache = g_weak_ref_get (weak_ref);
672 if (client_cache != NULL) {
673 client_ht_remove (client_cache, source);
674 g_object_unref (client_cache);
678 static void
679 client_cache_source_disabled_cb (ESourceRegistry *registry,
680 ESource *source,
681 GWeakRef *weak_ref)
683 EClientCache *client_cache;
685 client_cache = g_weak_ref_get (weak_ref);
687 if (client_cache != NULL) {
688 e_client_cache_emit_allow_auth_prompt (client_cache, source);
690 client_ht_remove (client_cache, source);
691 g_object_unref (client_cache);
695 static void
696 client_cache_set_registry (EClientCache *client_cache,
697 ESourceRegistry *registry)
699 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
700 g_return_if_fail (client_cache->priv->registry == NULL);
702 client_cache->priv->registry = g_object_ref (registry);
705 static void
706 client_cache_set_property (GObject *object,
707 guint property_id,
708 const GValue *value,
709 GParamSpec *pspec)
711 switch (property_id) {
712 case PROP_REGISTRY:
713 client_cache_set_registry (
714 E_CLIENT_CACHE (object),
715 g_value_get_object (value));
716 return;
719 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
722 static void
723 client_cache_get_property (GObject *object,
724 guint property_id,
725 GValue *value,
726 GParamSpec *pspec)
728 switch (property_id) {
729 case PROP_REGISTRY:
730 g_value_take_object (
731 value,
732 e_client_cache_ref_registry (
733 E_CLIENT_CACHE (object)));
734 return;
737 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
740 static void
741 client_cache_dispose (GObject *object)
743 EClientCachePrivate *priv;
745 priv = E_CLIENT_CACHE_GET_PRIVATE (object);
747 if (priv->source_removed_handler_id > 0) {
748 g_signal_handler_disconnect (
749 priv->registry,
750 priv->source_removed_handler_id);
751 priv->source_removed_handler_id = 0;
754 if (priv->source_disabled_handler_id > 0) {
755 g_signal_handler_disconnect (
756 priv->registry,
757 priv->source_disabled_handler_id);
758 priv->source_disabled_handler_id = 0;
761 g_clear_object (&priv->registry);
763 g_hash_table_remove_all (priv->client_ht);
765 if (priv->main_context != NULL) {
766 g_main_context_unref (priv->main_context);
767 priv->main_context = NULL;
770 /* Chain up to parent's dispose() method. */
771 G_OBJECT_CLASS (e_client_cache_parent_class)->dispose (object);
774 static void
775 client_cache_finalize (GObject *object)
777 EClientCachePrivate *priv;
779 priv = E_CLIENT_CACHE_GET_PRIVATE (object);
781 g_hash_table_destroy (priv->client_ht);
782 g_mutex_clear (&priv->client_ht_lock);
784 /* Chain up to parent's finalize() method. */
785 G_OBJECT_CLASS (e_client_cache_parent_class)->finalize (object);
788 static void
789 client_cache_constructed (GObject *object)
791 EClientCache *client_cache;
792 ESourceRegistry *registry;
793 gulong handler_id;
795 client_cache = E_CLIENT_CACHE (object);
797 /* Chain up to parent's constructed() method. */
798 G_OBJECT_CLASS (e_client_cache_parent_class)->constructed (object);
800 registry = e_client_cache_ref_registry (client_cache);
802 handler_id = g_signal_connect_data (
803 registry, "source-removed",
804 G_CALLBACK (client_cache_source_removed_cb),
805 e_weak_ref_new (client_cache),
806 (GClosureNotify) e_weak_ref_free, 0);
807 client_cache->priv->source_removed_handler_id = handler_id;
809 handler_id = g_signal_connect_data (
810 registry, "source-disabled",
811 G_CALLBACK (client_cache_source_disabled_cb),
812 e_weak_ref_new (client_cache),
813 (GClosureNotify) e_weak_ref_free, 0);
814 client_cache->priv->source_disabled_handler_id = handler_id;
816 g_object_unref (registry);
818 e_extensible_load_extensions (E_EXTENSIBLE (object));
821 static void
822 e_client_cache_class_init (EClientCacheClass *class)
824 GObjectClass *object_class;
826 g_type_class_add_private (class, sizeof (EClientCachePrivate));
828 object_class = G_OBJECT_CLASS (class);
829 object_class->set_property = client_cache_set_property;
830 object_class->get_property = client_cache_get_property;
831 object_class->dispose = client_cache_dispose;
832 object_class->finalize = client_cache_finalize;
833 object_class->constructed = client_cache_constructed;
836 * EClientCache:registry:
838 * The #ESourceRegistry manages #ESource instances.
840 g_object_class_install_property (
841 object_class,
842 PROP_REGISTRY,
843 g_param_spec_object (
844 "registry",
845 "Registry",
846 "Data source registry",
847 E_TYPE_SOURCE_REGISTRY,
848 G_PARAM_READWRITE |
849 G_PARAM_CONSTRUCT_ONLY |
850 G_PARAM_STATIC_STRINGS));
853 * EClientCache::backend-died:
854 * @client_cache: the #EClientCache that received the signal
855 * @client: the #EClient that received the D-Bus notification
856 * @alert: an #EAlert with a user-friendly error description
858 * Rebroadcasts a #EClient::backend-died signal emitted by @client,
859 * along with a pre-formatted #EAlert.
861 * As a convenience to signal handlers, this signal is always
862 * emitted from the #GMainContext that was thread-default when
863 * the @client_cache was created.
865 signals[BACKEND_DIED] = g_signal_new (
866 "backend-died",
867 G_TYPE_FROM_CLASS (class),
868 G_SIGNAL_RUN_LAST,
869 G_STRUCT_OFFSET (EClientCacheClass, backend_died),
870 NULL, NULL, NULL,
871 G_TYPE_NONE, 2,
872 E_TYPE_CLIENT,
873 E_TYPE_ALERT);
876 * EClientCache::backend-error:
877 * @client_cache: the #EClientCache that received the signal
878 * @client: the #EClient that received the D-Bus notification
879 * @alert: an #EAlert with a user-friendly error description
881 * Rebroadcasts a #EClient::backend-error signal emitted by @client,
882 * along with a pre-formatted #EAlert.
884 * As a convenience to signal handlers, this signal is always
885 * emitted from the #GMainContext that was thread-default when
886 * the @client_cache was created.
888 signals[BACKEND_ERROR] = g_signal_new (
889 "backend-error",
890 G_TYPE_FROM_CLASS (class),
891 G_SIGNAL_RUN_LAST,
892 G_STRUCT_OFFSET (EClientCacheClass, backend_error),
893 NULL, NULL, NULL,
894 G_TYPE_NONE, 2,
895 E_TYPE_CLIENT,
896 E_TYPE_ALERT);
899 * EClientCache::client-connected:
900 * @client_cache: the #EClientCache that received the signal
901 * @client: the newly-created #EClient
903 * This signal is emitted when a call to e_client_cache_get_client()
904 * triggers the creation of a new #EClient instance, immediately after
905 * the client's opening phase is over.
907 * See the difference with EClientCache::client-created, which is
908 * called on idle.
910 signals[CLIENT_CONNECTED] = g_signal_new (
911 "client-connected",
912 G_TYPE_FROM_CLASS (class),
913 G_SIGNAL_RUN_FIRST,
914 G_STRUCT_OFFSET (EClientCacheClass, client_connected),
915 NULL, NULL, NULL,
916 G_TYPE_NONE, 1,
917 E_TYPE_CLIENT);
920 * EClientCache::client-created:
921 * @client_cache: the #EClientCache that received the signal
922 * @client: the newly-created #EClient
924 * This signal is emitted when a call to e_client_cache_get_client()
925 * triggers the creation of a new #EClient instance, invoked in an idle
926 * callback.
928 * See the difference with EClientCache::client-connected, which is
929 * called immediately.
931 signals[CLIENT_CREATED] = g_signal_new (
932 "client-created",
933 G_TYPE_FROM_CLASS (class),
934 G_SIGNAL_RUN_FIRST,
935 G_STRUCT_OFFSET (EClientCacheClass, client_created),
936 NULL, NULL, NULL,
937 G_TYPE_NONE, 1,
938 E_TYPE_CLIENT);
941 * EClientCache::client-notify:
942 * @client_cache: the #EClientCache that received the signal
943 * @client: the #EClient whose property changed
944 * @pspec: the #GParamSpec of the property that changed
946 * Rebroadcasts a #GObject::notify signal emitted by @client.
948 * This signal supports "::detail" appendices to the signal name
949 * just like the #GObject::notify signal, so you can connect to
950 * change notification signals for specific #EClient properties.
952 * As a convenience to signal handlers, this signal is always
953 * emitted from the #GMainContext that was thread-default when
954 * the @client_cache was created.
956 signals[CLIENT_NOTIFY] = g_signal_new (
957 "client-notify",
958 G_TYPE_FROM_CLASS (class),
959 /* same flags as GObject::notify */
960 G_SIGNAL_RUN_FIRST |
961 G_SIGNAL_NO_RECURSE |
962 G_SIGNAL_DETAILED |
963 G_SIGNAL_NO_HOOKS |
964 G_SIGNAL_ACTION,
965 G_STRUCT_OFFSET (EClientCacheClass, client_notify),
966 NULL, NULL, NULL,
967 G_TYPE_NONE, 2,
968 E_TYPE_CLIENT,
969 G_TYPE_PARAM);
972 * EClientCache::allow-auth-prompt:
973 * @client_cache: an #EClientCache, which sent the signal
974 * @source: an #ESource
976 * This signal is emitted with e_client_cache_emit_allow_auth_prompt() to let
977 * any listeners know to enable credentials prompt for the given @source.
979 * Since: 3.16
981 signals[ALLOW_AUTH_PROMPT] = g_signal_new (
982 "allow-auth-prompt",
983 G_TYPE_FROM_CLASS (class),
984 G_SIGNAL_RUN_FIRST,
985 G_STRUCT_OFFSET (EClientCacheClass, allow_auth_prompt),
986 NULL, NULL, NULL,
987 G_TYPE_NONE, 1,
988 E_TYPE_SOURCE);
991 static void
992 e_client_cache_init (EClientCache *client_cache)
994 GHashTable *client_ht;
995 gint ii;
997 const gchar *extension_names[] = {
998 E_SOURCE_EXTENSION_ADDRESS_BOOK,
999 E_SOURCE_EXTENSION_CALENDAR,
1000 E_SOURCE_EXTENSION_MEMO_LIST,
1001 E_SOURCE_EXTENSION_TASK_LIST
1004 client_ht = g_hash_table_new_full (
1005 (GHashFunc) g_str_hash,
1006 (GEqualFunc) g_str_equal,
1007 (GDestroyNotify) g_free,
1008 (GDestroyNotify) g_hash_table_unref);
1010 client_cache->priv = E_CLIENT_CACHE_GET_PRIVATE (client_cache);
1012 client_cache->priv->main_context = g_main_context_ref_thread_default ();
1013 client_cache->priv->client_ht = client_ht;
1015 g_mutex_init (&client_cache->priv->client_ht_lock);
1017 /* Pre-load the extension names that can be used to instantiate
1018 * EClients. Then we can validate an extension name by testing
1019 * for a matching hash table key. */
1021 for (ii = 0; ii < G_N_ELEMENTS (extension_names); ii++) {
1022 GHashTable *inner_ht;
1024 inner_ht = g_hash_table_new_full (
1025 (GHashFunc) e_source_hash,
1026 (GEqualFunc) e_source_equal,
1027 (GDestroyNotify) g_object_unref,
1028 (GDestroyNotify) client_data_dispose);
1030 g_hash_table_insert (
1031 client_ht,
1032 g_strdup (extension_names[ii]),
1033 g_hash_table_ref (inner_ht));
1035 g_hash_table_unref (inner_ht);
1040 * e_client_cache_new:
1041 * @registry: an #ESourceRegistry
1043 * Creates a new #EClientCache instance.
1045 * Returns: an #EClientCache
1047 EClientCache *
1048 e_client_cache_new (ESourceRegistry *registry)
1050 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
1052 return g_object_new (
1053 E_TYPE_CLIENT_CACHE,
1054 "registry", registry, NULL);
1058 * e_client_cache_ref_registry:
1059 * @client_cache: an #EClientCache
1061 * Returns the #ESourceRegistry passed to e_client_cache_new().
1063 * The returned #ESourceRegistry is referenced for thread-safety and must be
1064 * unreferenced with g_object_unref() when finished with it.
1066 * Returns: an #ESourceRegistry
1068 ESourceRegistry *
1069 e_client_cache_ref_registry (EClientCache *client_cache)
1071 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
1073 return g_object_ref (client_cache->priv->registry);
1077 * e_client_cache_get_client_sync:
1078 * @client_cache: an #EClientCache
1079 * @source: an #ESource
1080 * @extension_name: an extension name
1081 * @wait_for_connected_seconds: timeout, in seconds, to wait for the backend to be fully connected
1082 * @cancellable: optional #GCancellable object, or %NULL
1083 * @error: return location for a #GError, or %NULL
1085 * Obtains a shared #EClient instance for @source, or else creates a new
1086 * #EClient instance to be shared.
1088 * The @extension_name determines the type of #EClient to obtain. Valid
1089 * @extension_name values are:
1091 * #E_SOURCE_EXTENSION_ADDRESS_BOOK will obtain an #EBookClient.
1093 * #E_SOURCE_EXTENSION_CALENDAR will obtain an #ECalClient with a
1094 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_EVENTS.
1096 * #E_SOURCE_EXTENSION_MEMO_LIST will obtain an #ECalClient with a
1097 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_MEMOS.
1099 * #E_SOURCE_EXTENSION_TASK_LIST will obtain an #ECalClient with a
1100 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_TASKS.
1102 * The @source must already have an #ESourceExtension by that name
1103 * for this function to work. All other @extension_name values will
1104 * result in an error.
1106 * The @wait_for_connected_seconds argument had been added since 3.16,
1107 * to let the caller decide how long to wait for the backend to fully
1108 * connect to its (possibly remote) data store. This is required due
1109 * to a change in the authentication process, which is fully asynchronous
1110 * and done on the client side, while not every client is supposed to
1111 * response to authentication requests. In case the backend will not connect
1112 * within the set interval, then it is opened in an offline mode. A special
1113 * value -1 can be used to not wait for the connected state at all.
1115 * If a request for the same @source and @extension_name is already in
1116 * progress when this function is called, this request will "piggyback"
1117 * on the in-progress request such that they will both succeed or fail
1118 * simultaneously.
1120 * Unreference the returned #EClient with g_object_unref() when finished
1121 * with it. If an error occurs, the function will set @error and return
1122 * %NULL.
1124 * Returns: an #EClient, or %NULL
1126 EClient *
1127 e_client_cache_get_client_sync (EClientCache *client_cache,
1128 ESource *source,
1129 const gchar *extension_name,
1130 guint32 wait_for_connected_seconds,
1131 GCancellable *cancellable,
1132 GError **error)
1134 ClientData *client_data;
1135 EClient *client = NULL;
1136 GError *local_error = NULL;
1138 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
1139 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1140 g_return_val_if_fail (extension_name != NULL, NULL);
1142 client_data = client_ht_lookup (client_cache, source, extension_name);
1144 if (client_data == NULL) {
1145 g_set_error (
1146 error, G_IO_ERROR,
1147 G_IO_ERROR_INVALID_ARGUMENT,
1148 _("Cannot create a client object from "
1149 "extension name '%s'"), extension_name);
1150 return NULL;
1153 g_mutex_lock (&client_data->lock);
1155 if (client_data->client != NULL)
1156 client = g_object_ref (client_data->client);
1158 g_mutex_unlock (&client_data->lock);
1160 /* If a cached EClient already exists, we're done. */
1161 if (client != NULL) {
1162 client_data_unref (client_data);
1163 return client;
1166 /* Create an appropriate EClient instance for the extension
1167 * name. The client_ht_lookup() call above ensures us that
1168 * one of these options will match. */
1170 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
1171 client = e_book_client_connect_sync (source, wait_for_connected_seconds,
1172 cancellable, &local_error);
1173 } else if (g_str_equal (extension_name, E_SOURCE_EXTENSION_CALENDAR)) {
1174 client = e_cal_client_connect_sync (
1175 source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, wait_for_connected_seconds,
1176 cancellable, &local_error);
1177 } else if (g_str_equal (extension_name, E_SOURCE_EXTENSION_MEMO_LIST)) {
1178 client = e_cal_client_connect_sync (
1179 source, E_CAL_CLIENT_SOURCE_TYPE_MEMOS, wait_for_connected_seconds,
1180 cancellable, &local_error);
1181 } else if (g_str_equal (extension_name, E_SOURCE_EXTENSION_TASK_LIST)) {
1182 client = e_cal_client_connect_sync (
1183 source, E_CAL_CLIENT_SOURCE_TYPE_TASKS, wait_for_connected_seconds,
1184 cancellable, &local_error);
1185 } else {
1186 g_warn_if_reached (); /* Should never happen. */
1189 if (client)
1190 client_cache_process_results (client_data, client, local_error);
1192 if (local_error)
1193 g_propagate_error (error, local_error);
1195 client_data_unref (client_data);
1197 return client;
1201 * e_client_cache_get_client:
1202 * @client_cache: an #EClientCache
1203 * @source: an #ESource
1204 * @extension_name: an extension name
1205 * @wait_for_connected_seconds: timeout, in seconds, to wait for the backend to be fully connected
1206 * @cancellable: optional #GCancellable object, or %NULL
1207 * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1208 * @user_data: data to pass to the callback function
1210 * Asynchronously obtains a shared #EClient instance for @source, or else
1211 * creates a new #EClient instance to be shared.
1213 * The @extension_name determines the type of #EClient to obtain. Valid
1214 * @extension_name values are:
1216 * #E_SOURCE_EXTENSION_ADDRESS_BOOK will obtain an #EBookClient.
1218 * #E_SOURCE_EXTENSION_CALENDAR will obtain an #ECalClient with a
1219 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_EVENTS.
1221 * #E_SOURCE_EXTENSION_MEMO_LIST will obtain an #ECalClient with a
1222 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_MEMOS.
1224 * #E_SOURCE_EXTENSION_TASK_LIST will obtain an #ECalClient with a
1225 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_TASKS.
1227 * The @source must already have an #ESourceExtension by that name
1228 * for this function to work. All other @extension_name values will
1229 * result in an error.
1231 * The @wait_for_connected_seconds argument had been added since 3.16,
1232 * to let the caller decide how long to wait for the backend to fully
1233 * connect to its (possibly remote) data store. This is required due
1234 * to a change in the authentication process, which is fully asynchronous
1235 * and done on the client side, while not every client is supposed to
1236 * response to authentication requests. In case the backend will not connect
1237 * within the set interval, then it is opened in an offline mode. A special
1238 * value -1 can be used to not wait for the connected state at all.
1240 * If a request for the same @source and @extension_name is already in
1241 * progress when this function is called, this request will "piggyback"
1242 * on the in-progress request such that they will both succeed or fail
1243 * simultaneously.
1245 * When the operation is finished, @callback will be called. You can
1246 * then call e_client_cache_get_client_finish() to get the result of the
1247 * operation.
1249 void
1250 e_client_cache_get_client (EClientCache *client_cache,
1251 ESource *source,
1252 const gchar *extension_name,
1253 guint32 wait_for_connected_seconds,
1254 GCancellable *cancellable,
1255 GAsyncReadyCallback callback,
1256 gpointer user_data)
1258 GSimpleAsyncResult *simple;
1259 ClientData *client_data;
1260 EClient *client = NULL;
1261 gboolean connect_in_progress = FALSE;
1263 g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
1264 g_return_if_fail (E_IS_SOURCE (source));
1265 g_return_if_fail (extension_name != NULL);
1267 simple = g_simple_async_result_new (
1268 G_OBJECT (client_cache), callback,
1269 user_data, e_client_cache_get_client);
1271 g_simple_async_result_set_check_cancellable (simple, cancellable);
1273 client_data = client_ht_lookup (client_cache, source, extension_name);
1275 if (client_data == NULL) {
1276 g_simple_async_result_set_error (
1277 simple, G_IO_ERROR,
1278 G_IO_ERROR_INVALID_ARGUMENT,
1279 _("Cannot create a client object from "
1280 "extension name '%s'"), extension_name);
1281 g_simple_async_result_complete_in_idle (simple);
1282 goto exit;
1285 g_mutex_lock (&client_data->lock);
1287 if (client_data->client != NULL) {
1288 client = g_object_ref (client_data->client);
1289 } else {
1290 GQueue *connecting = &client_data->connecting;
1291 connect_in_progress = !g_queue_is_empty (connecting);
1292 g_queue_push_tail (connecting, g_object_ref (simple));
1295 g_mutex_unlock (&client_data->lock);
1297 /* If a cached EClient already exists, we're done. */
1298 if (client != NULL) {
1299 g_simple_async_result_set_op_res_gpointer (
1300 simple, client, (GDestroyNotify) g_object_unref);
1301 g_simple_async_result_complete_in_idle (simple);
1302 goto exit;
1305 /* If an EClient connection attempt is already in progress, our
1306 * cache request will complete when it finishes, so now we wait. */
1307 if (connect_in_progress)
1308 goto exit;
1310 /* Create an appropriate EClient instance for the extension
1311 * name. The client_ht_lookup() call above ensures us that
1312 * one of these options will match. */
1314 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
1315 e_book_client_connect (
1316 source, wait_for_connected_seconds, cancellable,
1317 client_cache_book_connect_cb,
1318 client_data_ref (client_data));
1319 goto exit;
1322 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_CALENDAR)) {
1323 e_cal_client_connect (
1324 source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, wait_for_connected_seconds,
1325 cancellable, client_cache_cal_connect_cb,
1326 client_data_ref (client_data));
1327 goto exit;
1330 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_MEMO_LIST)) {
1331 e_cal_client_connect (
1332 source, E_CAL_CLIENT_SOURCE_TYPE_MEMOS, wait_for_connected_seconds,
1333 cancellable, client_cache_cal_connect_cb,
1334 client_data_ref (client_data));
1335 goto exit;
1338 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_TASK_LIST)) {
1339 e_cal_client_connect (
1340 source, E_CAL_CLIENT_SOURCE_TYPE_TASKS, wait_for_connected_seconds,
1341 cancellable, client_cache_cal_connect_cb,
1342 client_data_ref (client_data));
1343 goto exit;
1346 g_warn_if_reached (); /* Should never happen. */
1348 exit:
1349 if (client_data)
1350 client_data_unref (client_data);
1351 g_object_unref (simple);
1355 * e_client_cache_get_client_finish:
1356 * @client_cache: an #EClientCache
1357 * @result: a #GAsyncResult
1358 * @error: return location for a #GError, or %NULL
1360 * Finishes the operation started with e_client_cache_get_client().
1362 * Unreference the returned #EClient with g_object_unref() when finished
1363 * with it. If an error occurred, the function will set @error and return
1364 * %NULL.
1366 * Returns: an #EClient, or %NULL
1368 EClient *
1369 e_client_cache_get_client_finish (EClientCache *client_cache,
1370 GAsyncResult *result,
1371 GError **error)
1373 GSimpleAsyncResult *simple;
1374 EClient *client;
1376 g_return_val_if_fail (
1377 g_simple_async_result_is_valid (
1378 result, G_OBJECT (client_cache),
1379 e_client_cache_get_client), NULL);
1381 simple = G_SIMPLE_ASYNC_RESULT (result);
1383 if (g_simple_async_result_propagate_error (simple, error))
1384 return NULL;
1386 client = g_simple_async_result_get_op_res_gpointer (simple);
1387 g_return_val_if_fail (client != NULL, NULL);
1389 return g_object_ref (client);
1393 * e_client_cache_ref_cached_client:
1394 * @client_cache: an #EClientCache
1395 * @source: an #ESource
1396 * @extension_name: an extension name
1398 * Returns a shared #EClient instance for @source and @extension_name if
1399 * such an instance is already cached, or else %NULL. This function does
1400 * not create a new #EClient instance, and therefore does not block.
1402 * See e_client_cache_get_client() for valid @extension_name values.
1404 * The returned #EClient is referenced for thread-safety and must be
1405 * unreferenced with g_object_unref() when finished with it.
1407 * Returns: an #EClient, or %NULL
1409 EClient *
1410 e_client_cache_ref_cached_client (EClientCache *client_cache,
1411 ESource *source,
1412 const gchar *extension_name)
1414 ClientData *client_data;
1415 EClient *client = NULL;
1417 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
1418 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1419 g_return_val_if_fail (extension_name != NULL, NULL);
1421 client_data = client_ht_lookup (client_cache, source, extension_name);
1423 if (client_data != NULL) {
1424 g_mutex_lock (&client_data->lock);
1425 if (client_data->client != NULL)
1426 client = g_object_ref (client_data->client);
1427 g_mutex_unlock (&client_data->lock);
1429 client_data_unref (client_data);
1432 return client;
1436 * e_client_cache_is_backend_dead:
1437 * @client_cache: an #EClientCache
1438 * @source: an #ESource
1439 * @extension_name: an extension name
1441 * Returns %TRUE if an #EClient instance for @source and @extension_name
1442 * was recently discarded after having emitted a #EClient::backend-died
1443 * signal, and a replacement #EClient instance has not yet been created.
1445 * Returns: whether the backend for @source and @extension_name died
1447 gboolean
1448 e_client_cache_is_backend_dead (EClientCache *client_cache,
1449 ESource *source,
1450 const gchar *extension_name)
1452 ClientData *client_data;
1453 gboolean dead_backend = FALSE;
1455 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), FALSE);
1456 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1457 g_return_val_if_fail (extension_name != NULL, FALSE);
1459 client_data = client_ht_lookup (client_cache, source, extension_name);
1461 if (client_data != NULL) {
1462 dead_backend = client_data->dead_backend;
1463 client_data_unref (client_data);
1466 return dead_backend;
1470 * e_client_cache_emit_allow_auth_prompt:
1471 * @client_cache: an #EClientCache
1472 * @source: an #ESource
1474 * Emits 'allow-auth-prompt' on @client_cache for @source. This lets
1475 * any listeners know to enable credentials prompt for this @source.
1477 * Since: 3.16
1479 void
1480 e_client_cache_emit_allow_auth_prompt (EClientCache *client_cache,
1481 ESource *source)
1483 g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
1484 g_return_if_fail (E_IS_SOURCE (source));
1486 g_signal_emit (client_cache, signals[ALLOW_AUTH_PROMPT], 0, source);