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
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/>.
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
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.
33 #include "e-client-cache.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
;
62 volatile gint ref_count
;
64 GWeakRef client_cache
;
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
;
80 G_DEFINE_TYPE_WITH_CODE (
84 G_IMPLEMENT_INTERFACE (
85 E_TYPE_EXTENSIBLE
, NULL
))
102 static guint signals
[LAST_SIGNAL
];
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
);
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
);
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
);
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 (
162 client_data
->backend_died_handler_id
);
163 client_data
->backend_died_handler_id
= 0;
165 g_signal_handler_disconnect (
167 client_data
->backend_error_handler_id
);
168 client_data
->backend_error_handler_id
= 0;
170 g_signal_handler_disconnect (
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
);
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
);
198 client_ht_lookup (EClientCache
*client_cache
,
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
);
232 client_ht_remove (EClientCache
*client_cache
,
235 GHashTable
*client_ht
;
236 GHashTableIter client_ht_iter
;
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
);
257 client_cache_emit_backend_died_idle_cb (gpointer user_data
)
259 SignalClosure
*signal_closure
= user_data
;
260 ESourceRegistry
*registry
;
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
);
306 signal_closure
->client_cache
,
307 signals
[BACKEND_DIED
], 0,
308 signal_closure
->client
,
311 g_object_unref (alert
);
313 g_free (display_name
);
319 client_cache_emit_backend_error_idle_cb (gpointer user_data
)
321 SignalClosure
*signal_closure
= user_data
;
322 ESourceRegistry
*registry
;
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
);
370 signal_closure
->client_cache
,
371 signals
[BACKEND_ERROR
], 0,
372 signal_closure
->client
,
375 g_object_unref (alert
);
377 g_free (display_name
);
383 client_cache_emit_client_notify_idle_cb (gpointer user_data
)
385 SignalClosure
*signal_closure
= user_data
;
388 name
= g_param_spec_get_name (signal_closure
->pspec
);
391 signal_closure
->client_cache
,
392 signals
[CLIENT_NOTIFY
],
393 g_quark_from_string (name
),
394 signal_closure
->client
,
395 signal_closure
->pspec
);
401 client_cache_emit_client_created_idle_cb (gpointer user_data
)
403 SignalClosure
*signal_closure
= user_data
;
406 signal_closure
->client_cache
,
407 signals
[CLIENT_CREATED
], 0,
408 signal_closure
->client
);
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 (
432 client_cache_emit_backend_died_idle_cb
,
434 (GDestroyNotify
) signal_closure_free
);
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
);
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 (
472 client_cache_emit_backend_error_idle_cb
,
474 (GDestroyNotify
) signal_closure_free
);
476 idle_source
, client_cache
->priv
->main_context
);
477 g_source_unref (idle_source
);
479 g_object_unref (client_cache
);
484 client_cache_notify_cb (EClient
*client
,
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 (
504 client_cache_emit_client_notify_idle_cb
,
506 (GDestroyNotify
) signal_closure_free
);
508 idle_source
, client_cache
->priv
->main_context
);
509 g_source_unref (idle_source
);
511 g_object_unref (client_cache
);
516 client_cache_process_results (ClientData
*client_data
,
520 GQueue queue
= G_QUEUE_INIT
;
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
;
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 (
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 (
591 client_cache_emit_client_created_idle_cb
,
593 (GDestroyNotify
) signal_closure_free
);
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
);
609 g_simple_async_result_set_op_res_gpointer (
610 simple
, g_object_ref (client
),
611 (GDestroyNotify
) g_object_unref
);
613 g_simple_async_result_set_from_error (simple
, error
);
614 g_simple_async_result_complete_in_idle (simple
);
615 g_object_unref (simple
);
620 client_cache_book_connect_cb (GObject
*source_object
,
621 GAsyncResult
*result
,
624 ClientData
*client_data
= user_data
;
626 GError
*error
= NULL
;
628 client
= e_book_client_connect_finish (result
, &error
);
630 client_cache_process_results (client_data
, client
, error
);
633 g_object_unref (client
);
636 g_error_free (error
);
638 client_data_unref (client_data
);
642 client_cache_cal_connect_cb (GObject
*source_object
,
643 GAsyncResult
*result
,
646 ClientData
*client_data
= user_data
;
648 GError
*error
= NULL
;
650 client
= e_cal_client_connect_finish (result
, &error
);
652 client_cache_process_results (client_data
, client
, error
);
655 g_object_unref (client
);
658 g_error_free (error
);
660 client_data_unref (client_data
);
664 client_cache_source_removed_cb (ESourceRegistry
*registry
,
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
);
679 client_cache_source_disabled_cb (ESourceRegistry
*registry
,
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
);
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
);
706 client_cache_set_property (GObject
*object
,
711 switch (property_id
) {
713 client_cache_set_registry (
714 E_CLIENT_CACHE (object
),
715 g_value_get_object (value
));
719 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
723 client_cache_get_property (GObject
*object
,
728 switch (property_id
) {
730 g_value_take_object (
732 e_client_cache_ref_registry (
733 E_CLIENT_CACHE (object
)));
737 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
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 (
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 (
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
);
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
);
789 client_cache_constructed (GObject
*object
)
791 EClientCache
*client_cache
;
792 ESourceRegistry
*registry
;
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
));
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 (
843 g_param_spec_object (
846 "Data source registry",
847 E_TYPE_SOURCE_REGISTRY
,
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 (
867 G_TYPE_FROM_CLASS (class),
869 G_STRUCT_OFFSET (EClientCacheClass
, backend_died
),
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 (
890 G_TYPE_FROM_CLASS (class),
892 G_STRUCT_OFFSET (EClientCacheClass
, backend_error
),
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
910 signals
[CLIENT_CONNECTED
] = g_signal_new (
912 G_TYPE_FROM_CLASS (class),
914 G_STRUCT_OFFSET (EClientCacheClass
, client_connected
),
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
928 * See the difference with EClientCache::client-connected, which is
929 * called immediately.
931 signals
[CLIENT_CREATED
] = g_signal_new (
933 G_TYPE_FROM_CLASS (class),
935 G_STRUCT_OFFSET (EClientCacheClass
, client_created
),
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 (
958 G_TYPE_FROM_CLASS (class),
959 /* same flags as GObject::notify */
961 G_SIGNAL_NO_RECURSE
|
965 G_STRUCT_OFFSET (EClientCacheClass
, client_notify
),
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.
981 signals
[ALLOW_AUTH_PROMPT
] = g_signal_new (
983 G_TYPE_FROM_CLASS (class),
985 G_STRUCT_OFFSET (EClientCacheClass
, allow_auth_prompt
),
992 e_client_cache_init (EClientCache
*client_cache
)
994 GHashTable
*client_ht
;
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 (
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
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
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
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
1124 * Returns: an #EClient, or %NULL
1127 e_client_cache_get_client_sync (EClientCache
*client_cache
,
1129 const gchar
*extension_name
,
1130 guint32 wait_for_connected_seconds
,
1131 GCancellable
*cancellable
,
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
) {
1147 G_IO_ERROR_INVALID_ARGUMENT
,
1148 _("Cannot create a client object from "
1149 "extension name '%s'"), extension_name
);
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
);
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
);
1186 g_warn_if_reached (); /* Should never happen. */
1190 client_cache_process_results (client_data
, client
, local_error
);
1193 g_propagate_error (error
, local_error
);
1195 client_data_unref (client_data
);
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
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
1250 e_client_cache_get_client (EClientCache
*client_cache
,
1252 const gchar
*extension_name
,
1253 guint32 wait_for_connected_seconds
,
1254 GCancellable
*cancellable
,
1255 GAsyncReadyCallback callback
,
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 (
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
);
1285 g_mutex_lock (&client_data
->lock
);
1287 if (client_data
->client
!= NULL
) {
1288 client
= g_object_ref (client_data
->client
);
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
);
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
)
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
));
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
));
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
));
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
));
1346 g_warn_if_reached (); /* Should never happen. */
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
1366 * Returns: an #EClient, or %NULL
1369 e_client_cache_get_client_finish (EClientCache
*client_cache
,
1370 GAsyncResult
*result
,
1373 GSimpleAsyncResult
*simple
;
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
))
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
1410 e_client_cache_ref_cached_client (EClientCache
*client_cache
,
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
);
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
1448 e_client_cache_is_backend_dead (EClientCache
*client_cache
,
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.
1480 e_client_cache_emit_allow_auth_prompt (EClientCache
*client_cache
,
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
);