Bug #652914 - IMAP: moving mail puts a copy into real Trash too
[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 <libedataserver/e-data-server-util.h>
39
40 #include "camel-debug.h"
41 #include "camel-operation.h"
42 #include "camel-service.h"
43 #include "camel-session.h"
44
45 #define d(x)
46 #define w(x)
47
48 #define CAMEL_SERVICE_GET_PRIVATE(obj) \
49         (G_TYPE_INSTANCE_GET_PRIVATE \
50         ((obj), CAMEL_TYPE_SERVICE, CamelServicePrivate))
51
52 typedef struct _AsyncContext AsyncContext;
53
54 struct _CamelServicePrivate {
55         gpointer session;  /* weak pointer */
56
57         CamelSettings *settings;
58         CamelProvider *provider;
59         CamelURL *url;
60
61         gchar *display_name;
62         gchar *user_data_dir;
63         gchar *uid;
64
65         GCancellable *connect_op;
66         CamelServiceConnectionStatus status;
67
68         GStaticRecMutex connect_lock;   /* for locking connection operations */
69         GStaticMutex connect_op_lock;   /* for locking the connection_op */
70 };
71
72 struct _AsyncContext {
73         GList *auth_types;
74 };
75
76 enum {
77         PROP_0,
78         PROP_DISPLAY_NAME,
79         PROP_PROVIDER,
80         PROP_SESSION,
81         PROP_SETTINGS,
82         PROP_UID,
83         PROP_URL
84 };
85
86 /* Forward Declarations */
87 static void camel_service_initable_init (GInitableIface *interface);
88
89 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
90         CamelService, camel_service, CAMEL_TYPE_OBJECT,
91         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, camel_service_initable_init))
92
93 static void
94 async_context_free (AsyncContext *async_context)
95 {
96         g_list_free (async_context->auth_types);
97
98         g_slice_free (AsyncContext, async_context);
99 }
100
101 static gchar *
102 service_find_old_data_dir (CamelService *service)
103 {
104         CamelProvider *provider;
105         CamelSession *session;
106         CamelURL *url;
107         GString *path;
108         gboolean allows_host;
109         gboolean allows_user;
110         gboolean needs_host;
111         gboolean needs_path;
112         gboolean needs_user;
113         const gchar *base_dir;
114         gchar *old_data_dir;
115
116         provider = camel_service_get_provider (service);
117         session = camel_service_get_session (service);
118         url = camel_service_get_camel_url (service);
119
120         allows_host = CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_HOST);
121         allows_user = CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_USER);
122
123         needs_host = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST);
124         needs_path = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH);
125         needs_user = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER);
126
127         /* This function reproduces the way service data directories used
128          * to be determined before we moved to just using the UID.  If the
129          * old data directory exists, try renaming it to the new form.
130          *
131          * A virtual class method was used to determine the directory path,
132          * but no known CamelProviders ever overrode the default algorithm
133          * below.  So this should work for everyone. */
134
135         path = g_string_new (provider->protocol);
136
137         if (allows_user) {
138                 g_string_append_c (path, '/');
139                 if (url->user != NULL)
140                         g_string_append (path, url->user);
141                 if (allows_host) {
142                         g_string_append_c (path, '@');
143                         if (url->host != NULL)
144                                 g_string_append (path, url->host);
145                         if (url->port) {
146                                 g_string_append_c (path, ':');
147                                 g_string_append_printf (path, "%d", url->port);
148                         }
149                 } else if (!needs_user) {
150                         g_string_append_c (path, '@');
151                 }
152
153         } else if (allows_host) {
154                 g_string_append_c (path, '/');
155                 if (!needs_host)
156                         g_string_append_c (path, '@');
157                 if (url->host != NULL)
158                         g_string_append (path, url->host);
159                 if (url->port) {
160                         g_string_append_c (path, ':');
161                         g_string_append_printf (path, "%d", url->port);
162                 }
163         }
164
165         if (needs_path) {
166                 if (*url->path != '/')
167                         g_string_append_c (path, '/');
168                 g_string_append (path, url->path);
169         }
170
171         base_dir = camel_session_get_user_data_dir (session);
172         old_data_dir = g_build_filename (base_dir, path->str, NULL);
173
174         g_string_free (path, TRUE);
175
176         if (!g_file_test (old_data_dir, G_FILE_TEST_IS_DIR)) {
177                 g_free (old_data_dir);
178                 old_data_dir = NULL;
179         }
180
181         return old_data_dir;
182 }
183
184 static void
185 service_set_provider (CamelService *service,
186                       CamelProvider *provider)
187 {
188         g_return_if_fail (provider != NULL);
189         g_return_if_fail (service->priv->provider == NULL);
190
191         service->priv->provider = provider;
192 }
193
194 static void
195 service_set_session (CamelService *service,
196                      CamelSession *session)
197 {
198         g_return_if_fail (CAMEL_IS_SESSION (session));
199         g_return_if_fail (service->priv->session == NULL);
200
201         service->priv->session = session;
202
203         g_object_add_weak_pointer (
204                 G_OBJECT (session), &service->priv->session);
205 }
206
207 static void
208 service_set_uid (CamelService *service,
209                  const gchar *uid)
210 {
211         g_return_if_fail (uid != NULL);
212         g_return_if_fail (service->priv->uid == NULL);
213
214         service->priv->uid = g_strdup (uid);
215 }
216
217 static void
218 service_set_url (CamelService *service,
219                  CamelURL *url)
220 {
221         g_return_if_fail (url != NULL);
222         g_return_if_fail (service->priv->url == NULL);
223
224         service->priv->url = camel_url_copy (url);
225 }
226
227 static void
228 service_set_property (GObject *object,
229                       guint property_id,
230                       const GValue *value,
231                       GParamSpec *pspec)
232 {
233         switch (property_id) {
234                 case PROP_DISPLAY_NAME:
235                         camel_service_set_display_name (
236                                 CAMEL_SERVICE (object),
237                                 g_value_get_string (value));
238                         return;
239
240                 case PROP_PROVIDER:
241                         service_set_provider (
242                                 CAMEL_SERVICE (object),
243                                 g_value_get_pointer (value));
244                         return;
245
246                 case PROP_SESSION:
247                         service_set_session (
248                                 CAMEL_SERVICE (object),
249                                 g_value_get_object (value));
250                         return;
251
252                 case PROP_SETTINGS:
253                         camel_service_set_settings (
254                                 CAMEL_SERVICE (object),
255                                 g_value_get_object (value));
256                         return;
257
258                 case PROP_UID:
259                         service_set_uid (
260                                 CAMEL_SERVICE (object),
261                                 g_value_get_string (value));
262                         return;
263
264                 case PROP_URL:
265                         service_set_url (
266                                 CAMEL_SERVICE (object),
267                                 g_value_get_boxed (value));
268                         return;
269         }
270
271         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
272 }
273
274 static void
275 service_get_property (GObject *object,
276                       guint property_id,
277                       GValue *value,
278                       GParamSpec *pspec)
279 {
280         switch (property_id) {
281                 case PROP_DISPLAY_NAME:
282                         g_value_set_string (
283                                 value, camel_service_get_display_name (
284                                 CAMEL_SERVICE (object)));
285                         return;
286
287                 case PROP_PROVIDER:
288                         g_value_set_pointer (
289                                 value, camel_service_get_provider (
290                                 CAMEL_SERVICE (object)));
291                         return;
292
293                 case PROP_SESSION:
294                         g_value_set_object (
295                                 value, camel_service_get_session (
296                                 CAMEL_SERVICE (object)));
297                         return;
298
299                 case PROP_SETTINGS:
300                         g_value_set_object (
301                                 value, camel_service_get_settings (
302                                 CAMEL_SERVICE (object)));
303                         return;
304
305                 case PROP_UID:
306                         g_value_set_string (
307                                 value, camel_service_get_uid (
308                                 CAMEL_SERVICE (object)));
309                         return;
310
311                 case PROP_URL:
312                         g_value_set_boxed (
313                                 value, camel_service_get_url (
314                                 CAMEL_SERVICE (object)));
315                         return;
316         }
317
318         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
319 }
320
321 static void
322 service_dispose (GObject *object)
323 {
324         CamelServicePrivate *priv;
325
326         priv = CAMEL_SERVICE_GET_PRIVATE (object);
327
328         if (priv->session != NULL) {
329                 g_object_remove_weak_pointer (
330                         G_OBJECT (priv->session), &priv->session);
331                 priv->session = NULL;
332         }
333
334         if (priv->settings != NULL) {
335                 g_object_unref (priv->settings);
336                 priv->settings = NULL;
337         }
338
339         /* Chain up to parent's dispose() method. */
340         G_OBJECT_CLASS (camel_service_parent_class)->dispose (object);
341 }
342
343 static void
344 service_finalize (GObject *object)
345 {
346         CamelServicePrivate *priv;
347
348         priv = CAMEL_SERVICE_GET_PRIVATE (object);
349
350         if (priv->status == CAMEL_SERVICE_CONNECTED)
351                 CAMEL_SERVICE_GET_CLASS (object)->disconnect_sync (
352                         CAMEL_SERVICE (object), TRUE, NULL, NULL);
353
354         if (priv->url != NULL)
355                 camel_url_free (priv->url);
356
357         g_free (priv->display_name);
358         g_free (priv->user_data_dir);
359         g_free (priv->uid);
360
361         g_static_rec_mutex_free (&priv->connect_lock);
362         g_static_mutex_free (&priv->connect_op_lock);
363
364         /* Chain up to parent's finalize() method. */
365         G_OBJECT_CLASS (camel_service_parent_class)->finalize (object);
366 }
367
368 static void
369 service_constructed (GObject *object)
370 {
371         CamelService *service;
372         CamelSession *session;
373         const gchar *base_data_dir;
374         const gchar *uid;
375
376         /* Chain up to parent's constructed() method. */
377         G_OBJECT_CLASS (camel_service_parent_class)->constructed (object);
378
379         service = CAMEL_SERVICE (object);
380         session = camel_service_get_session (service);
381
382         uid = camel_service_get_uid (service);
383         base_data_dir = camel_session_get_user_data_dir (session);
384
385         service->priv->user_data_dir =
386                 g_build_filename (base_data_dir, uid, NULL);
387 }
388
389 static gchar *
390 service_get_name (CamelService *service,
391                   gboolean brief)
392 {
393         g_warning (
394                 "%s does not implement CamelServiceClass::get_name()",
395                 G_OBJECT_TYPE_NAME (service));
396
397         return g_strdup (G_OBJECT_TYPE_NAME (service));
398 }
399
400 static void
401 service_cancel_connect (CamelService *service)
402 {
403         g_cancellable_cancel (service->priv->connect_op);
404 }
405
406 static gboolean
407 service_connect_sync (CamelService *service,
408                       GCancellable *cancellable,
409                       GError **error)
410 {
411         /* Things like the CamelMboxStore can validly
412          * not define a connect function. */
413          return TRUE;
414 }
415
416 static gboolean
417 service_disconnect_sync (CamelService *service,
418                          gboolean clean,
419                          GCancellable *cancellable,
420                          GError **error)
421 {
422         /* We let people get away with not having a disconnect
423          * function -- CamelMboxStore, for example. */
424         return TRUE;
425 }
426
427 static GList *
428 service_query_auth_types_sync (CamelService *service,
429                                GCancellable *cancellable,
430                                GError **error)
431 {
432         return NULL;
433 }
434
435 static void
436 service_query_auth_types_thread (GSimpleAsyncResult *simple,
437                                  GObject *object,
438                                  GCancellable *cancellable)
439 {
440         AsyncContext *async_context;
441         GError *error = NULL;
442
443         async_context = g_simple_async_result_get_op_res_gpointer (simple);
444
445         async_context->auth_types = camel_service_query_auth_types_sync (
446                 CAMEL_SERVICE (object), cancellable, &error);
447
448         if (error != NULL) {
449                 g_simple_async_result_set_from_error (simple, error);
450                 g_error_free (error);
451         }
452 }
453
454 static void
455 service_query_auth_types (CamelService *service,
456                           gint io_priority,
457                           GCancellable *cancellable,
458                           GAsyncReadyCallback callback,
459                           gpointer user_data)
460 {
461         GSimpleAsyncResult *simple;
462         AsyncContext *async_context;
463
464         async_context = g_slice_new0 (AsyncContext);
465
466         simple = g_simple_async_result_new (
467                 G_OBJECT (service), callback,
468                 user_data, service_query_auth_types);
469
470         g_simple_async_result_set_op_res_gpointer (
471                 simple, async_context, (GDestroyNotify) async_context_free);
472
473         g_simple_async_result_run_in_thread (
474                 simple, service_query_auth_types_thread,
475                 io_priority, cancellable);
476
477         g_object_unref (simple);
478 }
479
480 static GList *
481 service_query_auth_types_finish (CamelService *service,
482                                  GAsyncResult *result,
483                                  GError **error)
484 {
485         GSimpleAsyncResult *simple;
486         AsyncContext *async_context;
487
488         g_return_val_if_fail (
489                 g_simple_async_result_is_valid (
490                 result, G_OBJECT (service),
491                 service_query_auth_types), NULL);
492
493         simple = G_SIMPLE_ASYNC_RESULT (result);
494         async_context = g_simple_async_result_get_op_res_gpointer (simple);
495
496         if (g_simple_async_result_propagate_error (simple, error))
497                 return NULL;
498
499         return g_list_copy (async_context->auth_types);
500 }
501
502 static gboolean
503 service_initable_init (GInitable *initable,
504                        GCancellable *cancellable,
505                        GError **error)
506 {
507         CamelProvider *provider;
508         CamelService *service;
509         CamelURL *url;
510         gboolean success = FALSE;
511         const gchar *new_data_dir;
512         gchar *old_data_dir;
513         gchar *url_string;
514
515         service = CAMEL_SERVICE (initable);
516         url = camel_service_get_camel_url (service);
517         provider = camel_service_get_provider (service);
518
519         url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
520
521         if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER)) {
522                 if (url->user == NULL || *url->user == '\0') {
523                         g_set_error (
524                                 error, CAMEL_SERVICE_ERROR,
525                                 CAMEL_SERVICE_ERROR_URL_INVALID,
526                                 _("URL '%s' needs a user component"),
527                                 url_string);
528                         goto exit;
529                 }
530         }
531
532         if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST)) {
533                 if (url->host == NULL || *url->host == '\0') {
534                         g_set_error (
535                                 error, CAMEL_SERVICE_ERROR,
536                                 CAMEL_SERVICE_ERROR_URL_INVALID,
537                                 _("URL '%s' needs a host component"),
538                                 url_string);
539                         goto exit;
540                 }
541         }
542
543         if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH)) {
544                 if (url->path == NULL || *url->path == '\0') {
545                         g_set_error (
546                                 error, CAMEL_SERVICE_ERROR,
547                                 CAMEL_SERVICE_ERROR_URL_INVALID,
548                                 _("URL '%s' needs a path component"),
549                                 url_string);
550                         goto exit;
551                 }
552         }
553
554         new_data_dir = camel_service_get_user_data_dir (service);
555         old_data_dir = service_find_old_data_dir (service);
556
557         /* If the old data directory name exists, try renaming
558          * it to the new data directory.  Failure is non-fatal. */
559         if (old_data_dir != NULL) {
560                 g_rename (old_data_dir, new_data_dir);
561                 g_free (old_data_dir);
562         }
563
564         success = TRUE;
565
566 exit:
567         g_free (url_string);
568
569         return success;
570 }
571
572 static void
573 camel_service_class_init (CamelServiceClass *class)
574 {
575         GObjectClass *object_class;
576
577         g_type_class_add_private (class, sizeof (CamelServicePrivate));
578
579         object_class = G_OBJECT_CLASS (class);
580         object_class->set_property = service_set_property;
581         object_class->get_property = service_get_property;
582         object_class->dispose = service_dispose;
583         object_class->finalize = service_finalize;
584         object_class->constructed = service_constructed;
585
586         class->settings_type = CAMEL_TYPE_SETTINGS;
587         class->get_name = service_get_name;
588         class->cancel_connect = service_cancel_connect;
589         class->connect_sync = service_connect_sync;
590         class->disconnect_sync = service_disconnect_sync;
591         class->query_auth_types_sync = service_query_auth_types_sync;
592
593         class->query_auth_types = service_query_auth_types;
594         class->query_auth_types_finish = service_query_auth_types_finish;
595
596         g_object_class_install_property (
597                 object_class,
598                 PROP_DISPLAY_NAME,
599                 g_param_spec_string (
600                         "display-name",
601                         "Display Name",
602                         "The display name for the service",
603                         NULL,
604                         G_PARAM_READWRITE |
605                         G_PARAM_CONSTRUCT |
606                         G_PARAM_STATIC_STRINGS));
607
608         g_object_class_install_property (
609                 object_class,
610                 PROP_PROVIDER,
611                 g_param_spec_pointer (
612                         "provider",
613                         "Provider",
614                         "The CamelProvider for the service",
615                         G_PARAM_READWRITE |
616                         G_PARAM_CONSTRUCT_ONLY |
617                         G_PARAM_STATIC_STRINGS));
618
619         g_object_class_install_property (
620                 object_class,
621                 PROP_SESSION,
622                 g_param_spec_object (
623                         "session",
624                         "Session",
625                         "A CamelSession instance",
626                         CAMEL_TYPE_SESSION,
627                         G_PARAM_READWRITE |
628                         G_PARAM_CONSTRUCT_ONLY |
629                         G_PARAM_STATIC_STRINGS));
630
631         g_object_class_install_property (
632                 object_class,
633                 PROP_SETTINGS,
634                 g_param_spec_object (
635                         "settings",
636                         "Settings",
637                         "A CamelSettings instance",
638                         CAMEL_TYPE_SETTINGS,
639                         G_PARAM_READWRITE |
640                         G_PARAM_CONSTRUCT |
641                         G_PARAM_STATIC_STRINGS));
642
643         g_object_class_install_property (
644                 object_class,
645                 PROP_UID,
646                 g_param_spec_string (
647                         "uid",
648                         "UID",
649                         "The unique identity of the service",
650                         NULL,
651                         G_PARAM_READWRITE |
652                         G_PARAM_CONSTRUCT_ONLY |
653                         G_PARAM_STATIC_STRINGS));
654
655         g_object_class_install_property (
656                 object_class,
657                 PROP_URL,
658                 g_param_spec_boxed (
659                         "url",
660                         "URL",
661                         "The CamelURL for the service",
662                         CAMEL_TYPE_URL,
663                         G_PARAM_READWRITE |
664                         G_PARAM_CONSTRUCT_ONLY |
665                         G_PARAM_STATIC_STRINGS));
666 }
667
668 static void
669 camel_service_initable_init (GInitableIface *interface)
670 {
671         interface->init = service_initable_init;
672 }
673
674 static void
675 camel_service_init (CamelService *service)
676 {
677         service->priv = CAMEL_SERVICE_GET_PRIVATE (service);
678
679         service->priv->status = CAMEL_SERVICE_DISCONNECTED;
680
681         g_static_rec_mutex_init (&service->priv->connect_lock);
682         g_static_mutex_init (&service->priv->connect_op_lock);
683 }
684
685 GQuark
686 camel_service_error_quark (void)
687 {
688         static GQuark quark = 0;
689
690         if (G_UNLIKELY (quark == 0)) {
691                 const gchar *string = "camel-service-error-quark";
692                 quark = g_quark_from_static_string (string);
693         }
694
695         return quark;
696 }
697
698 /**
699  * camel_service_cancel_connect:
700  * @service: a #CamelService
701  *
702  * If @service is currently attempting to connect to or disconnect
703  * from a server, this causes it to stop and fail. Otherwise it is a
704  * no-op.
705  **/
706 void
707 camel_service_cancel_connect (CamelService *service)
708 {
709         CamelServiceClass *class;
710
711         g_return_if_fail (CAMEL_IS_SERVICE (service));
712
713         class = CAMEL_SERVICE_GET_CLASS (service);
714         g_return_if_fail (class->cancel_connect != NULL);
715
716         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
717         if (service->priv->connect_op)
718                 class->cancel_connect (service);
719         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
720 }
721
722 /**
723  * camel_service_get_display_name:
724  * @service: a #CamelService
725  *
726  * Returns the display name for @service, or %NULL if @service has not
727  * been given a display name.  The display name is intended for use in
728  * a user interface and should generally be given a user-defined name.
729  *
730  * Compare this with camel_service_get_name(), which returns a built-in
731  * description of the type of service (IMAP, SMTP, etc.).
732  *
733  * Returns: the display name for @service, or %NULL
734  *
735  * Since: 3.2
736  **/
737 const gchar *
738 camel_service_get_display_name (CamelService *service)
739 {
740         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
741
742         return service->priv->display_name;
743 }
744
745 /**
746  * camel_service_set_display_name:
747  * @service: a #CamelService
748  * @display_name: a valid UTF-8 string, or %NULL
749  *
750  * Assigns a UTF-8 display name to @service.  The display name is intended
751  * for use in a user interface and should generally be given a user-defined
752  * name.
753  *
754  * Compare this with camel_service_get_name(), which returns a built-in
755  * description of the type of service (IMAP, SMTP, etc.).
756  *
757  * Since: 3.2
758  **/
759 void
760 camel_service_set_display_name (CamelService *service,
761                                 const gchar *display_name)
762 {
763         g_return_if_fail (CAMEL_IS_SERVICE (service));
764
765         if (display_name != NULL)
766                 g_return_if_fail (g_utf8_validate (display_name, -1, NULL));
767
768         g_free (service->priv->display_name);
769         service->priv->display_name = g_strdup (display_name);
770
771         g_object_notify (G_OBJECT (service), "display-name");
772 }
773
774 /**
775  * camel_service_get_user_data_dir:
776  * @service: a #CamelService
777  *
778  * Returns the base directory under which to store user-specific data
779  * for @service.  The directory is formed by appending the directory
780  * returned by camel_session_get_user_data_dir() with the service's
781  * #CamelService:uid value.
782  *
783  * Returns: the base directory for @service
784  *
785  * Since: 3.2
786  **/
787 const gchar *
788 camel_service_get_user_data_dir (CamelService *service)
789 {
790         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
791
792         return service->priv->user_data_dir;
793 }
794
795 /**
796  * camel_service_get_name:
797  * @service: a #CamelService
798  * @brief: whether or not to use a briefer form
799  *
800  * This gets the name of the service in a "friendly" (suitable for
801  * humans) form. If @brief is %TRUE, this should be a brief description
802  * such as for use in the folder tree. If @brief is %FALSE, it should
803  * be a more complete and mostly unambiguous description.
804  *
805  * Returns: a description of the service which the caller must free
806  **/
807 gchar *
808 camel_service_get_name (CamelService *service,
809                         gboolean brief)
810 {
811         CamelServiceClass *class;
812
813         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
814         g_return_val_if_fail (service->priv->url, NULL);
815
816         class = CAMEL_SERVICE_GET_CLASS (service);
817         g_return_val_if_fail (class->get_name != NULL, NULL);
818
819         return class->get_name (service, brief);
820 }
821
822 /**
823  * camel_service_get_provider:
824  * @service: a #CamelService
825  *
826  * Gets the #CamelProvider associated with the service.
827  *
828  * Returns: the #CamelProvider
829  **/
830 CamelProvider *
831 camel_service_get_provider (CamelService *service)
832 {
833         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
834
835         return service->priv->provider;
836 }
837
838 /**
839  * camel_service_get_session:
840  * @service: a #CamelService
841  *
842  * Gets the #CamelSession associated with the service.
843  *
844  * Returns: the #CamelSession
845  **/
846 CamelSession *
847 camel_service_get_session (CamelService *service)
848 {
849         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
850
851         return CAMEL_SESSION (service->priv->session);
852 }
853
854 /**
855  * camel_service_get_settings:
856  * @service: a #CamelService
857  *
858  * Returns the #CamelSettings instance associated with the service.
859  *
860  * Returns: the #CamelSettings
861  *
862  * Since: 3.2
863  **/
864 CamelSettings *
865 camel_service_get_settings (CamelService *service)
866 {
867         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
868
869         /* Every service should have a settings object. */
870         g_warn_if_fail (service->priv->settings != NULL);
871
872         return service->priv->settings;
873 }
874
875 /**
876  * camel_service_set_settings:
877  * @service: a #CamelService
878  * @settings: an instance derviced from #CamelSettings, or %NULL
879  *
880  * Associates a new #CamelSettings instance with the service.
881  * The @settings instance must match the settings type defined in
882  * #CamelServiceClass.  If @settings is %NULL, a new #CamelSettings
883  * instance of the appropriate type is created with all properties
884  * set to defaults.
885  *
886  * Since: 3.2
887  **/
888 void
889 camel_service_set_settings (CamelService *service,
890                             CamelSettings *settings)
891 {
892         CamelServiceClass *class;
893
894         g_return_if_fail (CAMEL_IS_SERVICE (service));
895
896         class = CAMEL_SERVICE_GET_CLASS (service);
897
898         if (settings != NULL) {
899                 g_return_if_fail (
900                         g_type_is_a (
901                                 G_OBJECT_TYPE (settings),
902                                 class->settings_type));
903                 g_object_ref (settings);
904
905         } else {
906                 g_return_if_fail (
907                         g_type_is_a (
908                                 class->settings_type,
909                                 CAMEL_TYPE_SETTINGS));
910                 settings = g_object_new (class->settings_type, NULL);
911                 camel_settings_load_from_url (settings, camel_service_get_camel_url (service));
912         }
913
914         if (service->priv->settings != NULL)
915                 g_object_unref (service->priv->settings);
916
917         service->priv->settings = settings;
918
919         g_object_notify (G_OBJECT (service), "settings");
920 }
921
922 /**
923  * camel_service_get_uid:
924  * @service: a #CamelService
925  *
926  * Gets the unique identifier string associated with the service.
927  *
928  * Returns: the UID string
929  *
930  * Since: 3.2
931  **/
932 const gchar *
933 camel_service_get_uid (CamelService *service)
934 {
935         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
936
937         return service->priv->uid;
938 }
939
940 /**
941  * camel_service_get_camel_url:
942  * @service: a #CamelService
943  *
944  * Returns the #CamelURL representing @service.
945  *
946  * Returns: the #CamelURL representing @service
947  *
948  * Since: 3.2
949  **/
950 CamelURL *
951 camel_service_get_camel_url (CamelService *service)
952 {
953         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
954
955         return service->priv->url;
956 }
957
958 /**
959  * camel_service_get_url:
960  * @service: a #CamelService
961  *
962  * Gets the URL representing @service. The returned URL must be
963  * freed when it is no longer needed. For security reasons, this
964  * routine does not return the password.
965  *
966  * Returns: the URL representing @service
967  **/
968 gchar *
969 camel_service_get_url (CamelService *service)
970 {
971         CamelURL *url;
972
973         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
974
975         url = camel_service_get_camel_url (service);
976
977         return camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
978 }
979
980 /**
981  * camel_service_connect_sync:
982  * @service: a #CamelService
983  * @error: return location for a #GError, or %NULL
984  *
985  * Connect to the service using the parameters it was initialized
986  * with.
987  *
988  * Returns: %TRUE if the connection is made or %FALSE otherwise
989  **/
990 gboolean
991 camel_service_connect_sync (CamelService *service,
992                             GError **error)
993 {
994         CamelServiceClass *class;
995         GCancellable *op;
996         gboolean ret = FALSE;
997
998         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
999         g_return_val_if_fail (service->priv->session != NULL, FALSE);
1000         g_return_val_if_fail (service->priv->url != NULL, FALSE);
1001
1002         class = CAMEL_SERVICE_GET_CLASS (service);
1003         g_return_val_if_fail (class->connect_sync != NULL, FALSE);
1004
1005         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1006
1007         if (service->priv->status == CAMEL_SERVICE_CONNECTED) {
1008                 camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1009                 return TRUE;
1010         }
1011
1012         /* Register a separate operation for connecting, so that
1013          * the offline code can cancel it. */
1014         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
1015         service->priv->connect_op = camel_operation_new ();
1016         op = service->priv->connect_op;
1017         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
1018
1019         service->priv->status = CAMEL_SERVICE_CONNECTING;
1020         ret = class->connect_sync (service, service->priv->connect_op, error);
1021         CAMEL_CHECK_GERROR (service, connect_sync, ret, error);
1022         service->priv->status =
1023                 ret ? CAMEL_SERVICE_CONNECTED : CAMEL_SERVICE_DISCONNECTED;
1024
1025         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
1026         g_object_unref (op);
1027         if (op == service->priv->connect_op)
1028                 service->priv->connect_op = NULL;
1029         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
1030
1031         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1032
1033         return ret;
1034 }
1035
1036 /**
1037  * camel_service_disconnect_sync:
1038  * @service: a #CamelService
1039  * @clean: whether or not to try to disconnect cleanly
1040  * @error: return location for a #GError, or %NULL
1041  *
1042  * Disconnect from the service. If @clean is %FALSE, it should not
1043  * try to do any synchronizing or other cleanup of the connection.
1044  *
1045  * Returns: %TRUE if the disconnect was successful or %FALSE otherwise
1046  **/
1047 gboolean
1048 camel_service_disconnect_sync (CamelService *service,
1049                                gboolean clean,
1050                                GError **error)
1051 {
1052         CamelServiceClass *class;
1053         gboolean res = TRUE;
1054
1055         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
1056
1057         class = CAMEL_SERVICE_GET_CLASS (service);
1058         g_return_val_if_fail (class->disconnect_sync != NULL, FALSE);
1059
1060         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1061
1062         if (service->priv->status != CAMEL_SERVICE_DISCONNECTED
1063             && service->priv->status != CAMEL_SERVICE_DISCONNECTING) {
1064                 GCancellable *op;
1065                 camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
1066                 service->priv->connect_op = camel_operation_new ();
1067                 op = service->priv->connect_op;
1068                 camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
1069
1070                 service->priv->status = CAMEL_SERVICE_DISCONNECTING;
1071                 res = class->disconnect_sync (
1072                         service, clean, service->priv->connect_op, error);
1073                 CAMEL_CHECK_GERROR (service, disconnect_sync, res, error);
1074                 service->priv->status = CAMEL_SERVICE_DISCONNECTED;
1075
1076                 camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
1077                 g_object_unref (op);
1078                 if (op == service->priv->connect_op)
1079                         service->priv->connect_op = NULL;
1080                 camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
1081         }
1082
1083         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1084
1085         service->priv->status = CAMEL_SERVICE_DISCONNECTED;
1086
1087         return res;
1088 }
1089
1090 /**
1091  * camel_service_get_connection_status:
1092  * @service: a #CamelService
1093  *
1094  * Returns the connection status for @service.
1095  *
1096  * Returns: the connection status
1097  *
1098  * Since: 3.2
1099  **/
1100 CamelServiceConnectionStatus
1101 camel_service_get_connection_status (CamelService *service)
1102 {
1103         g_return_val_if_fail (
1104                 CAMEL_IS_SERVICE (service), CAMEL_SERVICE_DISCONNECTED);
1105
1106         return service->priv->status;
1107 }
1108
1109 /**
1110  * camel_service_lock:
1111  * @service: a #CamelService
1112  * @lock: lock type to lock
1113  *
1114  * Locks @service's @lock. Unlock it with camel_service_unlock().
1115  *
1116  * Since: 2.32
1117  **/
1118 void
1119 camel_service_lock (CamelService *service,
1120                     CamelServiceLock lock)
1121 {
1122         g_return_if_fail (CAMEL_IS_SERVICE (service));
1123
1124         switch (lock) {
1125                 case CAMEL_SERVICE_REC_CONNECT_LOCK:
1126                         g_static_rec_mutex_lock (&service->priv->connect_lock);
1127                         break;
1128                 case CAMEL_SERVICE_CONNECT_OP_LOCK:
1129                         g_static_mutex_lock (&service->priv->connect_op_lock);
1130                         break;
1131                 default:
1132                         g_return_if_reached ();
1133         }
1134 }
1135
1136 /**
1137  * camel_service_unlock:
1138  * @service: a #CamelService
1139  * @lock: lock type to unlock
1140  *
1141  * Unlocks @service's @lock, previously locked with camel_service_lock().
1142  *
1143  * Since: 2.32
1144  **/
1145 void
1146 camel_service_unlock (CamelService *service,
1147                       CamelServiceLock lock)
1148 {
1149         g_return_if_fail (CAMEL_IS_SERVICE (service));
1150
1151         switch (lock) {
1152                 case CAMEL_SERVICE_REC_CONNECT_LOCK:
1153                         g_static_rec_mutex_unlock (&service->priv->connect_lock);
1154                         break;
1155                 case CAMEL_SERVICE_CONNECT_OP_LOCK:
1156                         g_static_mutex_unlock (&service->priv->connect_op_lock);
1157                         break;
1158                 default:
1159                         g_return_if_reached ();
1160         }
1161 }
1162
1163 /**
1164  * camel_service_query_auth_types_sync:
1165  * @service: a #CamelService
1166  * @cancellable: optional #GCancellable object, or %NULL
1167  * @error: return location for a #GError, or %NULL
1168  *
1169  * Obtains a list of authentication types supported by @service.
1170  * Free the returned list with g_list_free().
1171  *
1172  * Returns: a list of #CamelServiceAuthType structs
1173  **/
1174 GList *
1175 camel_service_query_auth_types_sync (CamelService *service,
1176                                      GCancellable *cancellable,
1177                                      GError **error)
1178 {
1179         CamelServiceClass *class;
1180         GList *list;
1181
1182         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1183
1184         class = CAMEL_SERVICE_GET_CLASS (service);
1185         g_return_val_if_fail (class->query_auth_types_sync != NULL, NULL);
1186
1187         /* Note that we get the connect lock here, which means the
1188          * callee must not call the connect functions itself. */
1189         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1190         list = class->query_auth_types_sync (service, cancellable, error);
1191         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1192
1193         return list;
1194 }
1195
1196 /**
1197  * camel_service_query_auth_types:
1198  * @service: a #CamelService
1199  * @io_priority: the I/O priority of the request
1200  * @cancellable: optional #GCancellable object, or %NULL
1201  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1202  * @user_data: data to pass to the callback function
1203  *
1204  * Asynchronously obtains a list of authentication types supported by
1205  * @service.
1206  *
1207  * When the operation is finished, @callback will be called.  You can
1208  * then call camel_service_query_auth_types_finish() to get the result
1209  * of the operation.
1210  *
1211  * Since: 3.2
1212  **/
1213 void
1214 camel_service_query_auth_types (CamelService *service,
1215                                 gint io_priority,
1216                                 GCancellable *cancellable,
1217                                 GAsyncReadyCallback callback,
1218                                 gpointer user_data)
1219 {
1220         CamelServiceClass *class;
1221
1222         g_return_if_fail (CAMEL_IS_SERVICE (service));
1223
1224         class = CAMEL_SERVICE_GET_CLASS (service);
1225         g_return_if_fail (class->query_auth_types != NULL);
1226
1227         class->query_auth_types (
1228                 service, io_priority,
1229                 cancellable, callback, user_data);
1230 }
1231
1232 /**
1233  * camel_service_query_auth_types_finish:
1234  * @service: a #CamelService
1235  * @result: a #GAsyncResult
1236  * @error: return location for a #GError, or %NULL
1237  *
1238  * Finishes the operation started with camel_service_query_auth_types().
1239  * Free the returned list with g_list_free().
1240  *
1241  * Returns: a list of #CamelServiceAuthType structs
1242  *
1243  * Since: 3.2
1244  **/
1245 GList *
1246 camel_service_query_auth_types_finish (CamelService *service,
1247                                        GAsyncResult *result,
1248                                        GError **error)
1249 {
1250         CamelServiceClass *class;
1251
1252         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
1253         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
1254
1255         class = CAMEL_SERVICE_GET_CLASS (service);
1256         g_return_val_if_fail (class->query_auth_types_finish != NULL, NULL);
1257
1258         return class->query_auth_types_finish (service, result, error);
1259 }
1260