Seal up CamelService.
[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/gi18n-lib.h>
36
37 #include "camel-debug.h"
38 #include "camel-operation.h"
39 #include "camel-service.h"
40 #include "camel-session.h"
41
42 #define d(x)
43 #define w(x)
44
45 struct _CamelServicePrivate {
46         CamelSession *session;
47         CamelProvider *provider;
48         CamelURL *url;
49
50         GCancellable *connect_op;
51         CamelServiceConnectionStatus status;
52
53         GStaticRecMutex connect_lock;   /* for locking connection operations */
54         GStaticMutex connect_op_lock;   /* for locking the connection_op */
55 };
56
57 G_DEFINE_ABSTRACT_TYPE (CamelService, camel_service, CAMEL_TYPE_OBJECT)
58
59 static void
60 service_finalize (GObject *object)
61 {
62         CamelService *service = CAMEL_SERVICE (object);
63
64         if (service->priv->status == CAMEL_SERVICE_CONNECTED)
65                 CAMEL_SERVICE_GET_CLASS (service)->disconnect_sync (
66                         service, TRUE, NULL, NULL);
67
68         if (service->priv->url)
69                 camel_url_free (service->priv->url);
70
71         if (service->priv->session)
72                 g_object_unref (service->priv->session);
73
74         g_static_rec_mutex_free (&service->priv->connect_lock);
75         g_static_mutex_free (&service->priv->connect_op_lock);
76
77         /* Chain up to parent's finalize() method. */
78         G_OBJECT_CLASS (camel_service_parent_class)->finalize (object);
79 }
80
81 static gboolean
82 service_construct (CamelService *service,
83                    CamelSession *session,
84                    CamelProvider *provider,
85                    CamelURL *url,
86                    GError **error)
87 {
88         gchar *err, *url_string;
89
90         if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER) &&
91             (url->user == NULL || url->user[0] == '\0')) {
92                 err = _("URL '%s' needs a username component");
93                 goto fail;
94         } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST) &&
95                    (url->host == NULL || url->host[0] == '\0')) {
96                 err = _("URL '%s' needs a host component");
97                 goto fail;
98         } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH) &&
99                    (url->path == NULL || url->path[0] == '\0')) {
100                 err = _("URL '%s' needs a path component");
101                 goto fail;
102         }
103
104         service->priv->provider = provider;
105         service->priv->url = camel_url_copy (url);
106         service->priv->session = g_object_ref (session);
107
108         service->priv->status = CAMEL_SERVICE_DISCONNECTED;
109
110         return TRUE;
111
112 fail:
113         url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
114         g_set_error (
115                 error, CAMEL_SERVICE_ERROR,
116                 CAMEL_SERVICE_ERROR_URL_INVALID,
117                 err, url_string);
118         g_free (url_string);
119
120         return FALSE;
121 }
122
123 static gchar *
124 service_get_name (CamelService *service,
125                   gboolean brief)
126 {
127         g_warning (
128                 "%s does not implement CamelServiceClass::get_name()",
129                 G_OBJECT_TYPE_NAME (service));
130
131         return g_strdup (G_OBJECT_TYPE_NAME (service));
132 }
133
134 static gchar *
135 service_get_path (CamelService *service)
136 {
137         CamelProvider *prov = service->priv->provider;
138         CamelURL *url = service->priv->url;
139         GString *gpath;
140         gchar *path;
141
142         /* A sort of ad-hoc default implementation that works for our
143          * current set of services.
144          */
145
146         gpath = g_string_new (service->priv->provider->protocol);
147         if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_USER)) {
148                 if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
149                         g_string_append_printf (gpath, "/%s@%s",
150                                                 url->user ? url->user : "",
151                                                 url->host ? url->host : "");
152
153                         if (url->port)
154                                 g_string_append_printf (gpath, ":%d", url->port);
155                 } else {
156                         g_string_append_printf (gpath, "/%s%s", url->user ? url->user : "",
157                                                 CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_USER) ? "" : "@");
158                 }
159         } else if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
160                 g_string_append_printf (gpath, "/%s%s",
161                                         CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_HOST) ? "" : "@",
162                                         url->host ? url->host : "");
163
164                 if (url->port)
165                         g_string_append_printf (gpath, ":%d", url->port);
166         }
167
168         if (CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_PATH))
169                 g_string_append_printf (gpath, "%s%s", *url->path == '/' ? "" : "/", url->path);
170
171         path = gpath->str;
172         g_string_free (gpath, FALSE);
173
174         return path;
175 }
176
177 static void
178 service_cancel_connect (CamelService *service)
179 {
180         g_cancellable_cancel (service->priv->connect_op);
181 }
182
183 static gboolean
184 service_connect_sync (CamelService *service,
185                       GCancellable *cancellable,
186                       GError **error)
187 {
188         /* Things like the CamelMboxStore can validly
189          * not define a connect function. */
190          return TRUE;
191 }
192
193 static gboolean
194 service_disconnect_sync (CamelService *service,
195                          gboolean clean,
196                          GCancellable *cancellable,
197                          GError **error)
198 {
199         /* We let people get away with not having a disconnect
200          * function -- CamelMboxStore, for example. */
201         return TRUE;
202 }
203
204 static GList *
205 service_query_auth_types_sync (CamelService *service,
206                                GCancellable *cancellable,
207                                GError **error)
208 {
209         return NULL;
210 }
211
212 static void
213 camel_service_class_init (CamelServiceClass *class)
214 {
215         GObjectClass *object_class;
216
217         g_type_class_add_private (class, sizeof (CamelServicePrivate));
218
219         object_class = G_OBJECT_CLASS (class);
220         object_class->finalize = service_finalize;
221
222         class->construct = service_construct;
223         class->get_name = service_get_name;
224         class->get_path = service_get_path;
225         class->cancel_connect = service_cancel_connect;
226         class->connect_sync = service_connect_sync;
227         class->disconnect_sync = service_disconnect_sync;
228         class->query_auth_types_sync = service_query_auth_types_sync;
229 }
230
231 static void
232 camel_service_init (CamelService *service)
233 {
234         service->priv = G_TYPE_INSTANCE_GET_PRIVATE (
235                 service, CAMEL_TYPE_SERVICE, CamelServicePrivate);
236
237         g_static_rec_mutex_init (&service->priv->connect_lock);
238         g_static_mutex_init (&service->priv->connect_op_lock);
239 }
240
241 GQuark
242 camel_service_error_quark (void)
243 {
244         static GQuark quark = 0;
245
246         if (G_UNLIKELY (quark == 0)) {
247                 const gchar *string = "camel-service-error-quark";
248                 quark = g_quark_from_static_string (string);
249         }
250
251         return quark;
252 }
253
254 /**
255  * camel_service_construct:
256  * @service: a #CamelService
257  * @session: the #CamelSession for @service
258  * @provider: the #CamelProvider associated with @service
259  * @url: the default URL for the service (may be %NULL)
260  * @error: return location for a #GError, or %NULL
261  *
262  * Constructs a #CamelService initialized with the given parameters.
263  *
264  * Returns: %TRUE on success, %FALSE on failure
265  **/
266 gboolean
267 camel_service_construct (CamelService *service,
268                          CamelSession *session,
269                          CamelProvider *provider,
270                          CamelURL *url,
271                          GError **error)
272 {
273         CamelServiceClass *class;
274         gboolean success;
275
276         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
277         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
278
279         class = CAMEL_SERVICE_GET_CLASS (service);
280         g_return_val_if_fail (class->construct != NULL, FALSE);
281
282         success = class->construct (service, session, provider, url, error);
283         CAMEL_CHECK_GERROR (service, construct, success, error);
284
285         return success;
286 }
287
288 /**
289  * camel_service_cancel_connect:
290  * @service: a #CamelService
291  *
292  * If @service is currently attempting to connect to or disconnect
293  * from a server, this causes it to stop and fail. Otherwise it is a
294  * no-op.
295  **/
296 void
297 camel_service_cancel_connect (CamelService *service)
298 {
299         CamelServiceClass *class;
300
301         g_return_if_fail (CAMEL_IS_SERVICE (service));
302
303         class = CAMEL_SERVICE_GET_CLASS (service);
304         g_return_if_fail (class->cancel_connect != NULL);
305
306         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
307         if (service->priv->connect_op)
308                 class->cancel_connect (service);
309         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
310 }
311
312 /**
313  * camel_service_get_name:
314  * @service: a #CamelService
315  * @brief: whether or not to use a briefer form
316  *
317  * This gets the name of the service in a "friendly" (suitable for
318  * humans) form. If @brief is %TRUE, this should be a brief description
319  * such as for use in the folder tree. If @brief is %FALSE, it should
320  * be a more complete and mostly unambiguous description.
321  *
322  * Returns: a description of the service which the caller must free
323  **/
324 gchar *
325 camel_service_get_name (CamelService *service,
326                         gboolean brief)
327 {
328         CamelServiceClass *class;
329
330         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
331         g_return_val_if_fail (service->priv->url, NULL);
332
333         class = CAMEL_SERVICE_GET_CLASS (service);
334         g_return_val_if_fail (class->get_name != NULL, NULL);
335
336         return class->get_name (service, brief);
337 }
338
339 /**
340  * camel_service_get_path:
341  * @service: a #CamelService
342  *
343  * This gets a valid UNIX relative path describing @service, which
344  * is guaranteed to be different from the path returned for any
345  * different service. This path MUST start with the name of the
346  * provider, followed by a "/", but after that, it is up to the
347  * provider.
348  *
349  * Returns: the path, which the caller must free
350  **/
351 gchar *
352 camel_service_get_path (CamelService *service)
353 {
354         CamelServiceClass *class;
355
356         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
357         g_return_val_if_fail (service->priv->url, NULL);
358
359         class = CAMEL_SERVICE_GET_CLASS (service);
360         g_return_val_if_fail (class->get_path != NULL, NULL);
361
362         return class->get_path (service);
363 }
364
365 /**
366  * camel_service_get_session:
367  * @service: a #CamelService
368  *
369  * Gets the #CamelSession associated with the service.
370  *
371  * Returns: the session
372  **/
373 CamelSession *
374 camel_service_get_session (CamelService *service)
375 {
376         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
377
378         return service->priv->session;
379 }
380
381 /**
382  * camel_service_get_provider:
383  * @service: a #CamelService
384  *
385  * Gets the #CamelProvider associated with the service.
386  *
387  * Returns: the provider
388  **/
389 CamelProvider *
390 camel_service_get_provider (CamelService *service)
391 {
392         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
393
394         return service->priv->provider;
395 }
396
397 /**
398  * camel_service_get_camel_url:
399  * @service: a #CamelService
400  *
401  * Returns the #CamelURL representing @service.
402  *
403  * Returns: the #CamelURL representing @service
404  *
405  * Since: 3.2
406  **/
407 CamelURL *
408 camel_service_get_camel_url (CamelService *service)
409 {
410         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
411
412         return service->priv->url;
413 }
414
415 /**
416  * camel_service_get_url:
417  * @service: a #CamelService
418  *
419  * Gets the URL representing @service. The returned URL must be
420  * freed when it is no longer needed. For security reasons, this
421  * routine does not return the password.
422  *
423  * Returns: the URL representing @service
424  **/
425 gchar *
426 camel_service_get_url (CamelService *service)
427 {
428         CamelURL *url;
429
430         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
431
432         url = camel_service_get_camel_url (service);
433
434         return camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
435 }
436
437 /**
438  * camel_service_connect_sync:
439  * @service: a #CamelService
440  * @error: return location for a #GError, or %NULL
441  *
442  * Connect to the service using the parameters it was initialized
443  * with.
444  *
445  * Returns: %TRUE if the connection is made or %FALSE otherwise
446  **/
447 gboolean
448 camel_service_connect_sync (CamelService *service,
449                             GError **error)
450 {
451         CamelServiceClass *class;
452         GCancellable *op;
453         gboolean ret = FALSE;
454
455         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
456         g_return_val_if_fail (service->priv->session != NULL, FALSE);
457         g_return_val_if_fail (service->priv->url != NULL, FALSE);
458
459         class = CAMEL_SERVICE_GET_CLASS (service);
460         g_return_val_if_fail (class->connect_sync != NULL, FALSE);
461
462         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
463
464         if (service->priv->status == CAMEL_SERVICE_CONNECTED) {
465                 camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
466                 return TRUE;
467         }
468
469         /* Register a separate operation for connecting, so that
470          * the offline code can cancel it. */
471         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
472         service->priv->connect_op = camel_operation_new ();
473         op = service->priv->connect_op;
474         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
475
476         service->priv->status = CAMEL_SERVICE_CONNECTING;
477         ret = class->connect_sync (service, service->priv->connect_op, error);
478         CAMEL_CHECK_GERROR (service, connect_sync, ret, error);
479         service->priv->status =
480                 ret ? CAMEL_SERVICE_CONNECTED : CAMEL_SERVICE_DISCONNECTED;
481
482         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
483         g_object_unref (op);
484         if (op == service->priv->connect_op)
485                 service->priv->connect_op = NULL;
486         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
487
488         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
489
490         return ret;
491 }
492
493 /**
494  * camel_service_disconnect_sync:
495  * @service: a #CamelService
496  * @clean: whether or not to try to disconnect cleanly
497  * @error: return location for a #GError, or %NULL
498  *
499  * Disconnect from the service. If @clean is %FALSE, it should not
500  * try to do any synchronizing or other cleanup of the connection.
501  *
502  * Returns: %TRUE if the disconnect was successful or %FALSE otherwise
503  **/
504 gboolean
505 camel_service_disconnect_sync (CamelService *service,
506                                gboolean clean,
507                                GError **error)
508 {
509         CamelServiceClass *class;
510         gboolean res = TRUE;
511
512         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
513
514         class = CAMEL_SERVICE_GET_CLASS (service);
515         g_return_val_if_fail (class->disconnect_sync != NULL, FALSE);
516
517         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
518
519         if (service->priv->status != CAMEL_SERVICE_DISCONNECTED
520             && service->priv->status != CAMEL_SERVICE_DISCONNECTING) {
521                 GCancellable *op;
522                 camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
523                 service->priv->connect_op = camel_operation_new ();
524                 op = service->priv->connect_op;
525                 camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
526
527                 service->priv->status = CAMEL_SERVICE_DISCONNECTING;
528                 res = class->disconnect_sync (
529                         service, clean, service->priv->connect_op, error);
530                 CAMEL_CHECK_GERROR (service, disconnect_sync, res, error);
531                 service->priv->status = CAMEL_SERVICE_DISCONNECTED;
532
533                 camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
534                 g_object_unref (op);
535                 if (op == service->priv->connect_op)
536                         service->priv->connect_op = NULL;
537                 camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
538         }
539
540         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
541
542         service->priv->status = CAMEL_SERVICE_DISCONNECTED;
543
544         return res;
545 }
546
547 /**
548  * camel_service_get_connection_status:
549  * @service: a #CamelService
550  *
551  * Returns the connection status for @service.
552  *
553  * Returns: the connection status
554  *
555  * Since: 3.2
556  **/
557 CamelServiceConnectionStatus
558 camel_service_get_connection_status (CamelService *service)
559 {
560         g_return_val_if_fail (
561                 CAMEL_IS_SERVICE (service), CAMEL_SERVICE_DISCONNECTED);
562
563         return service->priv->status;
564 }
565
566 /**
567  * camel_service_query_auth_types_sync:
568  * @service: a #CamelService
569  * @cancellable: optional #GCancellable object, or %NULL
570  * @error: return location for a #GError, or %NULL
571  *
572  * This is used by the mail source wizard to get the list of
573  * authentication types supported by the protocol, and information
574  * about them.
575  *
576  * Returns: a list of #CamelServiceAuthType records. The caller
577  * must free the list with #g_list_free when it is done with it.
578  **/
579 GList *
580 camel_service_query_auth_types_sync (CamelService *service,
581                                      GCancellable *cancellable,
582                                      GError **error)
583 {
584         CamelServiceClass *class;
585         GList *ret;
586
587         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
588
589         class = CAMEL_SERVICE_GET_CLASS (service);
590         g_return_val_if_fail (class->query_auth_types_sync != NULL, NULL);
591
592         /* Note that we get the connect lock here, which means the
593          * callee must not call the connect functions itself. */
594         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
595         ret = class->query_auth_types_sync (service, cancellable, error);
596         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
597
598         return ret;
599 }
600
601 /**
602  * camel_service_lock:
603  * @service: a #CamelService
604  * @lock: lock type to lock
605  *
606  * Locks #service's #lock. Unlock it with camel_service_unlock().
607  *
608  * Since: 2.32
609  **/
610 void
611 camel_service_lock (CamelService *service,
612                     CamelServiceLock lock)
613 {
614         g_return_if_fail (CAMEL_IS_SERVICE (service));
615
616         switch (lock) {
617                 case CAMEL_SERVICE_REC_CONNECT_LOCK:
618                         g_static_rec_mutex_lock (&service->priv->connect_lock);
619                         break;
620                 case CAMEL_SERVICE_CONNECT_OP_LOCK:
621                         g_static_mutex_lock (&service->priv->connect_op_lock);
622                         break;
623                 default:
624                         g_return_if_reached ();
625         }
626 }
627
628 /**
629  * camel_service_unlock:
630  * @service: a #CamelService
631  * @lock: lock type to unlock
632  *
633  * Unlocks #service's #lock, previously locked with camel_service_lock().
634  *
635  * Since: 2.32
636  **/
637 void
638 camel_service_unlock (CamelService *service,
639                       CamelServiceLock lock)
640 {
641         g_return_if_fail (CAMEL_IS_SERVICE (service));
642
643         switch (lock) {
644                 case CAMEL_SERVICE_REC_CONNECT_LOCK:
645                         g_static_rec_mutex_unlock (&service->priv->connect_lock);
646                         break;
647                 case CAMEL_SERVICE_CONNECT_OP_LOCK:
648                         g_static_mutex_unlock (&service->priv->connect_op_lock);
649                         break;
650                 default:
651                         g_return_if_reached ();
652         }
653 }