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