1 /*
2 * e-client-cache.c
3 *
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.
7 *
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.
12 *
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/>.
15 *
16 */
17
18 /**
19 * SECTION: e-client-cache
20 * @include: e-util/e-util.h
21 * @short_description: Shared #EClient instances
22 *
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.
27 *
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 **/
32
33 #include "evolution-config.h"
34
35 #include <glib/gi18n-lib.h>
36
37 #include <libecal/libecal.h>
38 #include <libebook/libebook.h>
39 #include <libebackend/libebackend.h>
40
41 #include "e-client-cache.h"
42
43 #define E_CLIENT_CACHE_GET_PRIVATE(obj) \
44 (G_TYPE_INSTANCE_GET_PRIVATE \
45 ((obj), E_TYPE_CLIENT_CACHE, EClientCachePrivate))
46
47 typedef struct _ClientData ClientData;
48 typedef struct _SignalClosure SignalClosure;
49
50 struct _EClientCachePrivate {
51 ESourceRegistry *registry;
52 gulong source_removed_handler_id;
53 gulong source_disabled_handler_id;
54
55 GHashTable *client_ht;
56 GMutex client_ht_lock;
57
58 /* For signal emissions. */
59 GMainContext *main_context;
60 };
61
62 struct _ClientData {
63 volatile gint ref_count;
64 GMutex lock;
65 GWeakRef client_cache;
66 EClient *client;
67 GQueue connecting;
68 gboolean dead_backend;
69 gulong backend_died_handler_id;
70 gulong backend_error_handler_id;
71 gulong notify_handler_id;
72 };
73
74 struct _SignalClosure {
75 EClientCache *client_cache;
76 EClient *client;
77 GParamSpec *pspec;
78 gchar *error_message;
79 };
80
81 G_DEFINE_TYPE_WITH_CODE (
82 EClientCache,
83 e_client_cache,
84 G_TYPE_OBJECT,
85 G_IMPLEMENT_INTERFACE (
86 E_TYPE_EXTENSIBLE, NULL))
87
88 enum {
89 PROP_0,
90 PROP_REGISTRY
91 };
92
93 enum {
94 BACKEND_DIED,
95 BACKEND_ERROR,
96 CLIENT_CONNECTED,
97 CLIENT_CREATED,
98 CLIENT_NOTIFY,
99 ALLOW_AUTH_PROMPT,
100 LAST_SIGNAL
101 };
102
103 static guint signals[LAST_SIGNAL];
104
105 static ClientData *
client_data_new(EClientCache * client_cache)106 client_data_new (EClientCache *client_cache)
107 {
108 ClientData *client_data;
109
110 client_data = g_slice_new0 (ClientData);
111 client_data->ref_count = 1;
112 g_mutex_init (&client_data->lock);
113 g_weak_ref_set (&client_data->client_cache, client_cache);
114
115 return client_data;
116 }
117
118 static ClientData *
client_data_ref(ClientData * client_data)119 client_data_ref (ClientData *client_data)
120 {
121 g_return_val_if_fail (client_data != NULL, NULL);
122 g_return_val_if_fail (client_data->ref_count > 0, NULL);
123
124 g_atomic_int_inc (&client_data->ref_count);
125
126 return client_data;
127 }
128
129 static void
client_data_unref(ClientData * client_data)130 client_data_unref (ClientData *client_data)
131 {
132 g_return_if_fail (client_data != NULL);
133 g_return_if_fail (client_data->ref_count > 0);
134
135 if (g_atomic_int_dec_and_test (&client_data->ref_count)) {
136
137 /* The signal handlers hold a reference on client_data,
138 * so we should not be here unless the signal handlers
139 * have already been disconnected. */
140 g_warn_if_fail (client_data->backend_died_handler_id == 0);
141 g_warn_if_fail (client_data->backend_error_handler_id == 0);
142 g_warn_if_fail (client_data->notify_handler_id == 0);
143
144 g_mutex_clear (&client_data->lock);
145 g_clear_object (&client_data->client);
146 g_weak_ref_set (&client_data->client_cache, NULL);
147
148 /* There should be no connect() operations in progress. */
149 g_warn_if_fail (g_queue_is_empty (&client_data->connecting));
150
151 g_slice_free (ClientData, client_data);
152 }
153 }
154
155 static void
client_data_dispose(ClientData * client_data)156 client_data_dispose (ClientData *client_data)
157 {
158 g_mutex_lock (&client_data->lock);
159
160 if (client_data->client != NULL) {
161 g_signal_handler_disconnect (
162 client_data->client,
163 client_data->backend_died_handler_id);
164 client_data->backend_died_handler_id = 0;
165
166 g_signal_handler_disconnect (
167 client_data->client,
168 client_data->backend_error_handler_id);
169 client_data->backend_error_handler_id = 0;
170
171 g_signal_handler_disconnect (
172 client_data->client,
173 client_data->notify_handler_id);
174 client_data->notify_handler_id = 0;
175
176 g_clear_object (&client_data->client);
177 }
178
179 g_mutex_unlock (&client_data->lock);
180
181 client_data_unref (client_data);
182 }
183
184 static void
signal_closure_free(SignalClosure * signal_closure)185 signal_closure_free (SignalClosure *signal_closure)
186 {
187 g_clear_object (&signal_closure->client_cache);
188 g_clear_object (&signal_closure->client);
189
190 if (signal_closure->pspec != NULL)
191 g_param_spec_unref (signal_closure->pspec);
192
193 g_free (signal_closure->error_message);
194
195 g_slice_free (SignalClosure, signal_closure);
196 }
197
198 static ClientData *
client_ht_lookup(EClientCache * client_cache,ESource * source,const gchar * extension_name)199 client_ht_lookup (EClientCache *client_cache,
200 ESource *source,
201 const gchar *extension_name)
202 {
203 GHashTable *client_ht;
204 GHashTable *inner_ht;
205 ClientData *client_data = NULL;
206
207 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
208 g_return_val_if_fail (extension_name != NULL, NULL);
209
210 client_ht = client_cache->priv->client_ht;
211
212 g_mutex_lock (&client_cache->priv->client_ht_lock);
213
214 /* We pre-load the hash table with supported extension names,
215 * so lookup failures indicate an unsupported extension name. */
216 inner_ht = g_hash_table_lookup (client_ht, extension_name);
217 if (inner_ht != NULL) {
218 client_data = g_hash_table_lookup (inner_ht, source);
219 if (client_data == NULL) {
220 g_object_ref (source);
221 client_data = client_data_new (client_cache);
222 g_hash_table_insert (inner_ht, source, client_data);
223 }
224 client_data_ref (client_data);
225 }
226
227 g_mutex_unlock (&client_cache->priv->client_ht_lock);
228
229 return client_data;
230 }
231
232 static gboolean
client_ht_remove(EClientCache * client_cache,ESource * source)233 client_ht_remove (EClientCache *client_cache,
234 ESource *source)
235 {
236 GHashTable *client_ht;
237 GHashTableIter client_ht_iter;
238 gpointer inner_ht;
239 gboolean removed = FALSE;
240
241 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
242
243 client_ht = client_cache->priv->client_ht;
244
245 g_mutex_lock (&client_cache->priv->client_ht_lock);
246
247 g_hash_table_iter_init (&client_ht_iter, client_ht);
248
249 while (g_hash_table_iter_next (&client_ht_iter, NULL, &inner_ht))
250 removed |= g_hash_table_remove (inner_ht, source);
251
252 g_mutex_unlock (&client_cache->priv->client_ht_lock);
253
254 return removed;
255 }
256
257 static gboolean
client_cache_emit_backend_died_idle_cb(gpointer user_data)258 client_cache_emit_backend_died_idle_cb (gpointer user_data)
259 {
260 SignalClosure *signal_closure = user_data;
261 ESourceRegistry *registry;
262 EAlert *alert;
263 ESource *source;
264 const gchar *alert_id = NULL;
265 const gchar *extension_name;
266 gchar *display_name = NULL;
267
268 source = e_client_get_source (signal_closure->client);
269 registry = e_client_cache_ref_registry (signal_closure->client_cache);
270
271 extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
272 if (e_source_has_extension (source, extension_name)) {
273 alert_id = "system:address-book-backend-died";
274 display_name = e_source_registry_dup_unique_display_name (
275 registry, source, extension_name);
276 }
277
278 extension_name = E_SOURCE_EXTENSION_CALENDAR;
279 if (e_source_has_extension (source, extension_name)) {
280 alert_id = "system:calendar-backend-died";
281 display_name = e_source_registry_dup_unique_display_name (
282 registry, source, extension_name);
283 }
284
285 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
286 if (e_source_has_extension (source, extension_name)) {
287 alert_id = "system:memo-list-backend-died";
288 display_name = e_source_registry_dup_unique_display_name (
289 registry, source, extension_name);
290 }
291
292 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
293 if (e_source_has_extension (source, extension_name)) {
294 alert_id = "system:task-list-backend-died";
295 display_name = e_source_registry_dup_unique_display_name (
296 registry, source, extension_name);
297 }
298
299 g_object_unref (registry);
300
301 g_return_val_if_fail (alert_id != NULL, FALSE);
302 g_return_val_if_fail (display_name != NULL, FALSE);
303
304 alert = e_alert_new (alert_id, display_name, NULL);
305
306 g_signal_emit (
307 signal_closure->client_cache,
308 signals[BACKEND_DIED], 0,
309 signal_closure->client,
310 alert);
311
312 g_object_unref (alert);
313
314 g_free (display_name);
315
316 return FALSE;
317 }
318
319 static gboolean
client_cache_emit_backend_error_idle_cb(gpointer user_data)320 client_cache_emit_backend_error_idle_cb (gpointer user_data)
321 {
322 SignalClosure *signal_closure = user_data;
323 ESourceRegistry *registry;
324 EAlert *alert;
325 ESource *source;
326 const gchar *alert_id = NULL;
327 const gchar *extension_name;
328 gchar *display_name = NULL;
329
330 source = e_client_get_source (signal_closure->client);
331 registry = e_client_cache_ref_registry (signal_closure->client_cache);
332
333 extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
334 if (e_source_has_extension (source, extension_name)) {
335 alert_id = "system:address-book-backend-error";
336 display_name = e_source_registry_dup_unique_display_name (
337 registry, source, extension_name);
338 }
339
340 extension_name = E_SOURCE_EXTENSION_CALENDAR;
341 if (e_source_has_extension (source, extension_name)) {
342 alert_id = "system:calendar-backend-error";
343 display_name = e_source_registry_dup_unique_display_name (
344 registry, source, extension_name);
345 }
346
347 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
348 if (e_source_has_extension (source, extension_name)) {
349 alert_id = "system:memo-list-backend-error";
350 display_name = e_source_registry_dup_unique_display_name (
351 registry, source, extension_name);
352 }
353
354 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
355 if (e_source_has_extension (source, extension_name)) {
356 alert_id = "system:task-list-backend-error";
357 display_name = e_source_registry_dup_unique_display_name (
358 registry, source, extension_name);
359 }
360
361 g_object_unref (registry);
362
363 g_return_val_if_fail (alert_id != NULL, FALSE);
364 g_return_val_if_fail (display_name != NULL, FALSE);
365
366 alert = e_alert_new (
367 alert_id, display_name,
368 signal_closure->error_message, NULL);
369
370 g_signal_emit (
371 signal_closure->client_cache,
372 signals[BACKEND_ERROR], 0,
373 signal_closure->client,
374 alert);
375
376 g_object_unref (alert);
377
378 g_free (display_name);
379
380 return FALSE;
381 }
382
383 static gboolean
client_cache_emit_client_notify_idle_cb(gpointer user_data)384 client_cache_emit_client_notify_idle_cb (gpointer user_data)
385 {
386 SignalClosure *signal_closure = user_data;
387 const gchar *name;
388
389 name = g_param_spec_get_name (signal_closure->pspec);
390
391 g_signal_emit (
392 signal_closure->client_cache,
393 signals[CLIENT_NOTIFY],
394 g_quark_from_string (name),
395 signal_closure->client,
396 signal_closure->pspec);
397
398 return FALSE;
399 }
400
401 static gboolean
client_cache_emit_client_created_idle_cb(gpointer user_data)402 client_cache_emit_client_created_idle_cb (gpointer user_data)
403 {
404 SignalClosure *signal_closure = user_data;
405
406 g_signal_emit (
407 signal_closure->client_cache,
408 signals[CLIENT_CREATED], 0,
409 signal_closure->client);
410
411 return FALSE;
412 }
413
414 static void
client_cache_backend_died_cb(EClient * client,ClientData * client_data)415 client_cache_backend_died_cb (EClient *client,
416 ClientData *client_data)
417 {
418 EClientCache *client_cache;
419
420 client_cache = g_weak_ref_get (&client_data->client_cache);
421
422 if (client_cache != NULL) {
423 GSource *idle_source;
424 SignalClosure *signal_closure;
425
426 signal_closure = g_slice_new0 (SignalClosure);
427 signal_closure->client_cache = g_object_ref (client_cache);
428 signal_closure->client = g_object_ref (client);
429
430 idle_source = g_idle_source_new ();
431 g_source_set_callback (
432 idle_source,
433 client_cache_emit_backend_died_idle_cb,
434 signal_closure,
435 (GDestroyNotify) signal_closure_free);
436 g_source_attach (
437 idle_source, client_cache->priv->main_context);
438 g_source_unref (idle_source);
439
440 g_object_unref (client_cache);
441 }
442
443 /* Discard the EClient and tag the backend as
444 * dead until we create a replacement EClient. */
445 g_mutex_lock (&client_data->lock);
446 g_clear_object (&client_data->client);
447 client_data->dead_backend = TRUE;
448 g_mutex_unlock (&client_data->lock);
449
450 }
451
452 static void
client_cache_backend_error_cb(EClient * client,const gchar * error_message,ClientData * client_data)453 client_cache_backend_error_cb (EClient *client,
454 const gchar *error_message,
455 ClientData *client_data)
456 {
457 EClientCache *client_cache;
458
459 client_cache = g_weak_ref_get (&client_data->client_cache);
460
461 if (client_cache != NULL) {
462 GSource *idle_source;
463 SignalClosure *signal_closure;
464
465 signal_closure = g_slice_new0 (SignalClosure);
466 signal_closure->client_cache = g_object_ref (client_cache);
467 signal_closure->client = g_object_ref (client);
468 signal_closure->error_message = g_strdup (error_message);
469
470 idle_source = g_idle_source_new ();
471 g_source_set_callback (
472 idle_source,
473 client_cache_emit_backend_error_idle_cb,
474 signal_closure,
475 (GDestroyNotify) signal_closure_free);
476 g_source_attach (
477 idle_source, client_cache->priv->main_context);
478 g_source_unref (idle_source);
479
480 g_object_unref (client_cache);
481 }
482 }
483
484 static void
client_cache_notify_cb(EClient * client,GParamSpec * pspec,ClientData * client_data)485 client_cache_notify_cb (EClient *client,
486 GParamSpec *pspec,
487 ClientData *client_data)
488 {
489 EClientCache *client_cache;
490
491 client_cache = g_weak_ref_get (&client_data->client_cache);
492
493 if (client_cache != NULL) {
494 GSource *idle_source;
495 SignalClosure *signal_closure;
496
497 signal_closure = g_slice_new0 (SignalClosure);
498 signal_closure->client_cache = g_object_ref (client_cache);
499 signal_closure->client = g_object_ref (client);
500 signal_closure->pspec = g_param_spec_ref (pspec);
501
502 idle_source = g_idle_source_new ();
503 g_source_set_callback (
504 idle_source,
505 client_cache_emit_client_notify_idle_cb,
506 signal_closure,
507 (GDestroyNotify) signal_closure_free);
508 g_source_attach (
509 idle_source, client_cache->priv->main_context);
510 g_source_unref (idle_source);
511
512 g_object_unref (client_cache);
513 }
514 }
515
516 static void
client_cache_process_results(ClientData * client_data,EClient * client,const GError * error)517 client_cache_process_results (ClientData *client_data,
518 EClient *client,
519 const GError *error)
520 {
521 GQueue queue = G_QUEUE_INIT;
522
523 /* Sanity check. */
524 g_return_if_fail (
525 ((client != NULL) && (error == NULL)) ||
526 ((client == NULL) && (error != NULL)));
527
528 g_mutex_lock (&client_data->lock);
529
530 /* Complete async operations outside the lock. */
531 e_queue_transfer (&client_data->connecting, &queue);
532
533 if (client != NULL) {
534 EClientCache *client_cache;
535
536 /* Make sure we're not leaking a reference. This can happen when
537 a synchronous and an asynchronous open are interleaving. The
538 synchronous open bypasses pending openings, thus can eventually
539 overwrite, or preset, the client.
540 */
541 g_clear_object (&client_data->client);
542
543 client_data->client = g_object_ref (client);
544 client_data->dead_backend = FALSE;
545
546 client_cache = g_weak_ref_get (&client_data->client_cache);
547
548 /* If the EClientCache has been disposed already,
549 * there's no point in connecting signal handlers. */
550 if (client_cache != NULL) {
551 GSource *idle_source;
552 SignalClosure *signal_closure;
553 gulong handler_id;
554
555 /* client_data_dispose() will break the
556 * reference cycles we're creating here. */
557
558 handler_id = g_signal_connect_data (
559 client, "backend-died",
560 G_CALLBACK (client_cache_backend_died_cb),
561 client_data_ref (client_data),
562 (GClosureNotify) client_data_unref,
563 0);
564 client_data->backend_died_handler_id = handler_id;
565
566 handler_id = g_signal_connect_data (
567 client, "backend-error",
568 G_CALLBACK (client_cache_backend_error_cb),
569 client_data_ref (client_data),
570 (GClosureNotify) client_data_unref,
571 0);
572 client_data->backend_error_handler_id = handler_id;
573
574 handler_id = g_signal_connect_data (
575 client, "notify",
576 G_CALLBACK (client_cache_notify_cb),
577 client_data_ref (client_data),
578 (GClosureNotify) client_data_unref,
579 0);
580 client_data->notify_handler_id = handler_id;
581
582 g_signal_emit (client_cache, signals[CLIENT_CONNECTED], 0, client);
583
584 signal_closure = g_slice_new0 (SignalClosure);
585 signal_closure->client_cache =
586 g_object_ref (client_cache);
587 signal_closure->client = g_object_ref (client);
588
589 idle_source = g_idle_source_new ();
590 g_source_set_callback (
591 idle_source,
592 client_cache_emit_client_created_idle_cb,
593 signal_closure,
594 (GDestroyNotify) signal_closure_free);
595 g_source_attach (
596 idle_source, client_cache->priv->main_context);
597 g_source_unref (idle_source);
598
599 g_object_unref (client_cache);
600 }
601 }
602
603 g_mutex_unlock (&client_data->lock);
604
605 while (!g_queue_is_empty (&queue)) {
606 GSimpleAsyncResult *simple;
607
608 simple = g_queue_pop_head (&queue);
609 if (client != NULL)
610 g_simple_async_result_set_op_res_gpointer (
611 simple, g_object_ref (client),
612 (GDestroyNotify) g_object_unref);
613 if (error != NULL)
614 g_simple_async_result_set_from_error (simple, error);
615 g_simple_async_result_complete_in_idle (simple);
616 g_object_unref (simple);
617 }
618 }
619
620 static void
client_cache_book_connect_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)621 client_cache_book_connect_cb (GObject *source_object,
622 GAsyncResult *result,
623 gpointer user_data)
624 {
625 ClientData *client_data = user_data;
626 EClient *client;
627 GError *error = NULL;
628
629 client = e_book_client_connect_finish (result, &error);
630
631 client_cache_process_results (client_data, client, error);
632
633 if (client != NULL)
634 g_object_unref (client);
635
636 if (error != NULL)
637 g_error_free (error);
638
639 client_data_unref (client_data);
640 }
641
642 static void
client_cache_cal_connect_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)643 client_cache_cal_connect_cb (GObject *source_object,
644 GAsyncResult *result,
645 gpointer user_data)
646 {
647 ClientData *client_data = user_data;
648 EClient *client;
649 GError *error = NULL;
650
651 client = e_cal_client_connect_finish (result, &error);
652
653 client_cache_process_results (client_data, client, error);
654
655 if (client != NULL)
656 g_object_unref (client);
657
658 if (error != NULL)
659 g_error_free (error);
660
661 client_data_unref (client_data);
662 }
663
664 static void
client_cache_source_removed_cb(ESourceRegistry * registry,ESource * source,GWeakRef * weak_ref)665 client_cache_source_removed_cb (ESourceRegistry *registry,
666 ESource *source,
667 GWeakRef *weak_ref)
668 {
669 EClientCache *client_cache;
670
671 client_cache = g_weak_ref_get (weak_ref);
672
673 if (client_cache != NULL) {
674 client_ht_remove (client_cache, source);
675 g_object_unref (client_cache);
676 }
677 }
678
679 static void
client_cache_source_disabled_cb(ESourceRegistry * registry,ESource * source,GWeakRef * weak_ref)680 client_cache_source_disabled_cb (ESourceRegistry *registry,
681 ESource *source,
682 GWeakRef *weak_ref)
683 {
684 EClientCache *client_cache;
685
686 client_cache = g_weak_ref_get (weak_ref);
687
688 if (client_cache != NULL) {
689 e_client_cache_emit_allow_auth_prompt (client_cache, source);
690
691 client_ht_remove (client_cache, source);
692 g_object_unref (client_cache);
693 }
694 }
695
696 static void
client_cache_set_registry(EClientCache * client_cache,ESourceRegistry * registry)697 client_cache_set_registry (EClientCache *client_cache,
698 ESourceRegistry *registry)
699 {
700 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
701 g_return_if_fail (client_cache->priv->registry == NULL);
702
703 client_cache->priv->registry = g_object_ref (registry);
704 }
705
706 static void
client_cache_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)707 client_cache_set_property (GObject *object,
708 guint property_id,
709 const GValue *value,
710 GParamSpec *pspec)
711 {
712 switch (property_id) {
713 case PROP_REGISTRY:
714 client_cache_set_registry (
715 E_CLIENT_CACHE (object),
716 g_value_get_object (value));
717 return;
718 }
719
720 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
721 }
722
723 static void
client_cache_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)724 client_cache_get_property (GObject *object,
725 guint property_id,
726 GValue *value,
727 GParamSpec *pspec)
728 {
729 switch (property_id) {
730 case PROP_REGISTRY:
731 g_value_take_object (
732 value,
733 e_client_cache_ref_registry (
734 E_CLIENT_CACHE (object)));
735 return;
736 }
737
738 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
739 }
740
741 static void
client_cache_dispose(GObject * object)742 client_cache_dispose (GObject *object)
743 {
744 EClientCachePrivate *priv;
745
746 priv = E_CLIENT_CACHE_GET_PRIVATE (object);
747
748 if (priv->source_removed_handler_id > 0) {
749 g_signal_handler_disconnect (
750 priv->registry,
751 priv->source_removed_handler_id);
752 priv->source_removed_handler_id = 0;
753 }
754
755 if (priv->source_disabled_handler_id > 0) {
756 g_signal_handler_disconnect (
757 priv->registry,
758 priv->source_disabled_handler_id);
759 priv->source_disabled_handler_id = 0;
760 }
761
762 g_clear_object (&priv->registry);
763
764 g_hash_table_remove_all (priv->client_ht);
765
766 g_clear_pointer (&priv->main_context, g_main_context_unref);
767
768 /* Chain up to parent's dispose() method. */
769 G_OBJECT_CLASS (e_client_cache_parent_class)->dispose (object);
770 }
771
772 static void
client_cache_finalize(GObject * object)773 client_cache_finalize (GObject *object)
774 {
775 EClientCachePrivate *priv;
776
777 priv = E_CLIENT_CACHE_GET_PRIVATE (object);
778
779 g_hash_table_destroy (priv->client_ht);
780 g_mutex_clear (&priv->client_ht_lock);
781
782 /* Chain up to parent's finalize() method. */
783 G_OBJECT_CLASS (e_client_cache_parent_class)->finalize (object);
784 }
785
786 static void
client_cache_constructed(GObject * object)787 client_cache_constructed (GObject *object)
788 {
789 EClientCache *client_cache;
790 ESourceRegistry *registry;
791 gulong handler_id;
792
793 client_cache = E_CLIENT_CACHE (object);
794
795 /* Chain up to parent's constructed() method. */
796 G_OBJECT_CLASS (e_client_cache_parent_class)->constructed (object);
797
798 registry = e_client_cache_ref_registry (client_cache);
799
800 handler_id = g_signal_connect_data (
801 registry, "source-removed",
802 G_CALLBACK (client_cache_source_removed_cb),
803 e_weak_ref_new (client_cache),
804 (GClosureNotify) e_weak_ref_free, 0);
805 client_cache->priv->source_removed_handler_id = handler_id;
806
807 handler_id = g_signal_connect_data (
808 registry, "source-disabled",
809 G_CALLBACK (client_cache_source_disabled_cb),
810 e_weak_ref_new (client_cache),
811 (GClosureNotify) e_weak_ref_free, 0);
812 client_cache->priv->source_disabled_handler_id = handler_id;
813
814 g_object_unref (registry);
815
816 e_extensible_load_extensions (E_EXTENSIBLE (object));
817 }
818
819 static void
e_client_cache_class_init(EClientCacheClass * class)820 e_client_cache_class_init (EClientCacheClass *class)
821 {
822 GObjectClass *object_class;
823
824 g_type_class_add_private (class, sizeof (EClientCachePrivate));
825
826 object_class = G_OBJECT_CLASS (class);
827 object_class->set_property = client_cache_set_property;
828 object_class->get_property = client_cache_get_property;
829 object_class->dispose = client_cache_dispose;
830 object_class->finalize = client_cache_finalize;
831 object_class->constructed = client_cache_constructed;
832
833 /**
834 * EClientCache:registry:
835 *
836 * The #ESourceRegistry manages #ESource instances.
837 **/
838 g_object_class_install_property (
839 object_class,
840 PROP_REGISTRY,
841 g_param_spec_object (
842 "registry",
843 "Registry",
844 "Data source registry",
845 E_TYPE_SOURCE_REGISTRY,
846 G_PARAM_READWRITE |
847 G_PARAM_CONSTRUCT_ONLY |
848 G_PARAM_STATIC_STRINGS));
849
850 /**
851 * EClientCache::backend-died:
852 * @client_cache: the #EClientCache that received the signal
853 * @client: the #EClient that received the D-Bus notification
854 * @alert: an #EAlert with a user-friendly error description
855 *
856 * Rebroadcasts an #EClient::backend-died signal emitted by @client,
857 * along with a pre-formatted #EAlert.
858 *
859 * As a convenience to signal handlers, this signal is always
860 * emitted from the #GMainContext that was thread-default when
861 * the @client_cache was created.
862 **/
863 signals[BACKEND_DIED] = g_signal_new (
864 "backend-died",
865 G_TYPE_FROM_CLASS (class),
866 G_SIGNAL_RUN_LAST,
867 G_STRUCT_OFFSET (EClientCacheClass, backend_died),
868 NULL, NULL, NULL,
869 G_TYPE_NONE, 2,
870 E_TYPE_CLIENT,
871 E_TYPE_ALERT);
872
873 /**
874 * EClientCache::backend-error:
875 * @client_cache: the #EClientCache that received the signal
876 * @client: the #EClient that received the D-Bus notification
877 * @alert: an #EAlert with a user-friendly error description
878 *
879 * Rebroadcasts an #EClient::backend-error signal emitted by @client,
880 * along with a pre-formatted #EAlert.
881 *
882 * As a convenience to signal handlers, this signal is always
883 * emitted from the #GMainContext that was thread-default when
884 * the @client_cache was created.
885 **/
886 signals[BACKEND_ERROR] = g_signal_new (
887 "backend-error",
888 G_TYPE_FROM_CLASS (class),
889 G_SIGNAL_RUN_LAST,
890 G_STRUCT_OFFSET (EClientCacheClass, backend_error),
891 NULL, NULL, NULL,
892 G_TYPE_NONE, 2,
893 E_TYPE_CLIENT,
894 E_TYPE_ALERT);
895
896 /**
897 * EClientCache::client-connected:
898 * @client_cache: the #EClientCache that received the signal
899 * @client: the newly-created #EClient
900 *
901 * This signal is emitted when a call to e_client_cache_get_client()
902 * triggers the creation of a new #EClient instance, immediately after
903 * the client's opening phase is over.
904 *
905 * See the difference with EClientCache::client-created, which is
906 * called on idle.
907 **/
908 signals[CLIENT_CONNECTED] = g_signal_new (
909 "client-connected",
910 G_TYPE_FROM_CLASS (class),
911 G_SIGNAL_RUN_FIRST,
912 G_STRUCT_OFFSET (EClientCacheClass, client_connected),
913 NULL, NULL, NULL,
914 G_TYPE_NONE, 1,
915 E_TYPE_CLIENT);
916
917 /**
918 * EClientCache::client-created:
919 * @client_cache: the #EClientCache that received the signal
920 * @client: the newly-created #EClient
921 *
922 * This signal is emitted when a call to e_client_cache_get_client()
923 * triggers the creation of a new #EClient instance, invoked in an idle
924 * callback.
925 *
926 * See the difference with EClientCache::client-connected, which is
927 * called immediately.
928 **/
929 signals[CLIENT_CREATED] = g_signal_new (
930 "client-created",
931 G_TYPE_FROM_CLASS (class),
932 G_SIGNAL_RUN_FIRST,
933 G_STRUCT_OFFSET (EClientCacheClass, client_created),
934 NULL, NULL, NULL,
935 G_TYPE_NONE, 1,
936 E_TYPE_CLIENT);
937
938 /**
939 * EClientCache::client-notify:
940 * @client_cache: the #EClientCache that received the signal
941 * @client: the #EClient whose property changed
942 * @pspec: the #GParamSpec of the property that changed
943 *
944 * Rebroadcasts a #GObject::notify signal emitted by @client.
945 *
946 * This signal supports "::detail" appendices to the signal name
947 * just like the #GObject::notify signal, so you can connect to
948 * change notification signals for specific #EClient properties.
949 *
950 * As a convenience to signal handlers, this signal is always
951 * emitted from the #GMainContext that was thread-default when
952 * the @client_cache was created.
953 **/
954 signals[CLIENT_NOTIFY] = g_signal_new (
955 "client-notify",
956 G_TYPE_FROM_CLASS (class),
957 /* same flags as GObject::notify */
958 G_SIGNAL_RUN_FIRST |
959 G_SIGNAL_NO_RECURSE |
960 G_SIGNAL_DETAILED |
961 G_SIGNAL_NO_HOOKS |
962 G_SIGNAL_ACTION,
963 G_STRUCT_OFFSET (EClientCacheClass, client_notify),
964 NULL, NULL, NULL,
965 G_TYPE_NONE, 2,
966 E_TYPE_CLIENT,
967 G_TYPE_PARAM);
968
969 /**
970 * EClientCache::allow-auth-prompt:
971 * @client_cache: an #EClientCache, which sent the signal
972 * @source: an #ESource
973 *
974 * This signal is emitted with e_client_cache_emit_allow_auth_prompt() to let
975 * any listeners know to enable credentials prompt for the given @source.
976 *
977 * Since: 3.16
978 **/
979 signals[ALLOW_AUTH_PROMPT] = g_signal_new (
980 "allow-auth-prompt",
981 G_TYPE_FROM_CLASS (class),
982 G_SIGNAL_RUN_FIRST,
983 G_STRUCT_OFFSET (EClientCacheClass, allow_auth_prompt),
984 NULL, NULL, NULL,
985 G_TYPE_NONE, 1,
986 E_TYPE_SOURCE);
987 }
988
989 static void
e_client_cache_init(EClientCache * client_cache)990 e_client_cache_init (EClientCache *client_cache)
991 {
992 GHashTable *client_ht;
993 gint ii;
994
995 const gchar *extension_names[] = {
996 E_SOURCE_EXTENSION_ADDRESS_BOOK,
997 E_SOURCE_EXTENSION_CALENDAR,
998 E_SOURCE_EXTENSION_MEMO_LIST,
999 E_SOURCE_EXTENSION_TASK_LIST
1000 };
1001
1002 client_ht = g_hash_table_new_full (
1003 (GHashFunc) g_str_hash,
1004 (GEqualFunc) g_str_equal,
1005 (GDestroyNotify) g_free,
1006 (GDestroyNotify) g_hash_table_unref);
1007
1008 client_cache->priv = E_CLIENT_CACHE_GET_PRIVATE (client_cache);
1009
1010 client_cache->priv->main_context = g_main_context_ref_thread_default ();
1011 client_cache->priv->client_ht = client_ht;
1012
1013 g_mutex_init (&client_cache->priv->client_ht_lock);
1014
1015 /* Pre-load the extension names that can be used to instantiate
1016 * EClients. Then we can validate an extension name by testing
1017 * for a matching hash table key. */
1018
1019 for (ii = 0; ii < G_N_ELEMENTS (extension_names); ii++) {
1020 GHashTable *inner_ht;
1021
1022 inner_ht = g_hash_table_new_full (
1023 (GHashFunc) e_source_hash,
1024 (GEqualFunc) e_source_equal,
1025 (GDestroyNotify) g_object_unref,
1026 (GDestroyNotify) client_data_dispose);
1027
1028 g_hash_table_insert (
1029 client_ht,
1030 g_strdup (extension_names[ii]),
1031 g_hash_table_ref (inner_ht));
1032
1033 g_hash_table_unref (inner_ht);
1034 }
1035 }
1036
1037 /**
1038 * e_client_cache_new:
1039 * @registry: an #ESourceRegistry
1040 *
1041 * Creates a new #EClientCache instance.
1042 *
1043 * Returns: an #EClientCache
1044 **/
1045 EClientCache *
e_client_cache_new(ESourceRegistry * registry)1046 e_client_cache_new (ESourceRegistry *registry)
1047 {
1048 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
1049
1050 return g_object_new (
1051 E_TYPE_CLIENT_CACHE,
1052 "registry", registry, NULL);
1053 }
1054
1055 /**
1056 * e_client_cache_ref_registry:
1057 * @client_cache: an #EClientCache
1058 *
1059 * Returns the #ESourceRegistry passed to e_client_cache_new().
1060 *
1061 * The returned #ESourceRegistry is referenced for thread-safety and must be
1062 * unreferenced with g_object_unref() when finished with it.
1063 *
1064 * Returns: an #ESourceRegistry
1065 **/
1066 ESourceRegistry *
e_client_cache_ref_registry(EClientCache * client_cache)1067 e_client_cache_ref_registry (EClientCache *client_cache)
1068 {
1069 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
1070
1071 return g_object_ref (client_cache->priv->registry);
1072 }
1073
1074 /**
1075 * e_client_cache_get_client_sync:
1076 * @client_cache: an #EClientCache
1077 * @source: an #ESource
1078 * @extension_name: an extension name
1079 * @wait_for_connected_seconds: timeout, in seconds, to wait for the backend to be fully connected
1080 * @cancellable: optional #GCancellable object, or %NULL
1081 * @error: return location for a #GError, or %NULL
1082 *
1083 * Obtains a shared #EClient instance for @source, or else creates a new
1084 * #EClient instance to be shared.
1085 *
1086 * The @extension_name determines the type of #EClient to obtain. Valid
1087 * @extension_name values are:
1088 *
1089 * #E_SOURCE_EXTENSION_ADDRESS_BOOK will obtain an #EBookClient.
1090 *
1091 * #E_SOURCE_EXTENSION_CALENDAR will obtain an #ECalClient with a
1092 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_EVENTS.
1093 *
1094 * #E_SOURCE_EXTENSION_MEMO_LIST will obtain an #ECalClient with a
1095 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_MEMOS.
1096 *
1097 * #E_SOURCE_EXTENSION_TASK_LIST will obtain an #ECalClient with a
1098 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_TASKS.
1099 *
1100 * The @source must already have an #ESourceExtension by that name
1101 * for this function to work. All other @extension_name values will
1102 * result in an error.
1103 *
1104 * The @wait_for_connected_seconds argument had been added since 3.16,
1105 * to let the caller decide how long to wait for the backend to fully
1106 * connect to its (possibly remote) data store. This is required due
1107 * to a change in the authentication process, which is fully asynchronous
1108 * and done on the client side, while not every client is supposed to
1109 * response to authentication requests. In case the backend will not connect
1110 * within the set interval, then it is opened in an offline mode. A special
1111 * value -1 can be used to not wait for the connected state at all.
1112 *
1113 * If a request for the same @source and @extension_name is already in
1114 * progress when this function is called, this request will "piggyback"
1115 * on the in-progress request such that they will both succeed or fail
1116 * simultaneously.
1117 *
1118 * Unreference the returned #EClient with g_object_unref() when finished
1119 * with it. If an error occurs, the function will set @error and return
1120 * %NULL.
1121 *
1122 * Returns: an #EClient, or %NULL
1123 **/
1124 EClient *
e_client_cache_get_client_sync(EClientCache * client_cache,ESource * source,const gchar * extension_name,guint32 wait_for_connected_seconds,GCancellable * cancellable,GError ** error)1125 e_client_cache_get_client_sync (EClientCache *client_cache,
1126 ESource *source,
1127 const gchar *extension_name,
1128 guint32 wait_for_connected_seconds,
1129 GCancellable *cancellable,
1130 GError **error)
1131 {
1132 ClientData *client_data;
1133 EClient *client = NULL;
1134 GError *local_error = NULL;
1135
1136 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
1137 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1138 g_return_val_if_fail (extension_name != NULL, NULL);
1139
1140 client_data = client_ht_lookup (client_cache, source, extension_name);
1141
1142 if (client_data == NULL) {
1143 g_set_error (
1144 error, G_IO_ERROR,
1145 G_IO_ERROR_INVALID_ARGUMENT,
1146 _("Cannot create a client object from "
1147 "extension name “%s”"), extension_name);
1148 return NULL;
1149 }
1150
1151 g_mutex_lock (&client_data->lock);
1152
1153 if (client_data->client != NULL)
1154 client = g_object_ref (client_data->client);
1155
1156 g_mutex_unlock (&client_data->lock);
1157
1158 /* If a cached EClient already exists, we're done. */
1159 if (client != NULL) {
1160 client_data_unref (client_data);
1161 return client;
1162 }
1163
1164 /* Create an appropriate EClient instance for the extension
1165 * name. The client_ht_lookup() call above ensures us that
1166 * one of these options will match. */
1167
1168 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
1169 client = e_book_client_connect_sync (source, wait_for_connected_seconds,
1170 cancellable, &local_error);
1171 } else if (g_str_equal (extension_name, E_SOURCE_EXTENSION_CALENDAR)) {
1172 client = e_cal_client_connect_sync (
1173 source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, wait_for_connected_seconds,
1174 cancellable, &local_error);
1175 } else if (g_str_equal (extension_name, E_SOURCE_EXTENSION_MEMO_LIST)) {
1176 client = e_cal_client_connect_sync (
1177 source, E_CAL_CLIENT_SOURCE_TYPE_MEMOS, wait_for_connected_seconds,
1178 cancellable, &local_error);
1179 } else if (g_str_equal (extension_name, E_SOURCE_EXTENSION_TASK_LIST)) {
1180 client = e_cal_client_connect_sync (
1181 source, E_CAL_CLIENT_SOURCE_TYPE_TASKS, wait_for_connected_seconds,
1182 cancellable, &local_error);
1183 } else {
1184 g_warn_if_reached (); /* Should never happen. */
1185 }
1186
1187 if (client)
1188 client_cache_process_results (client_data, client, local_error);
1189
1190 if (local_error)
1191 g_propagate_error (error, local_error);
1192
1193 client_data_unref (client_data);
1194
1195 return client;
1196 }
1197
1198 /**
1199 * e_client_cache_get_client:
1200 * @client_cache: an #EClientCache
1201 * @source: an #ESource
1202 * @extension_name: an extension name
1203 * @wait_for_connected_seconds: timeout, in seconds, to wait for the backend to be fully connected
1204 * @cancellable: optional #GCancellable object, or %NULL
1205 * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1206 * @user_data: data to pass to the callback function
1207 *
1208 * Asynchronously obtains a shared #EClient instance for @source, or else
1209 * creates a new #EClient instance to be shared.
1210 *
1211 * The @extension_name determines the type of #EClient to obtain. Valid
1212 * @extension_name values are:
1213 *
1214 * #E_SOURCE_EXTENSION_ADDRESS_BOOK will obtain an #EBookClient.
1215 *
1216 * #E_SOURCE_EXTENSION_CALENDAR will obtain an #ECalClient with a
1217 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_EVENTS.
1218 *
1219 * #E_SOURCE_EXTENSION_MEMO_LIST will obtain an #ECalClient with a
1220 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_MEMOS.
1221 *
1222 * #E_SOURCE_EXTENSION_TASK_LIST will obtain an #ECalClient with a
1223 * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_TASKS.
1224 *
1225 * The @source must already have an #ESourceExtension by that name
1226 * for this function to work. All other @extension_name values will
1227 * result in an error.
1228 *
1229 * The @wait_for_connected_seconds argument had been added since 3.16,
1230 * to let the caller decide how long to wait for the backend to fully
1231 * connect to its (possibly remote) data store. This is required due
1232 * to a change in the authentication process, which is fully asynchronous
1233 * and done on the client side, while not every client is supposed to
1234 * response to authentication requests. In case the backend will not connect
1235 * within the set interval, then it is opened in an offline mode. A special
1236 * value -1 can be used to not wait for the connected state at all.
1237 *
1238 * If a request for the same @source and @extension_name is already in
1239 * progress when this function is called, this request will "piggyback"
1240 * on the in-progress request such that they will both succeed or fail
1241 * simultaneously.
1242 *
1243 * When the operation is finished, @callback will be called. You can
1244 * then call e_client_cache_get_client_finish() to get the result of the
1245 * operation.
1246 **/
1247 void
e_client_cache_get_client(EClientCache * client_cache,ESource * source,const gchar * extension_name,guint32 wait_for_connected_seconds,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1248 e_client_cache_get_client (EClientCache *client_cache,
1249 ESource *source,
1250 const gchar *extension_name,
1251 guint32 wait_for_connected_seconds,
1252 GCancellable *cancellable,
1253 GAsyncReadyCallback callback,
1254 gpointer user_data)
1255 {
1256 GSimpleAsyncResult *simple;
1257 ClientData *client_data;
1258 EClient *client = NULL;
1259 gboolean connect_in_progress = FALSE;
1260
1261 g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
1262 g_return_if_fail (E_IS_SOURCE (source));
1263 g_return_if_fail (extension_name != NULL);
1264
1265 simple = g_simple_async_result_new (
1266 G_OBJECT (client_cache), callback,
1267 user_data, e_client_cache_get_client);
1268
1269 g_simple_async_result_set_check_cancellable (simple, cancellable);
1270
1271 client_data = client_ht_lookup (client_cache, source, extension_name);
1272
1273 if (client_data == NULL) {
1274 g_simple_async_result_set_error (
1275 simple, G_IO_ERROR,
1276 G_IO_ERROR_INVALID_ARGUMENT,
1277 _("Cannot create a client object from "
1278 "extension name “%s”"), extension_name);
1279 g_simple_async_result_complete_in_idle (simple);
1280 goto exit;
1281 }
1282
1283 g_mutex_lock (&client_data->lock);
1284
1285 if (client_data->client != NULL) {
1286 client = g_object_ref (client_data->client);
1287 } else {
1288 GQueue *connecting = &client_data->connecting;
1289 connect_in_progress = !g_queue_is_empty (connecting);
1290 g_queue_push_tail (connecting, g_object_ref (simple));
1291 }
1292
1293 g_mutex_unlock (&client_data->lock);
1294
1295 /* If a cached EClient already exists, we're done. */
1296 if (client != NULL) {
1297 g_simple_async_result_set_op_res_gpointer (
1298 simple, client, (GDestroyNotify) g_object_unref);
1299 g_simple_async_result_complete_in_idle (simple);
1300 goto exit;
1301 }
1302
1303 /* If an EClient connection attempt is already in progress, our
1304 * cache request will complete when it finishes, so now we wait. */
1305 if (connect_in_progress)
1306 goto exit;
1307
1308 /* Create an appropriate EClient instance for the extension
1309 * name. The client_ht_lookup() call above ensures us that
1310 * one of these options will match. */
1311
1312 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
1313 e_book_client_connect (
1314 source, wait_for_connected_seconds, cancellable,
1315 client_cache_book_connect_cb,
1316 client_data_ref (client_data));
1317 goto exit;
1318 }
1319
1320 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_CALENDAR)) {
1321 e_cal_client_connect (
1322 source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, wait_for_connected_seconds,
1323 cancellable, client_cache_cal_connect_cb,
1324 client_data_ref (client_data));
1325 goto exit;
1326 }
1327
1328 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_MEMO_LIST)) {
1329 e_cal_client_connect (
1330 source, E_CAL_CLIENT_SOURCE_TYPE_MEMOS, wait_for_connected_seconds,
1331 cancellable, client_cache_cal_connect_cb,
1332 client_data_ref (client_data));
1333 goto exit;
1334 }
1335
1336 if (g_str_equal (extension_name, E_SOURCE_EXTENSION_TASK_LIST)) {
1337 e_cal_client_connect (
1338 source, E_CAL_CLIENT_SOURCE_TYPE_TASKS, wait_for_connected_seconds,
1339 cancellable, client_cache_cal_connect_cb,
1340 client_data_ref (client_data));
1341 goto exit;
1342 }
1343
1344 g_warn_if_reached (); /* Should never happen. */
1345
1346 exit:
1347 if (client_data)
1348 client_data_unref (client_data);
1349 g_object_unref (simple);
1350 }
1351
1352 /**
1353 * e_client_cache_get_client_finish:
1354 * @client_cache: an #EClientCache
1355 * @result: a #GAsyncResult
1356 * @error: return location for a #GError, or %NULL
1357 *
1358 * Finishes the operation started with e_client_cache_get_client().
1359 *
1360 * Unreference the returned #EClient with g_object_unref() when finished
1361 * with it. If an error occurred, the function will set @error and return
1362 * %NULL.
1363 *
1364 * Returns: an #EClient, or %NULL
1365 **/
1366 EClient *
e_client_cache_get_client_finish(EClientCache * client_cache,GAsyncResult * result,GError ** error)1367 e_client_cache_get_client_finish (EClientCache *client_cache,
1368 GAsyncResult *result,
1369 GError **error)
1370 {
1371 GSimpleAsyncResult *simple;
1372 EClient *client;
1373
1374 g_return_val_if_fail (
1375 g_simple_async_result_is_valid (
1376 result, G_OBJECT (client_cache),
1377 e_client_cache_get_client), NULL);
1378
1379 simple = G_SIMPLE_ASYNC_RESULT (result);
1380
1381 if (g_simple_async_result_propagate_error (simple, error))
1382 return NULL;
1383
1384 client = g_simple_async_result_get_op_res_gpointer (simple);
1385 g_return_val_if_fail (client != NULL, NULL);
1386
1387 return g_object_ref (client);
1388 }
1389
1390 /**
1391 * e_client_cache_ref_cached_client:
1392 * @client_cache: an #EClientCache
1393 * @source: an #ESource
1394 * @extension_name: an extension name
1395 *
1396 * Returns a shared #EClient instance for @source and @extension_name if
1397 * such an instance is already cached, or else %NULL. This function does
1398 * not create a new #EClient instance, and therefore does not block.
1399 *
1400 * See e_client_cache_get_client() for valid @extension_name values.
1401 *
1402 * The returned #EClient is referenced for thread-safety and must be
1403 * unreferenced with g_object_unref() when finished with it.
1404 *
1405 * Returns: an #EClient, or %NULL
1406 **/
1407 EClient *
e_client_cache_ref_cached_client(EClientCache * client_cache,ESource * source,const gchar * extension_name)1408 e_client_cache_ref_cached_client (EClientCache *client_cache,
1409 ESource *source,
1410 const gchar *extension_name)
1411 {
1412 ClientData *client_data;
1413 EClient *client = NULL;
1414
1415 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
1416 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1417 g_return_val_if_fail (extension_name != NULL, NULL);
1418
1419 client_data = client_ht_lookup (client_cache, source, extension_name);
1420
1421 if (client_data != NULL) {
1422 g_mutex_lock (&client_data->lock);
1423 if (client_data->client != NULL)
1424 client = g_object_ref (client_data->client);
1425 g_mutex_unlock (&client_data->lock);
1426
1427 client_data_unref (client_data);
1428 }
1429
1430 return client;
1431 }
1432
1433 /**
1434 * e_client_cache_is_backend_dead:
1435 * @client_cache: an #EClientCache
1436 * @source: an #ESource
1437 * @extension_name: an extension name
1438 *
1439 * Returns %TRUE if an #EClient instance for @source and @extension_name
1440 * was recently discarded after having emitted an #EClient::backend-died
1441 * signal, and a replacement #EClient instance has not yet been created.
1442 *
1443 * Returns: whether the backend for @source and @extension_name died
1444 **/
1445 gboolean
e_client_cache_is_backend_dead(EClientCache * client_cache,ESource * source,const gchar * extension_name)1446 e_client_cache_is_backend_dead (EClientCache *client_cache,
1447 ESource *source,
1448 const gchar *extension_name)
1449 {
1450 ClientData *client_data;
1451 gboolean dead_backend = FALSE;
1452
1453 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), FALSE);
1454 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1455 g_return_val_if_fail (extension_name != NULL, FALSE);
1456
1457 client_data = client_ht_lookup (client_cache, source, extension_name);
1458
1459 if (client_data != NULL) {
1460 dead_backend = client_data->dead_backend;
1461 client_data_unref (client_data);
1462 }
1463
1464 return dead_backend;
1465 }
1466
1467 /**
1468 * e_client_cache_emit_allow_auth_prompt:
1469 * @client_cache: an #EClientCache
1470 * @source: an #ESource
1471 *
1472 * Emits 'allow-auth-prompt' on @client_cache for @source. This lets
1473 * any listeners know to enable credentials prompt for this @source.
1474 *
1475 * Since: 3.16
1476 **/
1477 void
e_client_cache_emit_allow_auth_prompt(EClientCache * client_cache,ESource * source)1478 e_client_cache_emit_allow_auth_prompt (EClientCache *client_cache,
1479 ESource *source)
1480 {
1481 g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
1482 g_return_if_fail (E_IS_SOURCE (source));
1483
1484 g_signal_emit (client_cache, signals[ALLOW_AUTH_PROMPT], 0, source);
1485 }
1486