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