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