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