updated changelog
[platform/upstream/evolution-data-server.git] / camel / camel-service.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-service.c : Abstract class for an email service */
3
4 /*
5  *
6  * Author :
7  *  Bertrand Guiheneuf <bertrand@helixcode.com>
8  *
9  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
10  *
11  * This library is free software you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation.
14  *
15  * This library is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
18  *for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, see <http://www.gnu.org/licenses/>.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <ctype.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
35
36 #include "camel-async-closure.h"
37 #include "camel-debug.h"
38 #include "camel-enumtypes.h"
39 #include "camel-local-settings.h"
40 #include "camel-network-service.h"
41 #include "camel-network-settings.h"
42 #include "camel-operation.h"
43 #include "camel-service.h"
44 #include "camel-session.h"
45
46 #define d(x)
47 #define w(x)
48
49 #define CAMEL_SERVICE_GET_PRIVATE(obj) \
50         (G_TYPE_INSTANCE_GET_PRIVATE \
51         ((obj), CAMEL_TYPE_SERVICE, CamelServicePrivate))
52
53 #define DISPATCH_DATA_KEY "camel-service-dispatch-data"
54
55 typedef struct _AsyncContext AsyncContext;
56 typedef struct _ConnectionOp ConnectionOp;
57 typedef struct _DispatchData DispatchData;
58
59 struct _CamelServicePrivate {
60         GWeakRef session;
61
62         GMutex property_lock;
63         CamelSettings *settings;
64         GProxyResolver *proxy_resolver;
65
66         CamelProvider *provider;
67
68         gchar *display_name;
69         gchar *user_data_dir;
70         gchar *user_cache_dir;
71         gchar *uid;
72         gchar *password;
73
74         GMutex connection_lock;
75         ConnectionOp *connection_op;
76         CamelServiceConnectionStatus status;
77
78         /* Queues of GTasks, by source object. */
79         GHashTable *task_table;
80         GMutex task_table_lock;
81
82         gboolean network_service_inited;
83 };
84
85 struct _AsyncContext {
86         gchar *auth_mechanism;
87         gboolean clean;
88 };
89
90 /* The GQueue is only modified while CamelService's
91  * connection_lock is held, so it does not need its
92  * own mutex. */
93 struct _ConnectionOp {
94         volatile gint ref_count;
95         GQueue pending;
96         GMutex task_lock;
97         GTask *task;
98         GCancellable *cancellable;
99         gulong cancel_id;
100 };
101
102 struct _DispatchData {
103         GWeakRef service;
104         gboolean return_on_cancel;
105         GTaskThreadFunc task_func;
106 };
107
108 enum {
109         PROP_0,
110         PROP_CONNECTION_STATUS,
111         PROP_DISPLAY_NAME,
112         PROP_PASSWORD,
113         PROP_PROVIDER,
114         PROP_PROXY_RESOLVER,
115         PROP_SESSION,
116         PROP_SETTINGS,
117         PROP_UID
118 };
119
120 /* Forward Declarations */
121 void            camel_network_service_init      (CamelNetworkService *service);
122 static void     camel_service_initable_init     (GInitableIface *iface);
123 static void     service_task_dispatch           (CamelService *service,
124                                                  GTask *task);
125
126 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
127         CamelService, camel_service, CAMEL_TYPE_OBJECT,
128         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, camel_service_initable_init))
129
130 static void
131 async_context_free (AsyncContext *async_context)
132 {
133         g_free (async_context->auth_mechanism);
134
135         g_slice_free (AsyncContext, async_context);
136 }
137
138 static ConnectionOp *
139 connection_op_new (GTask *task,
140                    GCancellable *cancellable)
141 {
142         ConnectionOp *op;
143
144         op = g_slice_new0 (ConnectionOp);
145         op->ref_count = 1;
146         g_mutex_init (&op->task_lock);
147         op->task = g_object_ref (task);
148
149         if (G_IS_CANCELLABLE (cancellable))
150                 op->cancellable = g_object_ref (cancellable);
151
152         return op;
153 }
154
155 static ConnectionOp *
156 connection_op_ref (ConnectionOp *op)
157 {
158         g_return_val_if_fail (op != NULL, NULL);
159         g_return_val_if_fail (op->ref_count > 0, NULL);
160
161         g_atomic_int_inc (&op->ref_count);
162
163         return op;
164 }
165
166 static void
167 connection_op_unref (ConnectionOp *op)
168 {
169         g_return_if_fail (op != NULL);
170         g_return_if_fail (op->ref_count > 0);
171
172         if (g_atomic_int_dec_and_test (&op->ref_count)) {
173
174                 /* The pending queue should be empty. */
175                 g_warn_if_fail (g_queue_is_empty (&op->pending));
176
177                 g_mutex_clear (&op->task_lock);
178
179                 if (op->task != NULL)
180                         g_object_unref (op->task);
181
182                 if (op->cancel_id > 0)
183                         g_cancellable_disconnect (
184                                 op->cancellable, op->cancel_id);
185
186                 if (op->cancellable != NULL)
187                         g_object_unref (op->cancellable);
188
189                 g_slice_free (ConnectionOp, op);
190         }
191 }
192
193 static void
194 connection_op_complete (ConnectionOp *op,
195                         const GError *error)
196 {
197         g_mutex_lock (&op->task_lock);
198
199         if (op->task != NULL) {
200                 if (error != NULL) {
201                         g_task_return_error (op->task, g_error_copy (error));
202                 } else {
203                         g_task_return_boolean (op->task, TRUE);
204                 }
205
206                 g_clear_object (&op->task);
207         }
208
209         g_mutex_unlock (&op->task_lock);
210 }
211
212 static void
213 connection_op_cancelled (GCancellable *cancellable,
214                          ConnectionOp *op)
215 {
216         /* A cancelled GTask will always propagate an error, so we
217          * don't need to explicitly set a G_IO_ERROR_CANCELLED here. */
218
219         g_mutex_lock (&op->task_lock);
220
221         g_clear_object (&op->task);
222
223         g_mutex_unlock (&op->task_lock);
224 }
225
226 static void
227 connection_op_add_pending (ConnectionOp *op,
228                            GTask *task,
229                            GCancellable *cancellable)
230 {
231         ConnectionOp *pending_op;
232
233         g_return_if_fail (op != NULL);
234
235         pending_op = connection_op_new (task, cancellable);
236
237         if (pending_op->cancellable != NULL)
238                 pending_op->cancel_id = g_cancellable_connect (
239                         pending_op->cancellable,
240                         G_CALLBACK (connection_op_cancelled),
241                         pending_op, (GDestroyNotify) NULL);
242
243         g_queue_push_tail (&op->pending, pending_op);
244 }
245
246 static void
247 connection_op_complete_pending (ConnectionOp *op,
248                                 const GError *error)
249 {
250         ConnectionOp *pending_op;
251
252         g_return_if_fail (op != NULL);
253
254         while (!g_queue_is_empty (&op->pending)) {
255                 pending_op = g_queue_pop_head (&op->pending);
256                 connection_op_complete (pending_op, error);
257                 connection_op_unref (pending_op);
258         }
259 }
260
261 static void
262 dispatch_data_free (DispatchData *dispatch_data)
263 {
264         g_weak_ref_set (&dispatch_data->service, NULL);
265
266         g_slice_free (DispatchData, dispatch_data);
267 }
268
269 static void
270 task_queue_free (GQueue *task_queue)
271 {
272         g_queue_free_full (task_queue, g_object_unref);
273 }
274
275 static void
276 service_task_table_push (CamelService *service,
277                          GTask *task)
278 {
279         GQueue *task_queue;
280         gpointer source_object;
281         gboolean queue_was_empty;
282
283         g_return_if_fail (CAMEL_IS_SERVICE (service));
284         g_return_if_fail (G_IS_TASK (task));
285
286         source_object = g_task_get_source_object (task);
287         if (source_object == NULL)
288                 source_object = service;
289
290         g_mutex_lock (&service->priv->task_table_lock);
291
292         task_queue = g_hash_table_lookup (
293                 service->priv->task_table, source_object);
294
295         /* Create on demand. */
296         if (task_queue == NULL) {
297                 task_queue = g_queue_new ();
298                 g_hash_table_insert (
299                         service->priv->task_table,
300                         source_object, task_queue);
301         }
302
303         queue_was_empty = g_queue_is_empty (task_queue);
304         g_queue_push_tail (task_queue, g_object_ref (task));
305
306         g_mutex_unlock (&service->priv->task_table_lock);
307
308         if (queue_was_empty)
309                 service_task_dispatch (service, task);
310 }
311
312 static void
313 service_task_table_done (CamelService *service,
314                          GTask *task)
315 {
316         GQueue *task_queue;
317         gpointer source_object;
318         GTask *next = NULL;
319
320         g_return_if_fail (CAMEL_IS_SERVICE (service));
321         g_return_if_fail (G_IS_TASK (task));
322
323         source_object = g_task_get_source_object (task);
324         if (source_object == NULL)
325                 source_object = service;
326
327         g_mutex_lock (&service->priv->task_table_lock);
328
329         task_queue = g_hash_table_lookup (
330                 service->priv->task_table, source_object);
331
332         if (task_queue != NULL) {
333                 if (g_queue_remove (task_queue, task))
334                         g_object_unref (task);
335
336                 next = g_queue_peek_head (task_queue);
337                 if (next != NULL)
338                         g_object_ref (next);
339         }
340
341         g_mutex_unlock (&service->priv->task_table_lock);
342
343         if (next != NULL) {
344                 service_task_dispatch (service, next);
345                 g_object_unref (next);
346         }
347 }
348
349 static void
350 service_task_thread (GTask *task,
351                      gpointer source_object,
352                      gpointer task_data,
353                      GCancellable *cancellable)
354 {
355         CamelService *service;
356         DispatchData *data;
357
358         data = g_object_get_data (G_OBJECT (task), DISPATCH_DATA_KEY);
359         g_return_if_fail (data != NULL);
360
361         service = g_weak_ref_get (&data->service);
362         g_return_if_fail (service != NULL);
363
364         data->task_func (task, source_object, task_data, cancellable);
365
366         service_task_table_done (service, task);
367
368         g_object_unref (service);
369 }
370
371 static void
372 service_task_dispatch (CamelService *service,
373                        GTask *task)
374 {
375         DispatchData *data;
376
377         data = g_object_get_data (G_OBJECT (task), DISPATCH_DATA_KEY);
378         g_return_if_fail (data != NULL);
379
380         /* Restore the task's previous "return-on-cancel" flag.
381          * This returns FALSE if the task is already cancelled,
382          * in which case we skip calling g_task_run_in_thread()
383          * so the task doesn't complete twice. */
384         if (g_task_set_return_on_cancel (task, data->return_on_cancel))
385                 g_task_run_in_thread (task, service_task_thread);
386         else
387                 service_task_table_done (service, task);
388 }
389
390 static gchar *
391 service_find_old_data_dir (CamelService *service)
392 {
393         CamelProvider *provider;
394         CamelSession *session;
395         CamelURL *url;
396         GString *path;
397         gboolean allows_host;
398         gboolean allows_user;
399         gboolean needs_host;
400         gboolean needs_path;
401         gboolean needs_user;
402         const gchar *base_dir;
403         gchar *old_data_dir;
404
405         provider = camel_service_get_provider (service);
406         url = camel_service_new_camel_url (service);
407
408         allows_host = CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_HOST);
409         allows_user = CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_USER);
410
411         needs_host = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST);
412         needs_path = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH);
413         needs_user = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER);
414
415         /* This function reproduces the way service data directories used
416          * to be determined before we moved to just using the UID.  If the
417          * old data directory exists, try renaming it to the new form.
418          *
419          * A virtual class method was used to determine the directory path,
420          * but no known CamelProviders ever overrode the default algorithm
421          * below.  So this should work for everyone. */
422
423         path = g_string_new (provider->protocol);
424
425         if (allows_user) {
426                 g_string_append_c (path, '/');
427                 if (url->user != NULL)
428                         g_string_append (path, url->user);
429                 if (allows_host) {
430                         g_string_append_c (path, '@');
431                         if (url->host != NULL)
432                                 g_string_append (path, url->host);
433                         if (url->port) {
434                                 g_string_append_c (path, ':');
435                                 g_string_append_printf (path, "%d", url->port);
436                         }
437                 } else if (!needs_user) {
438                         g_string_append_c (path, '@');
439                 }
440
441         } else if (allows_host) {
442                 g_string_append_c (path, '/');
443                 if (!needs_host)
444                         g_string_append_c (path, '@');
445                 if (url->host != NULL)
446                         g_string_append (path, url->host);
447                 if (url->port) {
448                         g_string_append_c (path, ':');
449                         g_string_append_printf (path, "%d", url->port);
450                 }
451         }
452
453         if (needs_path && url->path) {
454                 if (*url->path != '/')
455                         g_string_append_c (path, '/');
456                 g_string_append (path, url->path);
457         }
458
459         session = camel_service_ref_session (service);
460
461         base_dir = camel_session_get_user_data_dir (session);
462         old_data_dir = g_build_filename (base_dir, path->str, NULL);
463
464         g_object_unref (session);
465
466         g_string_free (path, TRUE);
467
468         if (!g_file_test (old_data_dir, G_FILE_TEST_IS_DIR)) {
469                 g_free (old_data_dir);
470                 old_data_dir = NULL;
471         }
472
473         camel_url_free (url);
474
475         return old_data_dir;
476 }
477
478 static gboolean
479 service_notify_connection_status_cb (gpointer user_data)
480 {
481         CamelService *service = CAMEL_SERVICE (user_data);
482
483         g_object_notify (G_OBJECT (service), "connection-status");
484
485         return FALSE;
486 }
487
488 static void
489 service_queue_notify_connection_status (CamelService *service)
490 {
491         CamelSession *session;
492
493         session = camel_service_ref_session (service);
494
495         /* most-likely exitting the application */
496         if (!session)
497                 return;
498
499         /* Prioritize ahead of GTK+ redraws. */
500         camel_session_idle_add (
501                 session, G_PRIORITY_HIGH_IDLE,
502                 service_notify_connection_status_cb,
503                 g_object_ref (service),
504                 (GDestroyNotify) g_object_unref);
505
506         g_object_unref (session);
507 }
508
509 static void
510 service_shared_connect_cb (GObject *source_object,
511                            GAsyncResult *result,
512                            gpointer user_data)
513 {
514         CamelService *service;
515         ConnectionOp *op = user_data;
516         gboolean success;
517         GError *local_error = NULL;
518
519         service = CAMEL_SERVICE (source_object);
520         success = g_task_propagate_boolean (G_TASK (result), &local_error);
521
522         g_mutex_lock (&service->priv->connection_lock);
523
524         if (service->priv->connection_op == op) {
525                 connection_op_unref (service->priv->connection_op);
526                 service->priv->connection_op = NULL;
527                 if (success)
528                         service->priv->status = CAMEL_SERVICE_CONNECTED;
529                 else
530                         service->priv->status = CAMEL_SERVICE_DISCONNECTED;
531                 service_queue_notify_connection_status (service);
532         }
533
534         connection_op_complete (op, local_error);
535         connection_op_complete_pending (op, local_error);
536
537         g_mutex_unlock (&service->priv->connection_lock);
538
539         connection_op_unref (op);
540         g_clear_error (&local_error);
541 }
542
543 static void
544 service_shared_connect_thread (GTask *task,
545                                gpointer source_object,
546                                gpointer task_data,
547                                GCancellable *cancellable)
548 {
549         CamelServiceClass *class;
550         gboolean success;
551         GError *local_error = NULL;
552
553         /* Note we call the class method directly here. */
554
555         class = CAMEL_SERVICE_GET_CLASS (source_object);
556         g_return_if_fail (class->connect_sync != NULL);
557
558         success = class->connect_sync (
559                 CAMEL_SERVICE (source_object),
560                 cancellable, &local_error);
561
562         if (local_error != NULL) {
563                 g_task_return_error (task, local_error);
564         } else {
565                 g_task_return_boolean (task, success);
566         }
567 }
568
569 static void
570 service_shared_connect (CamelService *service,
571                         gint io_priority,
572                         ConnectionOp *op)
573 {
574         GTask *task;
575
576         task = g_task_new (
577                 service, op->cancellable,
578                 service_shared_connect_cb,
579                 connection_op_ref (op));
580
581         g_task_set_source_tag (task, service_shared_connect);
582         g_task_set_priority (task, io_priority);
583
584         g_task_run_in_thread (task, service_shared_connect_thread);
585
586         g_object_unref (task);
587 }
588
589 static void
590 service_shared_disconnect_cb (GObject *source_object,
591                               GAsyncResult *result,
592                               gpointer user_data)
593 {
594         CamelService *service;
595         ConnectionOp *op = user_data;
596         gboolean success;
597         GError *local_error = NULL;
598
599         service = CAMEL_SERVICE (source_object);
600         success = g_task_propagate_boolean (G_TASK (result), &local_error);
601
602         g_mutex_lock (&service->priv->connection_lock);
603
604         if (service->priv->connection_op == op) {
605                 connection_op_unref (service->priv->connection_op);
606                 service->priv->connection_op = NULL;
607                 if (success || service->priv->status == CAMEL_SERVICE_CONNECTING)
608                         service->priv->status = CAMEL_SERVICE_DISCONNECTED;
609                 else
610                         service->priv->status = CAMEL_SERVICE_CONNECTED;
611                 service_queue_notify_connection_status (service);
612         }
613
614         connection_op_complete (op, local_error);
615         connection_op_complete_pending (op, local_error);
616
617         g_mutex_unlock (&service->priv->connection_lock);
618
619         connection_op_unref (op);
620         g_clear_error (&local_error);
621 }
622
623 static void
624 service_shared_disconnect_thread (GTask *task,
625                                   gpointer source_object,
626                                   gpointer task_data,
627                                   GCancellable *cancellable)
628 {
629         CamelServiceClass *class;
630         AsyncContext *async_context;
631         gboolean success;
632         GError *local_error = NULL;
633
634         /* Note we call the class method directly here. */
635
636         async_context = (AsyncContext *) task_data;
637
638         class = CAMEL_SERVICE_GET_CLASS (source_object);
639         g_return_if_fail (class->disconnect_sync != NULL);
640
641         success = class->disconnect_sync (
642                 CAMEL_SERVICE (source_object),
643                 async_context->clean,
644                 cancellable, &local_error);
645
646         if (local_error != NULL) {
647                 g_task_return_error (task, local_error);
648         } else {
649                 g_task_return_boolean (task, success);
650         }
651 }
652
653 static void
654 service_shared_disconnect (CamelService *service,
655                            gboolean clean,
656                            gint io_priority,
657                            ConnectionOp *op)
658 {
659         GTask *task;
660         AsyncContext *async_context;
661
662         async_context = g_slice_new0 (AsyncContext);
663         async_context->clean = clean;
664
665         task = g_task_new (
666                 service, op->cancellable,
667                 service_shared_disconnect_cb,
668                 connection_op_ref (op));
669
670         g_task_set_source_tag (task, service_shared_disconnect);
671         g_task_set_priority (task, io_priority);
672
673         g_task_set_task_data (
674                 task, async_context,
675                 (GDestroyNotify) async_context_free);
676
677         g_task_run_in_thread (task, service_shared_disconnect_thread);
678
679         g_object_unref (task);
680 }
681
682 static void
683 service_set_provider (CamelService *service,
684                       CamelProvider *provider)
685 {
686         g_return_if_fail (provider != NULL);
687         g_return_if_fail (service->priv->provider == NULL);
688
689         service->priv->provider = provider;
690 }
691
692 static void
693 service_set_session (CamelService *service,
694                      CamelSession *session)
695 {
696         g_return_if_fail (CAMEL_IS_SESSION (session));
697
698         g_weak_ref_set (&service->priv->session, session);
699 }
700
701 static void
702 service_set_uid (CamelService *service,
703                  const gchar *uid)
704 {
705         g_return_if_fail (uid != NULL);
706         g_return_if_fail (service->priv->uid == NULL);
707
708         service->priv->uid = g_strdup (uid);
709 }
710
711 static void
712 service_set_property (GObject *object,
713                       guint property_id,
714                       const GValue *value,
715                       GParamSpec *pspec)
716 {
717         switch (property_id) {
718                 case PROP_DISPLAY_NAME:
719                         camel_service_set_display_name (
720                                 CAMEL_SERVICE (object),
721                                 g_value_get_string (value));
722                         return;
723
724                 case PROP_PASSWORD:
725                         camel_service_set_password (
726                                 CAMEL_SERVICE (object),
727                                 g_value_get_string (value));
728                         return;
729
730                 case PROP_PROVIDER:
731                         service_set_provider (
732                                 CAMEL_SERVICE (object),
733                                 g_value_get_pointer (value));
734                         return;
735
736                 case PROP_PROXY_RESOLVER:
737                         camel_service_set_proxy_resolver (
738                                 CAMEL_SERVICE (object),
739                                 g_value_get_object (value));
740                         return;
741
742                 case PROP_SESSION:
743                         service_set_session (
744                                 CAMEL_SERVICE (object),
745                                 g_value_get_object (value));
746                         return;
747
748                 case PROP_SETTINGS:
749                         camel_service_set_settings (
750                                 CAMEL_SERVICE (object),
751                                 g_value_get_object (value));
752                         return;
753
754                 case PROP_UID:
755                         service_set_uid (
756                                 CAMEL_SERVICE (object),
757                                 g_value_get_string (value));
758                         return;
759         }
760
761         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
762 }
763
764 static void
765 service_get_property (GObject *object,
766                       guint property_id,
767                       GValue *value,
768                       GParamSpec *pspec)
769 {
770         switch (property_id) {
771                 case PROP_CONNECTION_STATUS:
772                         g_value_set_enum (
773                                 value,
774                                 camel_service_get_connection_status (
775                                 CAMEL_SERVICE (object)));
776                         return;
777
778                 case PROP_DISPLAY_NAME:
779                         g_value_take_string (
780                                 value,
781                                 camel_service_dup_display_name (
782                                 CAMEL_SERVICE (object)));
783                         return;
784
785                 case PROP_PASSWORD:
786                         g_value_take_string (
787                                 value,
788                                 camel_service_dup_password (
789                                 CAMEL_SERVICE (object)));
790                         return;
791
792                 case PROP_PROVIDER:
793                         g_value_set_pointer (
794                                 value,
795                                 camel_service_get_provider (
796                                 CAMEL_SERVICE (object)));
797                         return;
798
799                 case PROP_PROXY_RESOLVER:
800                         g_value_take_object (
801                                 value,
802                                 camel_service_ref_proxy_resolver (
803                                 CAMEL_SERVICE (object)));
804                         return;
805
806                 case PROP_SESSION:
807                         g_value_take_object (
808                                 value,
809                                 camel_service_ref_session (
810                                 CAMEL_SERVICE (object)));
811                         return;
812
813                 case PROP_SETTINGS:
814                         g_value_take_object (
815                                 value,
816                                 camel_service_ref_settings (
817                                 CAMEL_SERVICE (object)));
818                         return;
819
820                 case PROP_UID:
821                         g_value_set_string (
822                                 value,
823                                 camel_service_get_uid (
824                                 CAMEL_SERVICE (object)));
825                         return;
826         }
827
828         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
829 }
830
831 static void
832 service_dispose (GObject *object)
833 {
834         CamelServicePrivate *priv;
835
836         priv = CAMEL_SERVICE_GET_PRIVATE (object);
837
838         if (priv->status == CAMEL_SERVICE_CONNECTED)
839                 CAMEL_SERVICE_GET_CLASS (object)->disconnect_sync (
840                         CAMEL_SERVICE (object), TRUE, NULL, NULL);
841
842         g_weak_ref_set (&priv->session, NULL);
843
844         g_clear_object (&priv->settings);
845         g_clear_object (&priv->proxy_resolver);
846
847         g_hash_table_remove_all (priv->task_table);
848
849         /* Chain up to parent's dispose() method. */
850         G_OBJECT_CLASS (camel_service_parent_class)->dispose (object);
851 }
852
853 static void
854 service_finalize (GObject *object)
855 {
856         CamelServicePrivate *priv;
857
858         priv = CAMEL_SERVICE_GET_PRIVATE (object);
859
860         g_mutex_clear (&priv->property_lock);
861
862         g_free (priv->display_name);
863         g_free (priv->user_data_dir);
864         g_free (priv->user_cache_dir);
865         g_free (priv->uid);
866         g_free (priv->password);
867
868         /* There should be no outstanding connection operations. */
869         g_warn_if_fail (priv->connection_op == NULL);
870         g_mutex_clear (&priv->connection_lock);
871
872         g_hash_table_destroy (priv->task_table);
873         g_mutex_clear (&priv->task_table_lock);
874
875         /* Chain up to parent's finalize() method. */
876         G_OBJECT_CLASS (camel_service_parent_class)->finalize (object);
877 }
878
879 static void
880 service_constructed (GObject *object)
881 {
882         CamelService *service;
883         CamelSession *session;
884         const gchar *base_dir;
885         const gchar *uid;
886
887         /* Chain up to parent's constructed() method. */
888         G_OBJECT_CLASS (camel_service_parent_class)->constructed (object);
889
890         service = CAMEL_SERVICE (object);
891         session = camel_service_ref_session (service);
892
893         uid = camel_service_get_uid (service);
894
895         base_dir = camel_session_get_user_data_dir (session);
896         service->priv->user_data_dir = g_build_filename (base_dir, uid, NULL);
897
898         base_dir = camel_session_get_user_cache_dir (session);
899         service->priv->user_cache_dir = g_build_filename (base_dir, uid, NULL);
900
901         g_object_unref (session);
902
903         /* The CamelNetworkService interface needs initialization. */
904         if (CAMEL_IS_NETWORK_SERVICE (service)) {
905                 camel_network_service_init (CAMEL_NETWORK_SERVICE (service));
906                 service->priv->network_service_inited = TRUE;
907         }
908 }
909
910 static gchar *
911 service_get_name (CamelService *service,
912                   gboolean brief)
913 {
914         g_warning (
915                 "%s does not implement CamelServiceClass::get_name()",
916                 G_OBJECT_TYPE_NAME (service));
917
918         return g_strdup (G_OBJECT_TYPE_NAME (service));
919 }
920
921 static gboolean
922 service_connect_sync (CamelService *service,
923                       GCancellable *cancellable,
924                       GError **error)
925 {
926         /* Default behavior for local storage providers. */
927         return TRUE;
928 }
929
930 static gboolean
931 service_disconnect_sync (CamelService *service,
932                          gboolean clean,
933                          GCancellable *cancellable,
934                          GError **error)
935 {
936         /* Default behavior for local storage providers. */
937         return TRUE;
938 }
939
940 static GList *
941 service_query_auth_types_sync (CamelService *service,
942                                GCancellable *cancellable,
943                                GError **error)
944 {
945         return NULL;
946 }
947
948 static gboolean
949 service_initable_init (GInitable *initable,
950                        GCancellable *cancellable,
951                        GError **error)
952 {
953         /* Nothing to do here, but we may need add something in the future.
954          * For now this is a placeholder so subclasses can safely chain up. */
955
956         return TRUE;
957 }
958
959 static void
960 camel_service_class_init (CamelServiceClass *class)
961 {
962         GObjectClass *object_class;
963
964         g_type_class_add_private (class, sizeof (CamelServicePrivate));
965
966         object_class = G_OBJECT_CLASS (class);
967         object_class->set_property = service_set_property;
968         object_class->get_property = service_get_property;
969         object_class->dispose = service_dispose;
970         object_class->finalize = service_finalize;
971         object_class->constructed = service_constructed;
972
973         class->settings_type = CAMEL_TYPE_SETTINGS;
974         class->get_name = service_get_name;
975         class->connect_sync = service_connect_sync;
976         class->disconnect_sync = service_disconnect_sync;
977         class->query_auth_types_sync = service_query_auth_types_sync;
978
979         g_object_class_install_property (
980                 object_class,
981                 PROP_CONNECTION_STATUS,
982                 g_param_spec_enum (
983                         "connection-status",
984                         "Connection Status",
985                         "The connection status for the service",
986                         CAMEL_TYPE_SERVICE_CONNECTION_STATUS,
987                         CAMEL_SERVICE_DISCONNECTED,
988                         G_PARAM_READABLE |
989                         G_PARAM_STATIC_STRINGS));
990
991         g_object_class_install_property (
992                 object_class,
993                 PROP_DISPLAY_NAME,
994                 g_param_spec_string (
995                         "display-name",
996                         "Display Name",
997                         "The display name for the service",
998                         NULL,
999                         G_PARAM_READWRITE |
1000                         G_PARAM_CONSTRUCT |
1001                         G_PARAM_STATIC_STRINGS));
1002
1003         g_object_class_install_property (
1004                 object_class,
1005                 PROP_PASSWORD,
1006                 g_param_spec_string (
1007                         "password",
1008                         "Password",
1009                         "The password for the service",
1010                         NULL,
1011                         G_PARAM_READWRITE |
1012                         G_PARAM_CONSTRUCT |
1013                         G_PARAM_STATIC_STRINGS));
1014
1015         g_object_class_install_property (
1016                 object_class,
1017                 PROP_PROVIDER,
1018                 g_param_spec_pointer (
1019                         "provider",
1020                         "Provider",
1021                         "The CamelProvider for the service",
1022                         G_PARAM_READWRITE |
1023                         G_PARAM_CONSTRUCT_ONLY |
1024                         G_PARAM_STATIC_STRINGS));
1025
1026         g_object_class_install_property (
1027                 object_class,
1028                 PROP_PROXY_RESOLVER,
1029                 g_param_spec_object (
1030                         "proxy-resolver",
1031                         "Proxy Resolver",
1032                         "The proxy resolver for the service",
1033                         G_TYPE_PROXY_RESOLVER,
1034                         G_PARAM_READWRITE |
1035                         G_PARAM_STATIC_STRINGS));
1036
1037         g_object_class_install_property (
1038                 object_class,
1039                 PROP_SESSION,
1040                 g_param_spec_object (
1041                         "session",
1042                         "Session",
1043                         "A CamelSession instance",
1044                         CAMEL_TYPE_SESSION,
1045                         G_PARAM_READWRITE |
1046                         G_PARAM_CONSTRUCT_ONLY |
1047                         G_PARAM_STATIC_STRINGS));
1048
1049         g_object_class_install_property (
1050                 object_class,
1051                 PROP_SETTINGS,
1052                 g_param_spec_object (
1053                         "settings",
1054                         "Settings",
1055                         "A CamelSettings instance",
1056                         CAMEL_TYPE_SETTINGS,
1057                         G_PARAM_READWRITE |
1058                         G_PARAM_CONSTRUCT |
1059                         G_PARAM_STATIC_STRINGS));
1060
1061         g_object_class_install_property (
1062                 object_class,
1063                 PROP_UID,
1064                 g_param_spec_string (
1065                         "uid",
1066                         "UID",
1067                         "The unique identity of the service",
1068                         NULL,
1069                         G_PARAM_READWRITE |
1070                         G_PARAM_CONSTRUCT_ONLY |
1071                         G_PARAM_STATIC_STRINGS));
1072 }
1073
1074 static void
1075 camel_service_initable_init (GInitableIface *iface)
1076 {
1077         iface->init = service_initable_init;
1078 }
1079
1080 static void
1081 camel_service_init (CamelService *service)
1082 {
1083         GHashTable *task_table;
1084
1085         task_table = g_hash_table_new_full (
1086                 (GHashFunc) g_direct_hash,
1087                 (GEqualFunc) g_direct_equal,
1088                 (GDestroyNotify) NULL,
1089                 (GDestroyNotify) task_queue_free);
1090
1091         service->priv = CAMEL_SERVICE_GET_PRIVATE (service);
1092
1093         g_mutex_init (&service->priv->property_lock);
1094         g_mutex_init (&service->priv->connection_lock);
1095         service->priv->status = CAMEL_SERVICE_DISCONNECTED;
1096
1097         service->priv->proxy_resolver = g_proxy_resolver_get_default ();
1098         if (service->priv->proxy_resolver != NULL)
1099                 g_object_ref (service->priv->proxy_resolver);
1100
1101         service->priv->task_table = task_table;
1102         g_mutex_init (&service->priv->task_table_lock);
1103 }
1104
1105 G_DEFINE_QUARK (camel-service-error-quark, camel_service_error)
1106
1107 /**
1108  * camel_service_migrate_files:
1109  * @service: a #CamelService
1110  *
1111  * Performs any necessary file migrations for @service.  This should be
1112  * called after installing or configuring the @service's #CamelSettings,
1113  * since it requires building a URL string for @service.
1114  *
1115  * Since: 3.4
1116  **/
1117 void
1118 camel_service_migrate_files (CamelService *service)
1119 {
1120         const gchar *new_data_dir;
1121         gchar *old_data_dir;
1122
1123         g_return_if_fail (CAMEL_IS_SERVICE (service));
1124
1125         new_data_dir = camel_service_get_user_data_dir (service);
1126         old_data_dir = service_find_old_data_dir (service);
1127
1128         /* If the old data directory name exists, try renaming
1129          * it to the new data directory.  Failure is non-fatal. */
1130         if (old_data_dir != NULL) {
1131                 if (g_rename (old_data_dir, new_data_dir) == -1) {
1132                         g_warning (
1133                                 "%s: Failed to rename '%s' to '%s': %s",
1134                                 G_STRFUNC, old_data_dir, new_data_dir, g_strerror (errno));
1135                 }
1136                 g_free (old_data_dir);
1137         }
1138 }
1139
1140 /**
1141  * camel_service_new_camel_url:
1142  * @service: a #CamelService
1143  *
1144  * Returns a new #CamelURL representing @service.
1145  * Free the returned #CamelURL with camel_url_free().
1146  *
1147  * Returns: a new #CamelURL
1148  *
1149  * Since: 3.2
1150  **/
1151 CamelURL *
1152 camel_service_new_camel_url (CamelService *service)
1153 {
1154         CamelURL *url;
1155         CamelProvider *provider;
1156         CamelSettings *settings;
1157         gchar *host = NULL;
1158         gchar *user = NULL;
1159         gchar *path = NULL;
1160         guint16 port = 0;
1161
1162         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1163
1164         provider = camel_service_get_provider (service);
1165         g_return_val_if_fail (provider != NULL, NULL);
1166
1167         settings = camel_service_ref_settings (service);
1168
1169         /* Allocate as camel_url_new_with_base() does. */
1170         url = g_new0 (CamelURL, 1);
1171
1172         if (CAMEL_IS_NETWORK_SETTINGS (settings)) {
1173                 CamelNetworkSettings *network_settings;
1174
1175                 network_settings = CAMEL_NETWORK_SETTINGS (settings);
1176                 host = camel_network_settings_dup_host (network_settings);
1177                 port = camel_network_settings_get_port (network_settings);
1178                 user = camel_network_settings_dup_user (network_settings);
1179         }
1180
1181         if (CAMEL_IS_LOCAL_SETTINGS (settings)) {
1182                 CamelLocalSettings *local_settings;
1183
1184                 local_settings = CAMEL_LOCAL_SETTINGS (settings);
1185                 path = camel_local_settings_dup_path (local_settings);
1186         }
1187
1188         camel_url_set_protocol (url, provider->protocol);
1189         camel_url_set_host (url, host);
1190         camel_url_set_port (url, port);
1191         camel_url_set_user (url, user);
1192         camel_url_set_path (url, path);
1193
1194         g_free (host);
1195         g_free (user);
1196         g_free (path);
1197
1198         g_object_unref (settings);
1199
1200         return url;
1201 }
1202
1203 /**
1204  * camel_service_get_connection_status:
1205  * @service: a #CamelService
1206  *
1207  * Returns the connection status for @service.
1208  *
1209  * Returns: the connection status
1210  *
1211  * Since: 3.2
1212  **/
1213 CamelServiceConnectionStatus
1214 camel_service_get_connection_status (CamelService *service)
1215 {
1216         g_return_val_if_fail (
1217                 CAMEL_IS_SERVICE (service),
1218                 CAMEL_SERVICE_DISCONNECTED);
1219
1220         return service->priv->status;
1221 }
1222
1223 /**
1224  * camel_service_get_display_name:
1225  * @service: a #CamelService
1226  *
1227  * Returns the display name for @service, or %NULL if @service has not
1228  * been given a display name.  The display name is intended for use in
1229  * a user interface and should generally be given a user-defined name.
1230  *
1231  * Compare this with camel_service_get_name(), which returns a built-in
1232  * description of the type of service (IMAP, SMTP, etc.).
1233  *
1234  * Returns: the display name for @service, or %NULL
1235  *
1236  * Since: 3.2
1237  **/
1238 const gchar *
1239 camel_service_get_display_name (CamelService *service)
1240 {
1241         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1242
1243         return service->priv->display_name;
1244 }
1245
1246 /**
1247  * camel_service_dup_display_name:
1248  * @service: a #CamelService
1249  *
1250  * Thread-safe variation of camel_service_get_display_name().
1251  * Use this function when accessing @service from multiple threads.
1252  *
1253  * The returned string should be freed with g_free() when no longer needed.
1254  *
1255  * Returns: a newly-allocated copy of #CamelService:display-name
1256  *
1257  * Since: 3.12
1258  **/
1259 gchar *
1260 camel_service_dup_display_name (CamelService *service)
1261 {
1262         const gchar *protected;
1263         gchar *duplicate;
1264
1265         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1266
1267         g_mutex_lock (&service->priv->property_lock);
1268
1269         protected = camel_service_get_display_name (service);
1270         duplicate = g_strdup (protected);
1271
1272         g_mutex_unlock (&service->priv->property_lock);
1273
1274         return duplicate;
1275 }
1276
1277 /**
1278  * camel_service_set_display_name:
1279  * @service: a #CamelService
1280  * @display_name: a valid UTF-8 string, or %NULL
1281  *
1282  * Assigns a UTF-8 display name to @service.  The display name is intended
1283  * for use in a user interface and should generally be given a user-defined
1284  * name.
1285  *
1286  * Compare this with camel_service_get_name(), which returns a built-in
1287  * description of the type of service (IMAP, SMTP, etc.).
1288  *
1289  * Since: 3.2
1290  **/
1291 void
1292 camel_service_set_display_name (CamelService *service,
1293                                 const gchar *display_name)
1294 {
1295         g_return_if_fail (CAMEL_IS_SERVICE (service));
1296
1297         if (display_name != NULL)
1298                 g_return_if_fail (g_utf8_validate (display_name, -1, NULL));
1299
1300         g_mutex_lock (&service->priv->property_lock);
1301
1302         if (g_strcmp0 (service->priv->display_name, display_name) == 0) {
1303                 g_mutex_unlock (&service->priv->property_lock);
1304                 return;
1305         }
1306
1307         g_free (service->priv->display_name);
1308         service->priv->display_name = g_strdup (display_name);
1309
1310         g_mutex_unlock (&service->priv->property_lock);
1311
1312         g_object_notify (G_OBJECT (service), "display-name");
1313 }
1314
1315 /**
1316  * camel_service_get_password:
1317  * @service: a #CamelService
1318  *
1319  * Returns the password for @service.  Some SASL mechanisms use this
1320  * when attempting to authenticate.
1321  *
1322  * Returns: the password for @service
1323  *
1324  * Since: 3.4
1325  **/
1326 const gchar *
1327 camel_service_get_password (CamelService *service)
1328 {
1329         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1330
1331         return service->priv->password;
1332 }
1333
1334 /**
1335  * camel_service_dup_password:
1336  * @service: a #CamelService
1337  *
1338  * Thread-safe variation of camel_service_get_password().
1339  * Use this function when accessing @service from multiple threads.
1340  *
1341  * The returned string should be freed with g_free() when no longer needed.
1342  *
1343  * Returns: a newly-allocated copy of #CamelService:password
1344  *
1345  * Since: 3.12
1346  **/
1347 gchar *
1348 camel_service_dup_password (CamelService *service)
1349 {
1350         const gchar *protected;
1351         gchar *duplicate;
1352
1353         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1354
1355         g_mutex_lock (&service->priv->property_lock);
1356
1357         protected = camel_service_get_password (service);
1358         duplicate = g_strdup (protected);
1359
1360         g_mutex_unlock (&service->priv->property_lock);
1361
1362         return duplicate;
1363 }
1364
1365 /**
1366  * camel_service_set_password:
1367  * @service: a #CamelService
1368  * @password: the password for @service
1369  *
1370  * Sets the password for @service.  Use this function to cache the password
1371  * in memory after obtaining it through camel_session_get_password().  Some
1372  * SASL mechanisms use this when attempting to authenticate.
1373  *
1374  * Since: 3.4
1375  **/
1376 void
1377 camel_service_set_password (CamelService *service,
1378                             const gchar *password)
1379 {
1380         g_return_if_fail (CAMEL_IS_SERVICE (service));
1381
1382         g_mutex_lock (&service->priv->property_lock);
1383
1384         if (g_strcmp0 (service->priv->password, password) == 0) {
1385                 g_mutex_unlock (&service->priv->property_lock);
1386                 return;
1387         }
1388
1389         g_free (service->priv->password);
1390         service->priv->password = g_strdup (password);
1391
1392         g_mutex_unlock (&service->priv->property_lock);
1393
1394         g_object_notify (G_OBJECT (service), "password");
1395 }
1396
1397 /**
1398  * camel_service_get_user_data_dir:
1399  * @service: a #CamelService
1400  *
1401  * Returns the base directory under which to store user-specific data
1402  * for @service.  The directory is formed by appending the directory
1403  * returned by camel_session_get_user_data_dir() with the service's
1404  * #CamelService:uid value.
1405  *
1406  * Returns: the base directory for @service
1407  *
1408  * Since: 3.2
1409  **/
1410 const gchar *
1411 camel_service_get_user_data_dir (CamelService *service)
1412 {
1413         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1414
1415         return service->priv->user_data_dir;
1416 }
1417
1418 /**
1419  * camel_service_get_user_cache_dir:
1420  * @service: a #CamelService
1421  *
1422  * Returns the base directory under which to store cache data
1423  * for @service.  The directory is formed by appending the directory
1424  * returned by camel_session_get_user_cache_dir() with the service's
1425  * #CamelService:uid value.
1426  *
1427  * Returns: the base cache directory for @service
1428  *
1429  * Since: 3.4
1430  **/
1431 const gchar *
1432 camel_service_get_user_cache_dir (CamelService *service)
1433 {
1434         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1435
1436         return service->priv->user_cache_dir;
1437 }
1438
1439 /**
1440  * camel_service_get_name:
1441  * @service: a #CamelService
1442  * @brief: whether or not to use a briefer form
1443  *
1444  * This gets the name of the service in a "friendly" (suitable for
1445  * humans) form. If @brief is %TRUE, this should be a brief description
1446  * such as for use in the folder tree. If @brief is %FALSE, it should
1447  * be a more complete and mostly unambiguous description.
1448  *
1449  * Returns: a description of the service which the caller must free
1450  **/
1451 gchar *
1452 camel_service_get_name (CamelService *service,
1453                         gboolean brief)
1454 {
1455         CamelServiceClass *class;
1456
1457         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1458
1459         class = CAMEL_SERVICE_GET_CLASS (service);
1460         g_return_val_if_fail (class->get_name != NULL, NULL);
1461
1462         return class->get_name (service, brief);
1463 }
1464
1465 /**
1466  * camel_service_get_provider:
1467  * @service: a #CamelService
1468  *
1469  * Gets the #CamelProvider associated with the service.
1470  *
1471  * Returns: the #CamelProvider
1472  **/
1473 CamelProvider *
1474 camel_service_get_provider (CamelService *service)
1475 {
1476         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1477
1478         return service->priv->provider;
1479 }
1480
1481 /**
1482  * camel_service_ref_proxy_resolver:
1483  * @service: a #CamelService
1484  *
1485  * Returns the #GProxyResolver for @service.  If an application needs to
1486  * override this, it should do so prior to calling functions on @service
1487  * that may require a network connection.
1488  *
1489  * The returned #GProxyResolver is referenced for thread-safety and must
1490  * be unreferenced with g_object_unref() when finished with it.
1491  *
1492  * Returns: a #GProxyResolver, or %NULL
1493  *
1494  * Since: 3.12
1495  **/
1496 GProxyResolver *
1497 camel_service_ref_proxy_resolver (CamelService *service)
1498 {
1499         GProxyResolver *proxy_resolver = NULL;
1500
1501         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1502
1503         g_mutex_lock (&service->priv->property_lock);
1504
1505         if (service->priv->proxy_resolver != NULL)
1506                 proxy_resolver = g_object_ref (service->priv->proxy_resolver);
1507
1508         g_mutex_unlock (&service->priv->property_lock);
1509
1510         return proxy_resolver;
1511 }
1512
1513 /**
1514  * camel_service_set_proxy_resolver:
1515  * @service: a #CamelService
1516  * @proxy_resolver: a #GProxyResolver, or %NULL for the default
1517  *
1518  * Sets the #GProxyResolver for @service.  If an application needs to
1519  * override this, it should do so prior to calling functions on @service
1520  * that may require a network connection.
1521  *
1522  * Since: 3.12
1523  **/
1524 void
1525 camel_service_set_proxy_resolver (CamelService *service,
1526                                   GProxyResolver *proxy_resolver)
1527 {
1528         gboolean notify = FALSE;
1529
1530         if (proxy_resolver == NULL)
1531                 proxy_resolver = g_proxy_resolver_get_default ();
1532
1533         g_return_if_fail (CAMEL_IS_SERVICE (service));
1534         g_return_if_fail (G_IS_PROXY_RESOLVER (proxy_resolver));
1535
1536         g_mutex_lock (&service->priv->property_lock);
1537
1538         /* Emitting a "notify" signal unnecessarily might have
1539          * unwanted side effects like cancelling a SoupMessage.
1540          * Only emit if we now have a different GProxyResolver. */
1541
1542         if (proxy_resolver != service->priv->proxy_resolver) {
1543                 g_clear_object (&service->priv->proxy_resolver);
1544                 service->priv->proxy_resolver = proxy_resolver;
1545
1546                 if (proxy_resolver != NULL)
1547                         g_object_ref (proxy_resolver);
1548
1549                 notify = TRUE;
1550         }
1551
1552         g_mutex_unlock (&service->priv->property_lock);
1553
1554         if (notify)
1555                 g_object_notify (G_OBJECT (service), "proxy-resolver");
1556 }
1557
1558 /**
1559  * camel_service_ref_session:
1560  * @service: a #CamelService
1561  *
1562  * Returns the #CamelSession associated with the service.
1563  *
1564  * The returned #CamelSession is referenced for thread-safety.  Unreference
1565  * the #CamelSession with g_object_unref() when finished with it.
1566  *
1567  * Returns: the #CamelSession
1568  *
1569  * Since: 3.8
1570  **/
1571 CamelSession *
1572 camel_service_ref_session (CamelService *service)
1573 {
1574         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1575
1576         return g_weak_ref_get (&service->priv->session);
1577 }
1578
1579 /**
1580  * camel_service_ref_settings:
1581  * @service: a #CamelService
1582  *
1583  * Returns the #CamelSettings instance associated with the service.
1584  *
1585  * The returned #CamelSettings is referenced for thread-safety and must
1586  * be unreferenced with g_object_unref() when finished with it.
1587  *
1588  * Returns: the #CamelSettings
1589  *
1590  * Since: 3.6
1591  **/
1592 CamelSettings *
1593 camel_service_ref_settings (CamelService *service)
1594 {
1595         CamelSettings *settings;
1596
1597         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1598
1599         /* Every service should have a settings object. */
1600         g_return_val_if_fail (service->priv->settings != NULL, NULL);
1601
1602         g_mutex_lock (&service->priv->property_lock);
1603
1604         settings = g_object_ref (service->priv->settings);
1605
1606         g_mutex_unlock (&service->priv->property_lock);
1607
1608         return settings;
1609 }
1610
1611 /**
1612  * camel_service_set_settings:
1613  * @service: a #CamelService
1614  * @settings: an instance derviced from #CamelSettings, or %NULL
1615  *
1616  * Associates a new #CamelSettings instance with the service.
1617  * The @settings instance must match the settings type defined in
1618  * #CamelServiceClass.  If @settings is %NULL, a new #CamelSettings
1619  * instance of the appropriate type is created with all properties
1620  * set to defaults.
1621  *
1622  * Since: 3.2
1623  **/
1624 void
1625 camel_service_set_settings (CamelService *service,
1626                             CamelSettings *settings)
1627 {
1628         CamelServiceClass *class;
1629
1630         g_return_if_fail (CAMEL_IS_SERVICE (service));
1631
1632         class = CAMEL_SERVICE_GET_CLASS (service);
1633
1634         if (settings != NULL) {
1635                 g_return_if_fail (
1636                         g_type_is_a (
1637                                 G_OBJECT_TYPE (settings),
1638                                 class->settings_type));
1639                 g_object_ref (settings);
1640
1641         } else {
1642                 g_return_if_fail (
1643                         g_type_is_a (
1644                                 class->settings_type,
1645                                 CAMEL_TYPE_SETTINGS));
1646                 settings = g_object_new (class->settings_type, NULL);
1647         }
1648
1649         g_mutex_lock (&service->priv->property_lock);
1650
1651         if (service->priv->settings != NULL)
1652                 g_object_unref (service->priv->settings);
1653
1654         service->priv->settings = settings;  /* takes ownership */
1655
1656         g_mutex_unlock (&service->priv->property_lock);
1657
1658         /* If the service is a CamelNetworkService, it needs to
1659          * replace its GSocketConnectable for the new settings. */
1660         if (service->priv->network_service_inited)
1661                 camel_network_service_set_connectable (
1662                         CAMEL_NETWORK_SERVICE (service), NULL);
1663
1664         g_object_notify (G_OBJECT (service), "settings");
1665 }
1666
1667 /**
1668  * camel_service_get_uid:
1669  * @service: a #CamelService
1670  *
1671  * Gets the unique identifier string associated with the service.
1672  *
1673  * Returns: the UID string
1674  *
1675  * Since: 3.2
1676  **/
1677 const gchar *
1678 camel_service_get_uid (CamelService *service)
1679 {
1680         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1681
1682         return service->priv->uid;
1683 }
1684
1685 /**
1686  * camel_service_queue_task:
1687  * @service: a #CamelService
1688  * @task: a #GTask
1689  * @task_func: function to call when @task is dispatched
1690  *
1691  * Adds @task to a queue of waiting tasks with the same source object.
1692  * Queued tasks execute one at a time in the order they were added.  When
1693  * @task reaches the front of the queue, it will be dispatched by invoking
1694  * @task_func in a separate thread.  If @task is cancelled while queued,
1695  * it will complete immediately with an appropriate error.
1696  *
1697  * This is primarily intended for use by #CamelStore, #CamelTransport and
1698  * #CamelFolder to achieve ordered invocation of synchronous class methods.
1699  *
1700  * Since: 3.12
1701  **/
1702 void
1703 camel_service_queue_task (CamelService *service,
1704                           GTask *task,
1705                           GTaskThreadFunc task_func)
1706 {
1707         DispatchData *dispatch_data;
1708         gboolean return_on_cancel;
1709
1710         g_return_if_fail (CAMEL_IS_SERVICE (service));
1711         g_return_if_fail (G_IS_TASK (task));
1712         g_return_if_fail (task_func != NULL);
1713
1714         return_on_cancel = g_task_get_return_on_cancel (task);
1715
1716         dispatch_data = g_slice_new0 (DispatchData);
1717         g_weak_ref_set (&dispatch_data->service, service);
1718         dispatch_data->return_on_cancel = return_on_cancel;
1719         dispatch_data->task_func = task_func;
1720
1721         /* Complete immediately if cancelled while queued. */
1722         g_task_set_return_on_cancel (task, TRUE);
1723
1724         /* Stash this until it's time to dispatch the GTask. */
1725         g_object_set_data_full (
1726                 G_OBJECT (task), DISPATCH_DATA_KEY,
1727                 dispatch_data, (GDestroyNotify) dispatch_data_free);
1728
1729         service_task_table_push (service, task);
1730 }
1731
1732 /**
1733  * camel_service_connect_sync:
1734  * @service: a #CamelService
1735  * @cancellable: optional #GCancellable object, or %NULL
1736  * @error: return location for a #GError, or %NULL
1737  *
1738  * Connects @service to a remote server using the information in its
1739  * #CamelService:settings instance.
1740  *
1741  * If a connect operation is already in progress when this function is
1742  * called, its results will be reflected in this connect operation.
1743  *
1744  * Returns: %TRUE if the connection is made or %FALSE otherwise
1745  *
1746  * Since: 3.6
1747  **/
1748 gboolean
1749 camel_service_connect_sync (CamelService *service,
1750                             GCancellable *cancellable,
1751                             GError **error)
1752 {
1753         CamelAsyncClosure *closure;
1754         GAsyncResult *result;
1755         gboolean success;
1756
1757         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
1758
1759         closure = camel_async_closure_new ();
1760
1761         camel_service_connect (
1762                 service, G_PRIORITY_DEFAULT, cancellable,
1763                 camel_async_closure_callback, closure);
1764
1765         result = camel_async_closure_wait (closure);
1766
1767         success = camel_service_connect_finish (service, result, error);
1768
1769         camel_async_closure_free (closure);
1770
1771         return success;
1772 }
1773
1774 /**
1775  * camel_service_connect:
1776  * @service: a #CamelService
1777  * @io_priority: the I/O priority of the request
1778  * @cancellable: optional #GCancellable object, or %NULL
1779  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1780  * @user_data: data to pass to the callback function
1781  *
1782  * Asynchronously connects @service to a remote server using the information
1783  * in its #CamelService:settings instance.
1784  *
1785  * If a connect operation is already in progress when this function is
1786  * called, its results will be reflected in this connect operation.
1787  *
1788  * If any disconnect operations are in progress when this function is
1789  * called, they will be cancelled.
1790  *
1791  * When the operation is finished, @callback will be called.  You can
1792  * then call camel_service_connect_finish() to get the result of the
1793  * operation.
1794  *
1795  * Since: 3.6
1796  **/
1797 void
1798 camel_service_connect (CamelService *service,
1799                        gint io_priority,
1800                        GCancellable *cancellable,
1801                        GAsyncReadyCallback callback,
1802                        gpointer user_data)
1803 {
1804         GTask *task;
1805         ConnectionOp *op;
1806
1807         g_return_if_fail (CAMEL_IS_SERVICE (service));
1808
1809         task = g_task_new (service, cancellable, callback, user_data);
1810         g_task_set_source_tag (task, camel_service_connect);
1811         g_task_set_priority (task, io_priority);
1812
1813         g_mutex_lock (&service->priv->connection_lock);
1814
1815         switch (service->priv->status) {
1816
1817                 /* If a connect operation is already in progress,
1818                  * queue this operation so it completes at the same
1819                  * time the first connect operation completes. */
1820                 case CAMEL_SERVICE_CONNECTING:
1821                         connection_op_add_pending (
1822                                 service->priv->connection_op,
1823                                 task, cancellable);
1824                         break;
1825
1826                 /* If we're already connected, just report success. */
1827                 case CAMEL_SERVICE_CONNECTED:
1828                         g_task_return_boolean (task, TRUE);
1829                         break;
1830
1831                 /* If a disconnect operation is currently in progress,
1832                  * cancel it and make room for the connect operation. */
1833                 case CAMEL_SERVICE_DISCONNECTING:
1834                         g_return_if_fail (
1835                                 service->priv->connection_op != NULL);
1836                         g_cancellable_cancel (
1837                                 service->priv->connection_op->cancellable);
1838                         connection_op_unref (service->priv->connection_op);
1839                         service->priv->connection_op = NULL;
1840                         /* fall through */
1841
1842                 /* Start a new connect operation.  Subsequent connect
1843                  * operations are queued until this operation completes
1844                  * and will share this operation's result. */
1845                 case CAMEL_SERVICE_DISCONNECTED:
1846                         g_return_if_fail (
1847                                 service->priv->connection_op == NULL);
1848
1849                         op = connection_op_new (task, cancellable);
1850                         service->priv->connection_op = op;
1851
1852                         service->priv->status = CAMEL_SERVICE_CONNECTING;
1853                         service_queue_notify_connection_status (service);
1854
1855                         service_shared_connect (service, io_priority, op);
1856                         break;
1857
1858                 default:
1859                         g_warn_if_reached ();
1860         }
1861
1862         g_mutex_unlock (&service->priv->connection_lock);
1863
1864         g_object_unref (task);
1865 }
1866
1867 /**
1868  * camel_service_connect_finish:
1869  * @service: a #CamelService
1870  * @result: a #GAsyncResult
1871  * @error: return location for a #GError, or %NULL
1872  *
1873  * Finishes the operation started with camel_service_connect().
1874  *
1875  * Returns: %TRUE if the connection was made or %FALSE otherwise
1876  *
1877  * Since: 3.6
1878  **/
1879 gboolean
1880 camel_service_connect_finish (CamelService *service,
1881                               GAsyncResult *result,
1882                               GError **error)
1883 {
1884         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
1885         g_return_val_if_fail (g_task_is_valid (result, service), FALSE);
1886
1887         g_return_val_if_fail (
1888                 g_async_result_is_tagged (
1889                 result, camel_service_connect), FALSE);
1890
1891         return g_task_propagate_boolean (G_TASK (result), error);
1892 }
1893
1894 /**
1895  * camel_service_disconnect_sync:
1896  * @service: a #CamelService
1897  * @clean: whether or not to try to disconnect cleanly
1898  * @cancellable: optional #GCancellable object, or %NULL
1899  * @error: return location for a #GError, or %NULL
1900  *
1901  * Disconnect from the service. If @clean is %FALSE, it should not
1902  * try to do any synchronizing or other cleanup of the connection.
1903  *
1904  * If a disconnect operation is already in progress when this function is
1905  * called, its results will be reflected in this disconnect operation.
1906  *
1907  * If any connect operations are in progress when this function is called,
1908  * they will be cancelled.
1909  *
1910  * Returns: %TRUE if the connection was severed or %FALSE otherwise
1911  *
1912  * Since: 3.6
1913  **/
1914 gboolean
1915 camel_service_disconnect_sync (CamelService *service,
1916                                gboolean clean,
1917                                GCancellable *cancellable,
1918                                GError **error)
1919 {
1920         CamelAsyncClosure *closure;
1921         GAsyncResult *result;
1922         gboolean success;
1923
1924         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
1925
1926         closure = camel_async_closure_new ();
1927
1928         camel_service_disconnect (
1929                 service, clean, G_PRIORITY_DEFAULT, cancellable,
1930                 camel_async_closure_callback, closure);
1931
1932         result = camel_async_closure_wait (closure);
1933
1934         success = camel_service_disconnect_finish (service, result, error);
1935
1936         camel_async_closure_free (closure);
1937
1938         return success;
1939 }
1940
1941 /**
1942  * camel_service_disconnect:
1943  * @service: a #CamelService
1944  * @clean: whether or not to try to disconnect cleanly
1945  * @io_priority: the I/O priority of the request
1946  * @cancellable: optional #GCancellable object, or %NULL
1947  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1948  * @user_data: data to pass to the callback function
1949  *
1950  * If a disconnect operation is already in progress when this function is
1951  * called, its results will be reflected in this disconnect operation.
1952  *
1953  * If any connect operations are in progress when this function is called,
1954  * they will be cancelled.
1955  *
1956  * When the operation is finished, @callback will be called.  You can
1957  * then call camel_service_disconnect_finish() to get the result of the
1958  * operation.
1959  *
1960  * Since: 3.6
1961  **/
1962 void
1963 camel_service_disconnect (CamelService *service,
1964                           gboolean clean,
1965                           gint io_priority,
1966                           GCancellable *cancellable,
1967                           GAsyncReadyCallback callback,
1968                           gpointer user_data)
1969 {
1970         GTask *task;
1971         ConnectionOp *op;
1972
1973         g_return_if_fail (CAMEL_IS_SERVICE (service));
1974
1975         task = g_task_new (service, cancellable, callback, user_data);
1976         g_task_set_source_tag (task, camel_service_disconnect);
1977         g_task_set_priority (task, io_priority);
1978
1979         g_mutex_lock (&service->priv->connection_lock);
1980
1981         switch (service->priv->status) {
1982
1983                 /* If a connect operation is currently in progress,
1984                  * cancel it and make room for the disconnect operation. */
1985                 case CAMEL_SERVICE_CONNECTING:
1986                         g_return_if_fail (
1987                                 service->priv->connection_op != NULL);
1988                         g_cancellable_cancel (
1989                                 service->priv->connection_op->cancellable);
1990                         connection_op_unref (service->priv->connection_op);
1991                         service->priv->connection_op = NULL;
1992                         /* fall through */
1993
1994                 /* Start a new disconnect operation.  Subsequent disconnect
1995                  * operations are queued until this operation completes and
1996                  * will share this operation's result. */
1997                 case CAMEL_SERVICE_CONNECTED:
1998                         g_return_if_fail (
1999                                 service->priv->connection_op == NULL);
2000
2001                         op = connection_op_new (task, cancellable);
2002                         service->priv->connection_op = op;
2003
2004                         /* Do not change the status if CONNECTING, in case a
2005                          * provider calls disconnect() during the connection
2006                          * phase, which confuses the other logic here and
2007                          * effectively makes the service's connection state
2008                          * CONNECTED instead of DISCONNECTED at the end. */
2009                         if (service->priv->status != CAMEL_SERVICE_CONNECTING) {
2010                                 service->priv->status = CAMEL_SERVICE_DISCONNECTING;
2011                                 service_queue_notify_connection_status (service);
2012                         }
2013
2014                         service_shared_disconnect (
2015                                 service, clean, io_priority, op);
2016                         break;
2017
2018                 /* If a disconnect operation is already in progress,
2019                  * queue this operation so it completes at the same
2020                  * time the first disconnect operation completes. */
2021                 case CAMEL_SERVICE_DISCONNECTING:
2022                         connection_op_add_pending (
2023                                 service->priv->connection_op,
2024                                 task, cancellable);
2025                         break;
2026
2027                 /* If we're already disconnected, just report success. */
2028                 case CAMEL_SERVICE_DISCONNECTED:
2029                         g_task_return_boolean (task, TRUE);
2030                         break;
2031
2032                 default:
2033                         g_warn_if_reached ();
2034         }
2035
2036         g_mutex_unlock (&service->priv->connection_lock);
2037
2038         g_object_unref (task);
2039 }
2040
2041 /**
2042  * camel_service_disconnect_finish:
2043  * @service: a #CamelService
2044  * @result: a #GAsyncResult
2045  * @error: return location for a #GError, or %NULL
2046  *
2047  * Finishes the operation started with camel_service_disconnect().
2048  *
2049  * Returns: %TRUE if the connection was severed or %FALSE otherwise
2050  *
2051  * Since: 3.6
2052  **/
2053 gboolean
2054 camel_service_disconnect_finish (CamelService *service,
2055                                  GAsyncResult *result,
2056                                  GError **error)
2057 {
2058         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
2059         g_return_val_if_fail (g_task_is_valid (result, service), FALSE);
2060
2061         g_return_val_if_fail (
2062                 g_async_result_is_tagged (
2063                 result, camel_service_disconnect), FALSE);
2064
2065         return g_task_propagate_boolean (G_TASK (result), error);
2066 }
2067
2068 /**
2069  * camel_service_authenticate_sync:
2070  * @service: a #CamelService
2071  * @mechanism: a SASL mechanism name, or %NULL
2072  * @cancellable: optional #GCancellable object, or %NULL
2073  * @error: return location for a #GError, or %NULL
2074  *
2075  * Attempts to authenticate @service using @mechanism and, if necessary,
2076  * @service's #CamelService:password property.  The function makes only
2077  * ONE attempt at authentication and does not loop.
2078  *
2079  * If the authentication attempt completed and the server accepted the
2080  * credentials, the function returns #CAMEL_AUTHENTICATION_ACCEPTED.
2081  *
2082  * If the authentication attempt completed but the server rejected the
2083  * credentials, the function returns #CAMEL_AUTHENTICATION_REJECTED.
2084  *
2085  * If the authentication attempt failed to complete due to a network
2086  * communication issue or some other mishap, the function sets @error
2087  * and returns #CAMEL_AUTHENTICATION_ERROR.
2088  *
2089  * Generally this function should only be called from a #CamelSession
2090  * subclass in order to implement its own authentication loop.
2091  *
2092  * Returns: the authentication result
2093  *
2094  * Since: 3.4
2095  **/
2096 CamelAuthenticationResult
2097 camel_service_authenticate_sync (CamelService *service,
2098                                  const gchar *mechanism,
2099                                  GCancellable *cancellable,
2100                                  GError **error)
2101 {
2102         CamelServiceClass *class;
2103         CamelAuthenticationResult result;
2104
2105         g_return_val_if_fail (
2106                 CAMEL_IS_SERVICE (service),
2107                 CAMEL_AUTHENTICATION_ERROR);
2108
2109         class = CAMEL_SERVICE_GET_CLASS (service);
2110         g_return_val_if_fail (
2111                 class->authenticate_sync != NULL,
2112                 CAMEL_AUTHENTICATION_ERROR);
2113
2114         result = class->authenticate_sync (
2115                 service, mechanism, cancellable, error);
2116         CAMEL_CHECK_GERROR (
2117                 service, authenticate_sync,
2118                 result != CAMEL_AUTHENTICATION_ERROR, error);
2119
2120         return result;
2121 }
2122
2123 /* Helper for camel_service_authenticate() */
2124 static void
2125 service_authenticate_thread (GTask *task,
2126                              gpointer source_object,
2127                              gpointer task_data,
2128                              GCancellable *cancellable)
2129 {
2130         CamelAuthenticationResult auth_result;
2131         AsyncContext *async_context;
2132         GError *local_error = NULL;
2133
2134         async_context = (AsyncContext *) task_data;
2135
2136         auth_result = camel_service_authenticate_sync (
2137                 CAMEL_SERVICE (source_object),
2138                 async_context->auth_mechanism,
2139                 cancellable, &local_error);
2140
2141         if (local_error != NULL) {
2142                 g_task_return_error (task, local_error);
2143         } else {
2144                 g_task_return_int (task, auth_result);
2145         }
2146 }
2147
2148 /**
2149  * camel_service_authenticate:
2150  * @service: a #CamelService
2151  * @mechanism: a SASL mechanism name, or %NULL
2152  * @io_priority: the I/O priority of the request
2153  * @cancellable: optional #GCancellable object, or %NULL
2154  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
2155  * @user_data: data to pass to the callback function
2156  *
2157  * Asynchronously attempts to authenticate @service using @mechanism and,
2158  * if necessary, @service's #CamelService:password property.  The function
2159  * makes only ONE attempt at authentication and does not loop.
2160  *
2161  * Generally this function should only be called from a #CamelSession
2162  * subclass in order to implement its own authentication loop.
2163  *
2164  * When the operation is finished, @callback will be called.  You can
2165  * then call camel_service_authenticate_finish() to get the result of
2166  * the operation.
2167  *
2168  * Since: 3.4
2169  **/
2170 void
2171 camel_service_authenticate (CamelService *service,
2172                             const gchar *mechanism,
2173                             gint io_priority,
2174                             GCancellable *cancellable,
2175                             GAsyncReadyCallback callback,
2176                             gpointer user_data)
2177 {
2178         GTask *task;
2179         AsyncContext *async_context;
2180
2181         g_return_if_fail (CAMEL_IS_SERVICE (service));
2182
2183         async_context = g_slice_new0 (AsyncContext);
2184         async_context->auth_mechanism = g_strdup (mechanism);
2185
2186         task = g_task_new (service, cancellable, callback, user_data);
2187         g_task_set_source_tag (task, camel_service_authenticate);
2188         g_task_set_priority (task, io_priority);
2189
2190         g_task_set_task_data (
2191                 task, async_context,
2192                 (GDestroyNotify) async_context_free);
2193
2194         g_task_run_in_thread (task, service_authenticate_thread);
2195
2196         g_object_unref (task);
2197 }
2198
2199 /**
2200  * camel_service_authenticate_finish:
2201  * @service: a #CamelService
2202  * @result: a #GAsyncResult
2203  * @error: return location for a #GError, or %NULL
2204  *
2205  * Finishes the operation started with camel_service_authenticate().
2206  *
2207  * If the authentication attempt completed and the server accepted the
2208  * credentials, the function returns #CAMEL_AUTHENTICATION_ACCEPTED.
2209  *
2210  * If the authentication attempt completed but the server rejected the
2211  * credentials, the function returns #CAMEL_AUTHENTICATION_REJECTED.
2212  *
2213  * If the authentication attempt failed to complete due to a network
2214  * communication issue or some other mishap, the function sets @error
2215  * and returns #CAMEL_AUTHENTICATION_ERROR.
2216  *
2217  * Returns: the authentication result
2218  *
2219  * Since: 3.4
2220  **/
2221 CamelAuthenticationResult
2222 camel_service_authenticate_finish (CamelService *service,
2223                                    GAsyncResult *result,
2224                                    GError **error)
2225 {
2226         CamelAuthenticationResult auth_result;
2227
2228         g_return_val_if_fail (
2229                 CAMEL_IS_SERVICE (service),
2230                 CAMEL_AUTHENTICATION_ERROR);
2231         g_return_val_if_fail (
2232                 g_task_is_valid (result, service),
2233                 CAMEL_AUTHENTICATION_ERROR);
2234
2235         g_return_val_if_fail (
2236                 g_async_result_is_tagged (
2237                 result, camel_service_authenticate),
2238                 CAMEL_AUTHENTICATION_ERROR);
2239
2240         /* XXX A little hackish, but best way to return enum values
2241          *     from GTask in GLib 2.36.  Recommended by Dan Winship. */
2242
2243         auth_result = g_task_propagate_int (G_TASK (result), error);
2244
2245         if (auth_result == (CamelAuthenticationResult) -1)
2246                 return CAMEL_AUTHENTICATION_ERROR;
2247
2248         return auth_result;
2249 }
2250
2251 /**
2252  * camel_service_query_auth_types_sync:
2253  * @service: a #CamelService
2254  * @cancellable: optional #GCancellable object, or %NULL
2255  * @error: return location for a #GError, or %NULL
2256  *
2257  * Obtains a list of authentication types supported by @service.
2258  * Free the returned list with g_list_free().
2259  *
2260  * Returns: a list of #CamelServiceAuthType structs
2261  **/
2262 GList *
2263 camel_service_query_auth_types_sync (CamelService *service,
2264                                      GCancellable *cancellable,
2265                                      GError **error)
2266 {
2267         CamelServiceClass *class;
2268
2269         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
2270
2271         class = CAMEL_SERVICE_GET_CLASS (service);
2272         g_return_val_if_fail (class->query_auth_types_sync != NULL, NULL);
2273
2274         return class->query_auth_types_sync (service, cancellable, error);
2275 }
2276
2277 /* Helper for camel_service_query_auth_types() */
2278 static void
2279 service_query_auth_types_thread (GTask *task,
2280                                  gpointer source_object,
2281                                  gpointer task_data,
2282                                  GCancellable *cancellable)
2283 {
2284         GList *auth_types;
2285         GError *local_error = NULL;
2286
2287         auth_types = camel_service_query_auth_types_sync (
2288                 CAMEL_SERVICE (source_object),
2289                 cancellable, &local_error);
2290
2291         if (local_error != NULL) {
2292                 g_warn_if_fail (auth_types == NULL);
2293                 g_task_return_error (task, local_error);
2294         } else {
2295                 g_task_return_pointer (
2296                         task, auth_types,
2297                         (GDestroyNotify) g_list_free);
2298         }
2299 }
2300
2301 /**
2302  * camel_service_query_auth_types:
2303  * @service: a #CamelService
2304  * @io_priority: the I/O priority of the request
2305  * @cancellable: optional #GCancellable object, or %NULL
2306  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
2307  * @user_data: data to pass to the callback function
2308  *
2309  * Asynchronously obtains a list of authentication types supported by
2310  * @service.
2311  *
2312  * When the operation is finished, @callback will be called.  You can
2313  * then call camel_service_query_auth_types_finish() to get the result
2314  * of the operation.
2315  *
2316  * Since: 3.2
2317  **/
2318 void
2319 camel_service_query_auth_types (CamelService *service,
2320                                 gint io_priority,
2321                                 GCancellable *cancellable,
2322                                 GAsyncReadyCallback callback,
2323                                 gpointer user_data)
2324 {
2325         GTask *task;
2326
2327         g_return_if_fail (CAMEL_IS_SERVICE (service));
2328
2329         task = g_task_new (service, cancellable, callback, user_data);
2330         g_task_set_source_tag (task, camel_service_query_auth_types);
2331         g_task_set_priority (task, io_priority);
2332
2333         g_task_run_in_thread (task, service_query_auth_types_thread);
2334
2335         g_object_unref (task);
2336 }
2337
2338 /**
2339  * camel_service_query_auth_types_finish:
2340  * @service: a #CamelService
2341  * @result: a #GAsyncResult
2342  * @error: return location for a #GError, or %NULL
2343  *
2344  * Finishes the operation started with camel_service_query_auth_types().
2345  * Free the returned list with g_list_free().
2346  *
2347  * Returns: a list of #CamelServiceAuthType structs
2348  *
2349  * Since: 3.2
2350  **/
2351 GList *
2352 camel_service_query_auth_types_finish (CamelService *service,
2353                                        GAsyncResult *result,
2354                                        GError **error)
2355 {
2356         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
2357         g_return_val_if_fail (g_task_is_valid (result, service), NULL);
2358
2359         g_return_val_if_fail (
2360                 g_async_result_is_tagged (
2361                 result, camel_service_query_auth_types), NULL);
2362
2363         return g_task_propagate_pointer (G_TASK (result), error);
2364 }
2365