1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /**
19  * SECTION: e-config-lookup
20  * @include: e-util/e-util.h
21  * @short_description: Configuration lookup
22  *
23  * #EConfigLookup is used to search for configuration of an account,
24  * which is identified by an e-mail address, server address or such.
25  * It is an #EExtensible object, where the extensions connect to
26  * the #EConfigLookup::run signal to run the configuration lookup.
27  **/
28 
29 #include "evolution-config.h"
30 
31 #include <glib/gi18n-lib.h>
32 #include <camel/camel.h>
33 #include <libedataserver/libedataserver.h>
34 
35 #include "e-config-lookup-result.h"
36 #include "e-config-lookup-worker.h"
37 #include "e-simple-async-result.h"
38 #include "e-util-enumtypes.h"
39 
40 #include "e-config-lookup.h"
41 
42 struct _EConfigLookupPrivate {
43 	ESourceRegistry *registry;
44 
45 	GMutex property_lock;
46 	GSList *workers; /* EConfigLookupWorker * */
47 	GSList *results; /* EConfigLookupResult * */
48 
49 	ESimpleAsyncResult *run_result;
50 	GCancellable *run_cancellable;
51 	GSList *worker_cancellables; /* CamelOperation * */
52 
53 	GThreadPool *pool;
54 };
55 
56 enum {
57 	PROP_0,
58 	PROP_REGISTRY,
59 	PROP_BUSY
60 };
61 
62 enum {
63 	GET_SOURCE,
64 	WORKER_STARTED,
65 	WORKER_FINISHED,
66 	RESULT_ADDED,
67 	LAST_SIGNAL
68 };
69 
70 static guint signals[LAST_SIGNAL];
71 
72 G_DEFINE_TYPE_WITH_CODE (EConfigLookup, e_config_lookup, G_TYPE_OBJECT,
73 	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
74 
75 enum {
76 	EMIT_BUSY		= 1 << 0,
77 	EMIT_WORKER_STARTED	= 1 << 1,
78 	EMIT_WORKER_FINISHED	= 1 << 2
79 };
80 
81 typedef struct _EmitData {
82 	EConfigLookup *config_lookup;
83 	EConfigLookupWorker *worker;
84 	guint32 flags;
85 	GCancellable *cancellable;
86 	ENamedParameters *params;
87 	GError *error;
88 } EmitData;
89 
90 static void
emit_data_free(gpointer ptr)91 emit_data_free (gpointer ptr)
92 {
93 	EmitData *ed = ptr;
94 
95 	if (ed) {
96 		e_named_parameters_free (ed->params);
97 		g_clear_object (&ed->config_lookup);
98 		g_clear_object (&ed->worker);
99 		g_clear_object (&ed->cancellable);
100 		g_clear_error (&ed->error);
101 		g_slice_free (EmitData, ed);
102 	}
103 }
104 
105 static gboolean
config_lookup_emit_idle_cb(gpointer user_data)106 config_lookup_emit_idle_cb (gpointer user_data)
107 {
108 	EmitData *ed = user_data;
109 
110 	g_return_val_if_fail (ed != NULL, FALSE);
111 	g_return_val_if_fail (E_IS_CONFIG_LOOKUP (ed->config_lookup), FALSE);
112 
113 	if ((ed->flags & EMIT_WORKER_STARTED) != 0)
114 		g_signal_emit (ed->config_lookup, signals[WORKER_STARTED], 0, ed->worker, ed->cancellable);
115 
116 	if ((ed->flags & EMIT_WORKER_FINISHED) != 0)
117 		g_signal_emit (ed->config_lookup, signals[WORKER_FINISHED], 0, ed->worker, ed->params, ed->error);
118 
119 	if ((ed->flags & EMIT_BUSY) != 0)
120 		g_object_notify (G_OBJECT (ed->config_lookup), "busy");
121 
122 	return FALSE;
123 }
124 
125 static void
config_lookup_schedule_emit_idle(EConfigLookup * config_lookup,guint32 emit_flags,EConfigLookupWorker * worker,GCancellable * cancellable,const ENamedParameters * params,const GError * error)126 config_lookup_schedule_emit_idle (EConfigLookup *config_lookup,
127 				  guint32 emit_flags,
128 				  EConfigLookupWorker *worker,
129 				  GCancellable *cancellable,
130 				  const ENamedParameters *params,
131 				  const GError *error)
132 {
133 	EmitData *ed;
134 
135 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
136 	if (worker)
137 		g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker));
138 
139 	ed = g_slice_new0 (EmitData);
140 	ed->config_lookup = g_object_ref (config_lookup);
141 	ed->flags = emit_flags;
142 	ed->worker = worker ? g_object_ref (worker) : NULL;
143 	ed->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
144 	ed->params = params ? e_named_parameters_new_clone (params) : NULL;
145 	ed->error = error ? g_error_copy (error) : NULL;
146 
147 	g_idle_add_full (G_PRIORITY_HIGH_IDLE, config_lookup_emit_idle_cb, ed, emit_data_free);
148 }
149 
150 typedef struct _ThreadData {
151 	ENamedParameters *params;
152 	EConfigLookupWorker *worker;
153 	GCancellable *cancellable;
154 } ThreadData;
155 
156 static void
config_lookup_thread(gpointer data,gpointer user_data)157 config_lookup_thread (gpointer data,
158 		      gpointer user_data)
159 {
160 	ThreadData *td = data;
161 	EConfigLookup *config_lookup = user_data;
162 	ESimpleAsyncResult *run_result = NULL;
163 	guint32 emit_flags;
164 	ENamedParameters *restart_params = NULL;
165 	GError *error = NULL;
166 
167 	g_return_if_fail (td != NULL);
168 	g_return_if_fail (td->params != NULL);
169 	g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (td->worker));
170 	g_return_if_fail (G_IS_CANCELLABLE (td->cancellable));
171 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
172 
173 	e_config_lookup_worker_run (td->worker, config_lookup, td->params, &restart_params, td->cancellable, &error);
174 
175 	g_mutex_lock (&config_lookup->priv->property_lock);
176 
177 	emit_flags = EMIT_WORKER_FINISHED;
178 
179 	if (g_slist_find (config_lookup->priv->worker_cancellables, td->cancellable)) {
180 		config_lookup->priv->worker_cancellables = g_slist_remove (config_lookup->priv->worker_cancellables, td->cancellable);
181 		g_object_unref (td->cancellable);
182 
183 		if (!config_lookup->priv->worker_cancellables)
184 			emit_flags |= EMIT_BUSY;
185 	}
186 
187 	config_lookup_schedule_emit_idle (config_lookup, emit_flags, td->worker, NULL, restart_params, error);
188 
189 	if ((emit_flags & EMIT_BUSY) != 0) {
190 		run_result = config_lookup->priv->run_result;
191 		config_lookup->priv->run_result = NULL;
192 
193 		g_clear_object (&config_lookup->priv->run_cancellable);
194 	}
195 
196 	g_mutex_unlock (&config_lookup->priv->property_lock);
197 
198 	if (run_result) {
199 		e_simple_async_result_complete_idle_take (run_result);
200 	}
201 
202 	e_named_parameters_free (restart_params);
203 	e_named_parameters_free (td->params);
204 	g_clear_object (&td->worker);
205 	g_clear_object (&td->cancellable);
206 	g_clear_error (&error);
207 	g_slice_free (ThreadData, td);
208 }
209 
210 static void
config_lookup_set_registry(EConfigLookup * config_lookup,ESourceRegistry * registry)211 config_lookup_set_registry (EConfigLookup *config_lookup,
212 			    ESourceRegistry *registry)
213 {
214 	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
215 	g_return_if_fail (config_lookup->priv->registry == NULL);
216 
217 	config_lookup->priv->registry = g_object_ref (registry);
218 }
219 
220 static void
config_lookup_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)221 config_lookup_set_property (GObject *object,
222 			    guint property_id,
223 			    const GValue *value,
224 			    GParamSpec *pspec)
225 {
226 	switch (property_id) {
227 		case PROP_REGISTRY:
228 			config_lookup_set_registry (
229 				E_CONFIG_LOOKUP (object),
230 				g_value_get_object (value));
231 			return;
232 	}
233 
234 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
235 }
236 
237 static void
config_lookup_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)238 config_lookup_get_property (GObject *object,
239 			    guint property_id,
240 			    GValue *value,
241 			    GParamSpec *pspec)
242 {
243 	switch (property_id) {
244 		case PROP_BUSY:
245 			g_value_set_boolean (
246 				value,
247 				e_config_lookup_get_busy (
248 				E_CONFIG_LOOKUP (object)));
249 			return;
250 
251 		case PROP_REGISTRY:
252 			g_value_set_object (
253 				value,
254 				e_config_lookup_get_registry (
255 				E_CONFIG_LOOKUP (object)));
256 			return;
257 	}
258 
259 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
260 }
261 
262 static void
config_lookup_constructed(GObject * object)263 config_lookup_constructed (GObject *object)
264 {
265 	/* Chain up to parent's method. */
266 	G_OBJECT_CLASS (e_config_lookup_parent_class)->constructed (object);
267 
268 	e_extensible_load_extensions (E_EXTENSIBLE (object));
269 }
270 
271 static void
config_lookup_dispose(GObject * object)272 config_lookup_dispose (GObject *object)
273 {
274 	EConfigLookup *config_lookup = E_CONFIG_LOOKUP (object);
275 	gboolean had_running_workers;
276 
277 	e_config_lookup_cancel_all (config_lookup);
278 
279 	if (config_lookup->priv->pool) {
280 		g_thread_pool_free (config_lookup->priv->pool, TRUE, TRUE);
281 		config_lookup->priv->pool = NULL;
282 	}
283 
284 	g_mutex_lock (&config_lookup->priv->property_lock);
285 
286 	g_clear_object (&config_lookup->priv->run_cancellable);
287 
288 	g_slist_free_full (config_lookup->priv->workers, g_object_unref);
289 	config_lookup->priv->workers = NULL;
290 
291 	had_running_workers = config_lookup->priv->worker_cancellables != NULL;
292 	g_slist_free_full (config_lookup->priv->worker_cancellables, g_object_unref);
293 	config_lookup->priv->worker_cancellables = NULL;
294 
295 	g_mutex_unlock (&config_lookup->priv->property_lock);
296 
297 	if (had_running_workers)
298 		g_object_notify (object, "busy");
299 
300 	g_clear_object (&config_lookup->priv->registry);
301 
302 	/* Chain up to parent's method. */
303 	G_OBJECT_CLASS (e_config_lookup_parent_class)->dispose (object);
304 }
305 
306 static void
config_lookup_finalize(GObject * object)307 config_lookup_finalize (GObject *object)
308 {
309 	EConfigLookup *config_lookup = E_CONFIG_LOOKUP (object);
310 
311 	g_slist_free_full (config_lookup->priv->results, g_object_unref);
312 	g_mutex_clear (&config_lookup->priv->property_lock);
313 
314 	/* Chain up to parent's method. */
315 	G_OBJECT_CLASS (e_config_lookup_parent_class)->finalize (object);
316 }
317 
318 static void
e_config_lookup_class_init(EConfigLookupClass * klass)319 e_config_lookup_class_init (EConfigLookupClass *klass)
320 {
321 	GObjectClass *object_class;
322 
323 	g_type_class_add_private (klass, sizeof (EConfigLookupPrivate));
324 
325 	object_class = G_OBJECT_CLASS (klass);
326 	object_class->set_property = config_lookup_set_property;
327 	object_class->get_property = config_lookup_get_property;
328 	object_class->constructed = config_lookup_constructed;
329 	object_class->dispose = config_lookup_dispose;
330 	object_class->finalize = config_lookup_finalize;
331 
332 	/**
333 	 * EConfigLookup:registry:
334 	 *
335 	 * The #ESourceRegistry manages #ESource instances.
336 	 *
337 	 * Since: 3.26
338 	 **/
339 	g_object_class_install_property (
340 		object_class,
341 		PROP_REGISTRY,
342 		g_param_spec_object (
343 			"registry",
344 			"Registry",
345 			"Data source registry",
346 			E_TYPE_SOURCE_REGISTRY,
347 			G_PARAM_READWRITE |
348 			G_PARAM_CONSTRUCT_ONLY |
349 			G_PARAM_STATIC_STRINGS));
350 
351 	/**
352 	 * EConfigLookup:busy:
353 	 *
354 	 * Whether the EConfigLookup has any running workers.
355 	 *
356 	 * Since: 3.28
357 	 **/
358 	g_object_class_install_property (
359 		object_class,
360 		PROP_BUSY,
361 		g_param_spec_boolean (
362 			"busy",
363 			"Busy",
364 			NULL,
365 			FALSE,
366 			G_PARAM_READABLE |
367 			G_PARAM_STATIC_STRINGS));
368 
369 	/**
370 	 * EConfigLookup::get-source:
371 	 * @kind: an #EConfigLookupSourceKind
372 	 *
373 	 * Emitted to get an #ESource of the given @kind. Return %NULL, when not available.
374 	 *
375 	 * Since: 3.26
376 	 **/
377 	signals[GET_SOURCE] = g_signal_new (
378 		"get-source",
379 		G_TYPE_FROM_CLASS (klass),
380 		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
381 		G_STRUCT_OFFSET (EConfigLookupClass, get_source),
382 		NULL, NULL,
383 		NULL,
384 		G_TYPE_POINTER, 1,
385 		E_TYPE_CONFIG_LOOKUP_SOURCE_KIND);
386 
387 	/**
388 	 * EConfigLookup::worker-started:
389 	 * @worker: an #EConfigLookupWorker
390 	 * @cancellable: associated #GCancellable for this worker run
391 	 *
392 	 * Emitted when the @worker is about to start running.
393 	 * Corresponding @EConfigLookup::worker-finished is emitted when
394 	 * the run is finished.
395 	 *
396 	 * Note that this signal is always emitted in the main thread.
397 	 *
398 	 * Since: 3.28
399 	 **/
400 	signals[WORKER_STARTED] = g_signal_new (
401 		"worker-started",
402 		G_TYPE_FROM_CLASS (klass),
403 		G_SIGNAL_RUN_LAST,
404 		G_STRUCT_OFFSET (EConfigLookupClass, worker_started),
405 		NULL, NULL,
406 		NULL,
407 		G_TYPE_NONE, 2,
408 		E_TYPE_CONFIG_LOOKUP_WORKER,
409 		G_TYPE_CANCELLABLE);
410 
411 	/**
412 	 * EConfigLookup::worker-finished:
413 	 * @worker: an #EConfigLookupWorker
414 	 * @restart_params: an optional #ENamedParameters to use when the @worker might be restarted
415 	 * @error: an optional #GError with an overall result of the run
416 	 *
417 	 * Emitted when the @worker finished its running.
418 	 *
419 	 * Note that this signal is always emitted in the main thread.
420 	 *
421 	 * Since: 3.28
422 	 **/
423 	signals[WORKER_FINISHED] = g_signal_new (
424 		"worker-finished",
425 		G_TYPE_FROM_CLASS (klass),
426 		G_SIGNAL_RUN_LAST,
427 		G_STRUCT_OFFSET (EConfigLookupClass, worker_finished),
428 		NULL, NULL,
429 		NULL,
430 		G_TYPE_NONE, 3,
431 		E_TYPE_CONFIG_LOOKUP_WORKER,
432 		E_TYPE_NAMED_PARAMETERS,
433 		G_TYPE_ERROR);
434 
435 	/**
436 	 * EConfigLookup::result-added:
437 	 * @result: an #EConfigLookupResult
438 	 *
439 	 * Emitted when a new @result is added to the config lookup.
440 	 *
441 	 * Note that this signal can be emitted in a worker's dedicated thread.
442 	 *
443 	 * Since: 3.28
444 	 **/
445 	signals[RESULT_ADDED] = g_signal_new (
446 		"result-added",
447 		G_TYPE_FROM_CLASS (klass),
448 		G_SIGNAL_RUN_LAST,
449 		G_STRUCT_OFFSET (EConfigLookupClass, result_added),
450 		NULL, NULL,
451 		NULL,
452 		G_TYPE_NONE, 1,
453 		E_TYPE_CONFIG_LOOKUP_RESULT);
454 }
455 
456 static void
e_config_lookup_init(EConfigLookup * config_lookup)457 e_config_lookup_init (EConfigLookup *config_lookup)
458 {
459 	config_lookup->priv = G_TYPE_INSTANCE_GET_PRIVATE (config_lookup, E_TYPE_CONFIG_LOOKUP, EConfigLookupPrivate);
460 
461 	g_mutex_init (&config_lookup->priv->property_lock);
462 	config_lookup->priv->pool = g_thread_pool_new (config_lookup_thread, config_lookup, 10, FALSE, NULL);
463 }
464 
465 /**
466  * e_config_lookup_new:
467  * @registry: an #ESourceRegistry
468  *
469  * Creates a new #EConfigLookup instance.
470  *
471  * Returns: (transfer full): a new #EConfigLookup
472  *
473  * Since: 3.26
474  **/
475 EConfigLookup *
e_config_lookup_new(ESourceRegistry * registry)476 e_config_lookup_new (ESourceRegistry *registry)
477 {
478 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
479 
480 	return g_object_new (E_TYPE_CONFIG_LOOKUP,
481 		"registry", registry,
482 		NULL);
483 }
484 
485 /**
486  * e_config_lookup_get_registry:
487  * @config_lookup: an #EConfigLookup
488  *
489  * Returns the #ESourceRegistry passed to e_config_lookup_new().
490  *
491  * Returns: (transfer none): an #ESourceRegistry
492  *
493  * Since: 3.26
494  **/
495 ESourceRegistry *
e_config_lookup_get_registry(EConfigLookup * config_lookup)496 e_config_lookup_get_registry (EConfigLookup *config_lookup)
497 {
498 	g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), NULL);
499 
500 	return config_lookup->priv->registry;
501 }
502 
503 /**
504  * e_config_lookup_get_source:
505  * @config_lookup: an #EConfigLookup
506  * @kind: one of #EConfigLookupSourceKind, except of the %E_CONFIG_LOOKUP_SOURCE_UNKNOWN
507  *
508  * Emits the #EConfigLookup::get-source signal and any listener can provide
509  * the source. The function can return %NULL, when there are no listeners
510  * or when such source is not available.
511  *
512  * Returns: (transfer none) (nullable): an #ESource of the given @kind, or %NULL, if not found
513  *
514  * Since: 3.26
515  **/
516 ESource *
e_config_lookup_get_source(EConfigLookup * config_lookup,EConfigLookupSourceKind kind)517 e_config_lookup_get_source (EConfigLookup *config_lookup,
518 			    EConfigLookupSourceKind kind)
519 {
520 	ESource *source = NULL;
521 
522 	g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), NULL);
523 
524 	g_signal_emit (config_lookup, signals[GET_SOURCE], 0, kind, &source);
525 
526 	return source;
527 }
528 
529 /**
530  * e_config_lookup_get_busy:
531  * @config_lookup: an #EConfigLookup
532  *
533  * Returns whether there's any running worker. They can be cancelled
534  * with e_config_lookup_cancel_all().
535  *
536  * Returns: whether there's any running worker
537  *
538  * Since: 3.28
539  **/
540 gboolean
e_config_lookup_get_busy(EConfigLookup * config_lookup)541 e_config_lookup_get_busy (EConfigLookup *config_lookup)
542 {
543 	gboolean busy;
544 
545 	g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), FALSE);
546 
547 	g_mutex_lock (&config_lookup->priv->property_lock);
548 	busy = config_lookup->priv->worker_cancellables != NULL;
549 	g_mutex_unlock (&config_lookup->priv->property_lock);
550 
551 	return busy;
552 }
553 
554 /**
555  * e_config_lookup_cancel_all:
556  * @config_lookup: an #EConfigLookup
557  *
558  * Cancels all pending workers.
559  *
560  * Since: 3.28
561  **/
562 void
e_config_lookup_cancel_all(EConfigLookup * config_lookup)563 e_config_lookup_cancel_all (EConfigLookup *config_lookup)
564 {
565 	GSList *cancellables;
566 	GCancellable *run_cancellable;
567 
568 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
569 
570 	g_mutex_lock (&config_lookup->priv->property_lock);
571 	cancellables = g_slist_copy_deep (config_lookup->priv->worker_cancellables, (GCopyFunc) g_object_ref, NULL);
572 	run_cancellable = config_lookup->priv->run_cancellable ? g_object_ref (config_lookup->priv->run_cancellable) : NULL;
573 	g_mutex_unlock (&config_lookup->priv->property_lock);
574 
575 	g_slist_foreach (cancellables, (GFunc) g_cancellable_cancel, NULL);
576 	g_slist_free_full (cancellables, g_object_unref);
577 
578 	if (run_cancellable) {
579 		g_cancellable_cancel (run_cancellable);
580 		g_object_unref (run_cancellable);
581 	}
582 }
583 
584 /**
585  * e_config_lookup_register_worker:
586  * @config_lookup: an #EConfigLookup
587  * @worker: an #EConfigLookupWorker
588  *
589  * Registers a @worker as a worker, which can be run as part of e_config_lookup_run().
590  * The function adds its own reference to @worker.
591  *
592  * Since: 3.28
593  **/
594 void
e_config_lookup_register_worker(EConfigLookup * config_lookup,EConfigLookupWorker * worker)595 e_config_lookup_register_worker (EConfigLookup *config_lookup,
596 				 EConfigLookupWorker *worker)
597 {
598 	GSList *existing_worker;
599 
600 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
601 	g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker));
602 
603 	g_mutex_lock (&config_lookup->priv->property_lock);
604 
605 	existing_worker = g_slist_find (config_lookup->priv->workers, worker);
606 
607 	g_warn_if_fail (existing_worker == NULL);
608 
609 	if (!existing_worker)
610 		config_lookup->priv->workers = g_slist_prepend (config_lookup->priv->workers, g_object_ref (worker));
611 
612 	g_mutex_unlock (&config_lookup->priv->property_lock);
613 }
614 
615 /**
616  * e_config_lookup_unregister_worker:
617  * @config_lookup: an #EConfigLookup
618  * @worker: an #EConfigLookupWorker
619  *
620  * Removes a @worker previously registered with e_config_lookup_register_worker().
621  *
622  * Since: 3.28
623  **/
624 void
e_config_lookup_unregister_worker(EConfigLookup * config_lookup,EConfigLookupWorker * worker)625 e_config_lookup_unregister_worker (EConfigLookup *config_lookup,
626 				   EConfigLookupWorker *worker)
627 {
628 	GSList *existing_worker;
629 
630 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
631 	g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker));
632 
633 	g_mutex_lock (&config_lookup->priv->property_lock);
634 
635 	existing_worker = g_slist_find (config_lookup->priv->workers, worker);
636 
637 	g_warn_if_fail (existing_worker != NULL);
638 
639 	if (existing_worker) {
640 		config_lookup->priv->workers = g_slist_remove (config_lookup->priv->workers, worker);
641 		g_object_unref (worker);
642 	}
643 
644 	g_mutex_unlock (&config_lookup->priv->property_lock);
645 }
646 
647 /**
648  * e_config_lookup_dup_registered_workers:
649  * @config_lookup: an #EConfigLookup
650  *
651  * Returns a list of all registered #EConfigLookupWorker objects.
652  *
653  * The returned #GSList should be freed with
654  * g_slist_free_full (workers, g_object_unref);
655  * when no longer needed.
656  *
657  * Returns: (transfer full) (element-type EConfigLookupWorker): a #GSList with all
658  *    workers registered with e_config_lookup_register_worker().
659  *
660  * Since: 3.28
661  **/
662 GSList *
e_config_lookup_dup_registered_workers(EConfigLookup * config_lookup)663 e_config_lookup_dup_registered_workers (EConfigLookup *config_lookup)
664 {
665 	GSList *workers;
666 
667 	g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), NULL);
668 
669 	g_mutex_lock (&config_lookup->priv->property_lock);
670 	workers = g_slist_copy_deep (config_lookup->priv->workers, (GCopyFunc) g_object_ref, NULL);
671 	g_mutex_unlock (&config_lookup->priv->property_lock);
672 
673 	return workers;
674 }
675 
676 /**
677  * e_config_lookup_run:
678  * @config_lookup: an #EConfigLookup
679  * @params: an #ENamedParameters with lookup parameters
680  * @cancellable: an optional #GCancellable, or %NULL
681  * @callback: a callback to call, when the run is finished
682  * @user_data: user data for the @callback
683  *
684  * Runs configuration lookup asynchronously. Once the run is done, the @callback is called,
685  * and the call can be finished with e_config_lookup_run_finish(). The @callback is always
686  * called from the main thread.
687  *
688  * Workers can be run individually using e_config_lookup_run_worker().
689  *
690  * Note that there cannot be run two lookups at the same time, thus if it
691  * happens, then the @callback is called immediately with a %NULL result.
692  *
693  * Since: 3.26
694  **/
695 void
e_config_lookup_run(EConfigLookup * config_lookup,const ENamedParameters * params,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)696 e_config_lookup_run (EConfigLookup *config_lookup,
697 		     const ENamedParameters *params,
698 		     GCancellable *cancellable,
699 		     GAsyncReadyCallback callback,
700 		     gpointer user_data)
701 {
702 	GSList *workers, *link;
703 
704 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
705 	g_return_if_fail (params != NULL);
706 
707 	g_mutex_lock (&config_lookup->priv->property_lock);
708 
709 	if (config_lookup->priv->run_result) {
710 		g_mutex_unlock (&config_lookup->priv->property_lock);
711 
712 		if (callback)
713 			callback (G_OBJECT (config_lookup), NULL, user_data);
714 		return;
715 	}
716 
717 	g_slist_free_full (config_lookup->priv->results, g_object_unref);
718 	config_lookup->priv->results = NULL;
719 
720 	if (cancellable)
721 		g_object_ref (cancellable);
722 	else
723 		cancellable = g_cancellable_new ();
724 
725 	config_lookup->priv->run_result = e_simple_async_result_new (G_OBJECT (config_lookup), callback, user_data, e_config_lookup_run);
726 	config_lookup->priv->run_cancellable = cancellable;
727 
728 	workers = g_slist_copy_deep (config_lookup->priv->workers, (GCopyFunc) g_object_ref, NULL);
729 
730 	g_mutex_unlock (&config_lookup->priv->property_lock);
731 
732 	if (workers) {
733 		for (link = workers; link; link = g_slist_next (link)) {
734 			EConfigLookupWorker *worker = link->data;
735 
736 			e_config_lookup_run_worker (config_lookup, worker, params, cancellable);
737 		}
738 
739 		g_slist_free_full (workers, g_object_unref);
740 	} else {
741 		ESimpleAsyncResult *run_result;
742 
743 		g_mutex_lock (&config_lookup->priv->property_lock);
744 
745 		run_result = config_lookup->priv->run_result;
746 		config_lookup->priv->run_result = NULL;
747 
748 		g_clear_object (&config_lookup->priv->run_cancellable);
749 
750 		g_mutex_unlock (&config_lookup->priv->property_lock);
751 
752 		if (run_result) {
753 			e_simple_async_result_complete_idle_take (run_result);
754 		}
755 	}
756 }
757 
758 /**
759  * e_config_lookup_run_finish:
760  * @config_lookup: an #EConfigLookup
761  * @result: result of the operation
762  *
763  * Finishes the configuration lookup previously run by e_config_lookup_run().
764  * It's expected that the extensions may fail, thus it doesn't return
765  * anything and is provided mainly for consistency with asynchronous API.
766  *
767  * Since: 3.26
768  **/
769 void
e_config_lookup_run_finish(EConfigLookup * config_lookup,GAsyncResult * result)770 e_config_lookup_run_finish (EConfigLookup *config_lookup,
771 			    GAsyncResult *result)
772 {
773 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
774 	g_return_if_fail (G_IS_ASYNC_RESULT (result));
775 	g_return_if_fail (g_async_result_is_tagged (result, e_config_lookup_run));
776 }
777 
778 /**
779  * e_config_lookup_run_worker:
780  * @config_lookup: an #EConfigLookup
781  * @worker: an #EConfigLookupWorker to run in a dedicated thread
782  * @params: an #ENamedParameters with lookup parameters
783  * @cancellable: an optional #GCancellable, or %NULL
784  *
785  * Creates a new thread and runs @worker in it. When the @cancellable is %NULL,
786  * then there's creates a new #CamelOperation, which either proxies currently
787  * running lookup or the newly created cancellable is completely independent.
788  *
789  * This function can be called while there's an ongoing configuration lookup, but
790  * also when the @worker is restarted.
791  *
792  * Since: 3.28
793  **/
794 void
e_config_lookup_run_worker(EConfigLookup * config_lookup,EConfigLookupWorker * worker,const ENamedParameters * params,GCancellable * cancellable)795 e_config_lookup_run_worker (EConfigLookup *config_lookup,
796 			    EConfigLookupWorker *worker,
797 			    const ENamedParameters *params,
798 			    GCancellable *cancellable)
799 {
800 	ThreadData *td;
801 
802 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
803 	g_return_if_fail (E_IS_CONFIG_LOOKUP_WORKER (worker));
804 	g_return_if_fail (params != NULL);
805 
806 	td = g_slice_new0 (ThreadData);
807 	td->params = e_named_parameters_new_clone (params);
808 	td->worker = g_object_ref (worker);
809 
810 	g_mutex_lock (&config_lookup->priv->property_lock);
811 
812 	if (cancellable)
813 		td->cancellable = camel_operation_new_proxy (cancellable);
814 	else if (config_lookup->priv->run_cancellable)
815 		td->cancellable = camel_operation_new_proxy (config_lookup->priv->run_cancellable);
816 	else
817 		td->cancellable = camel_operation_new ();
818 
819 	camel_operation_push_message (td->cancellable, "%s", _("Running…"));
820 	config_lookup->priv->worker_cancellables = g_slist_prepend (config_lookup->priv->worker_cancellables, g_object_ref (td->cancellable));
821 
822 	config_lookup_schedule_emit_idle (config_lookup, EMIT_WORKER_STARTED |
823 		(!config_lookup->priv->worker_cancellables->next ? EMIT_BUSY : 0),
824 		worker, td->cancellable, NULL, NULL);
825 
826 	g_thread_pool_push (config_lookup->priv->pool, td, NULL);
827 
828 	g_mutex_unlock (&config_lookup->priv->property_lock);
829 }
830 
831 /**
832  * e_config_lookup_add_result:
833  * @config_lookup: an #EConfigLookup
834  * @result: (transfer full): an #EConfigLookupResult
835  *
836  * Adds a new @result in a list of known configuration lookup results.
837  * The @config_lookup assumes ownership of the @result and frees it
838  * when no longer needed.
839  *
840  * The list of results can be obtained with e_config_lookup_dup_results().
841  *
842  * Since: 3.26
843  **/
844 void
e_config_lookup_add_result(EConfigLookup * config_lookup,EConfigLookupResult * result)845 e_config_lookup_add_result (EConfigLookup *config_lookup,
846 			    EConfigLookupResult *result)
847 {
848 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
849 	g_return_if_fail (E_IS_CONFIG_LOOKUP_RESULT (result));
850 
851 	g_mutex_lock (&config_lookup->priv->property_lock);
852 
853 	config_lookup->priv->results = g_slist_prepend (config_lookup->priv->results, result);
854 
855 	g_mutex_unlock (&config_lookup->priv->property_lock);
856 
857 	g_signal_emit (config_lookup, signals[RESULT_ADDED], 0, result);
858 }
859 
860 /**
861  * e_config_lookup_count_results:
862  * @config_lookup: an #EConfigLookup
863  *
864  * Returns: how many results had been added already.
865  *
866  * Since: 3.28
867  **/
868 gint
e_config_lookup_count_results(EConfigLookup * config_lookup)869 e_config_lookup_count_results (EConfigLookup *config_lookup)
870 {
871 	gint n_results;
872 
873 	g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), -1);
874 
875 	g_mutex_lock (&config_lookup->priv->property_lock);
876 
877 	n_results = g_slist_length (config_lookup->priv->results);
878 
879 	g_mutex_unlock (&config_lookup->priv->property_lock);
880 
881 	return n_results;
882 }
883 
884 /**
885  * e_config_lookup_dup_results:
886  * @config_lookup: an #EConfigLookup
887  * @kind: an #EConfigLookupResultKind to filter the results with
888  * @protocol: (nullable): optional protocol to filter the results with, or %NULL
889  *
890  * Returns a #GSList with #EConfigLookupResult objects satisfying
891  * the @kind and @protocol filtering conditions. To receive all
892  * gathered results use %E_CONFIG_LOOKUP_RESULT_UNKNOWN for @kind
893  * and %NULL for the @protocol.
894  *
895  * Free the returned #GSList with
896  * g_slist_free_full (results, g_object_unref);
897  * when no longer needed.
898  *
899  * Returns: (transfer full) (element-type EConfigLookupResult): a #GSList
900  *    with results satisfying the @kind and @protocol filtering conditions.
901  *
902  * Since: 3.28
903  **/
904 GSList *
e_config_lookup_dup_results(EConfigLookup * config_lookup,EConfigLookupResultKind kind,const gchar * protocol)905 e_config_lookup_dup_results (EConfigLookup *config_lookup,
906 			     EConfigLookupResultKind kind,
907 			     const gchar *protocol)
908 {
909 	GSList *results = NULL, *link;
910 
911 	g_return_val_if_fail (E_IS_CONFIG_LOOKUP (config_lookup), NULL);
912 
913 	g_mutex_lock (&config_lookup->priv->property_lock);
914 
915 	for (link = config_lookup->priv->results; link; link = g_slist_next (link)) {
916 		EConfigLookupResult *result = link->data;
917 
918 		if (!E_IS_CONFIG_LOOKUP_RESULT (result))
919 			continue;
920 
921 		if (kind != E_CONFIG_LOOKUP_RESULT_UNKNOWN &&
922 		    kind != e_config_lookup_result_get_kind (result))
923 			continue;
924 
925 		if (protocol &&
926 		    g_strcmp0 (protocol, e_config_lookup_result_get_protocol (result)) != 0)
927 			continue;
928 
929 		results = g_slist_prepend (results, g_object_ref (result));
930 	}
931 
932 	g_mutex_unlock (&config_lookup->priv->property_lock);
933 
934 	return results;
935 }
936 
937 /**
938  * e_config_lookup_clear_results:
939  * @config_lookup: an #EConfigLookup
940  *
941  * Frees all gathered results. This might be usually called before
942  * starting new custom lookup. The e_config_lookup_run() frees
943  * all results automatically.
944  *
945  * Since: 3.28
946  **/
947 void
e_config_lookup_clear_results(EConfigLookup * config_lookup)948 e_config_lookup_clear_results (EConfigLookup *config_lookup)
949 {
950 	g_return_if_fail (E_IS_CONFIG_LOOKUP (config_lookup));
951 
952 	g_mutex_lock (&config_lookup->priv->property_lock);
953 
954 	g_slist_free_full (config_lookup->priv->results, g_object_unref);
955 	config_lookup->priv->results = NULL;
956 
957 	g_mutex_unlock (&config_lookup->priv->property_lock);
958 }
959 
960 /**
961  * e_config_lookup_encode_certificate_trust:
962  * @response: an #ETrustPromptResponse to encode
963  *
964  * Encodes @response to a string. This can be decoded back to enum
965  * with e_config_lookup_decode_certificate_trust().
966  *
967  * Returns: string representation of @response.
968  *
969  * Since: 3.28
970  **/
971 const gchar *
e_config_lookup_encode_certificate_trust(ETrustPromptResponse response)972 e_config_lookup_encode_certificate_trust (ETrustPromptResponse response)
973 {
974 	return e_enum_to_string (E_TYPE_TRUST_PROMPT_RESPONSE, response);
975 }
976 
977 /**
978  * e_config_lookup_decode_certificate_trust:
979  * @value: a text value to decode
980  *
981  * Decodes text @value to #ETrustPromptResponse, previously encoded
982  * with e_config_lookup_encode_certificate_trust().
983  *
984  * Returns: an #ETrustPromptResponse corresponding to @value.
985  *
986  * Since: 3.28
987  **/
988 ETrustPromptResponse
e_config_lookup_decode_certificate_trust(const gchar * value)989 e_config_lookup_decode_certificate_trust (const gchar *value)
990 {
991 	gint decoded;
992 
993 	if (!value ||
994 	    !e_enum_from_string (E_TYPE_TRUST_PROMPT_RESPONSE, value, &decoded))
995 		decoded = E_TRUST_PROMPT_RESPONSE_UNKNOWN;
996 
997 	return decoded;
998 }
999