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