Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-session.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-session.c : Abstract class for an email session */
3
4 /*
5  * Authors:
6  *  Dan Winship <danw@ximian.com>
7  *  Jeffrey Stedfast <fejj@ximian.com>
8  *  Bertrand Guiheneuf <bertrand@helixcode.com>
9  *
10  * Copyright 1999 - 2003 Ximian, Inc.
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of version 2 of the GNU Lesser General Public
14  * License as published by the Free Software Foundation.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
24  * USA
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31 #include <errno.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <sys/stat.h>
36
37 #include <glib.h>
38 #include <glib/gi18n-lib.h>
39 #include <glib/gstdio.h>
40
41 #include <libedataserver/e-data-server-util.h>
42
43 #include "camel-exception.h"
44 #include "camel-file-utils.h"
45 #include "camel-private.h"
46 #include "camel-session.h"
47 #include "camel-store.h"
48 #include "camel-string-utils.h"
49 #include "camel-transport.h"
50 #include "camel-url.h"
51
52 #define d(x)
53
54 #define CS_CLASS(so) ((CamelSessionClass *)((CamelObject *)so)->klass)
55
56 static CamelService *get_service (CamelSession *session,
57                                   const char *url_string,
58                                   CamelProviderType type,
59                                   CamelException *ex);
60 static char *get_storage_path (CamelSession *session,
61                                CamelService *service,
62                                CamelException *ex);
63
64 static void *session_thread_msg_new(CamelSession *session, CamelSessionThreadOps *ops, unsigned int size);
65 static void session_thread_msg_free(CamelSession *session, CamelSessionThreadMsg *msg);
66 static int session_thread_queue(CamelSession *session, CamelSessionThreadMsg *msg, int flags);
67 static void session_thread_wait(CamelSession *session, int id);
68 static void session_thread_status(CamelSession *session, CamelSessionThreadMsg *msg, const char *text, int pc);
69
70 static void
71 camel_session_init (CamelSession *session)
72 {
73         session->online = TRUE;
74         session->network_state = TRUE;
75         session->priv = g_malloc0(sizeof(*session->priv));
76         
77         session->priv->lock = g_mutex_new();
78         session->priv->thread_lock = g_mutex_new();
79         session->priv->thread_id = 1;
80         session->priv->thread_active = g_hash_table_new(NULL, NULL);
81         session->priv->thread_pool = NULL;
82 }
83
84 static void
85 camel_session_finalise (CamelObject *o)
86 {
87         CamelSession *session = (CamelSession *)o;
88         GThreadPool *thread_pool = session->priv->thread_pool;
89         
90         g_hash_table_destroy(session->priv->thread_active);
91
92         if (thread_pool != NULL) {
93                 /* there should be no unprocessed tasks */
94                 g_assert(g_thread_pool_unprocessed (thread_pool) == 0);
95                 g_thread_pool_free(thread_pool, FALSE, FALSE);
96         }
97
98         g_free(session->storage_path);
99         
100         g_mutex_free(session->priv->lock);
101         g_mutex_free(session->priv->thread_lock);
102         
103         g_free(session->priv);
104 }
105
106 static void
107 camel_session_class_init (CamelSessionClass *camel_session_class)
108 {
109         /* virtual method definition */
110         camel_session_class->get_service = get_service;
111         camel_session_class->get_storage_path = get_storage_path;
112         
113         camel_session_class->thread_msg_new = session_thread_msg_new;
114         camel_session_class->thread_msg_free = session_thread_msg_free;
115         camel_session_class->thread_queue = session_thread_queue;
116         camel_session_class->thread_wait = session_thread_wait;
117         camel_session_class->thread_status = session_thread_status;
118
119         camel_object_class_add_event((CamelObjectClass *)camel_session_class, "online", NULL);
120 }
121
122 CamelType
123 camel_session_get_type (void)
124 {
125         static CamelType camel_session_type = CAMEL_INVALID_TYPE;
126
127         if (camel_session_type == CAMEL_INVALID_TYPE) {
128                 camel_session_type = camel_type_register (
129                         camel_object_get_type (), "CamelSession",
130                         sizeof (CamelSession),
131                         sizeof (CamelSessionClass),
132                         (CamelObjectClassInitFunc) camel_session_class_init,
133                         NULL,
134                         (CamelObjectInitFunc) camel_session_init,
135                         (CamelObjectFinalizeFunc) camel_session_finalise);
136         }
137
138         return camel_session_type;
139 }
140
141 /**
142  * camel_session_construct:
143  * @session: a #CamelSession object to construct
144  * @storage_path: path to a directory the session can use for
145  * persistent storage. (This directory must already exist.)
146  *
147  * Constructs @session.
148  **/
149 void
150 camel_session_construct (CamelSession *session, const char *storage_path)
151 {
152         session->storage_path = g_strdup (storage_path);
153 }
154
155 static CamelService *
156 get_service (CamelSession *session, const char *url_string,
157              CamelProviderType type, CamelException *ex)
158 {
159         CamelURL *url;
160         CamelProvider *provider;
161         CamelService *service;
162         CamelException internal_ex;
163         
164         url = camel_url_new (url_string, ex);
165         if (!url)
166                 return NULL;
167
168         /* We need to look up the provider so we can then lookup
169            the service in the provider's cache */
170         provider = camel_provider_get(url->protocol, ex);
171         if (provider && !provider->object_types[type]) {
172                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
173                                       _("No provider available for protocol `%s'"),
174                                       url->protocol);
175                 provider = NULL;
176         }
177         
178         if (!provider) {
179                 camel_url_free (url);
180                 return NULL;
181         }
182
183         /* If the provider doesn't use paths but the URL contains one,
184          * ignore it.
185          */
186         if (url->path && !CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_PATH))
187                 camel_url_set_path (url, NULL);
188         
189         /* Now look up the service in the provider's cache */
190         service = camel_object_bag_reserve(provider->service_cache[type], url);
191         if (service == NULL) {
192                 service = (CamelService *)camel_object_new (provider->object_types[type]);
193                 camel_exception_init (&internal_ex);
194                 camel_service_construct (service, session, provider, url, &internal_ex);
195                 if (camel_exception_is_set (&internal_ex)) {
196                         camel_exception_xfer (ex, &internal_ex);
197                         camel_object_unref (service);
198                         service = NULL;
199                         camel_object_bag_abort(provider->service_cache[type], url);
200                 } else {
201                         camel_object_bag_add(provider->service_cache[type], url, service);
202                 }
203         }
204
205         camel_url_free (url);
206
207         return service;
208 }
209
210 /**
211  * camel_session_get_service:
212  * @session: a #CamelSession object
213  * @url_string: a #CamelURL describing the service to get
214  * @type: the provider type (#CAMEL_PROVIDER_STORE or
215  * #CAMEL_PROVIDER_TRANSPORT) to get, since some URLs may be able
216  * to specify either type.
217  * @ex: a #CamelException
218  *
219  * This resolves a #CamelURL into a #CamelService, including loading the
220  * provider library for that service if it has not already been loaded.
221  *
222  * Services are cached, and asking for "the same" @url_string multiple
223  * times will return the same CamelService (with its reference count
224  * incremented by one each time). What constitutes "the same" URL
225  * depends in part on the provider.
226  *
227  * Returns the requested #CamelService, or %NULL
228  **/
229 CamelService *
230 camel_session_get_service (CamelSession *session, const char *url_string,
231                            CamelProviderType type, CamelException *ex)
232 {
233         CamelService *service;
234
235         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
236         g_return_val_if_fail (url_string != NULL, NULL);
237
238         CAMEL_SESSION_LOCK (session, lock);
239         service = CS_CLASS (session)->get_service (session, url_string, type, ex);
240         CAMEL_SESSION_UNLOCK (session, lock);
241
242         return service;
243 }
244
245 /**
246  * camel_session_get_service_connected:
247  * @session: a #CamelSession object
248  * @url_string: a #CamelURL describing the service to get
249  * @type: the provider type
250  * @ex: a #CamelException
251  *
252  * This works like #camel_session_get_service, but also ensures that
253  * the returned service will have been successfully connected (via
254  * #camel_service_connect.)
255  *
256  * Returns the requested #CamelService, or %NULL
257  **/
258 CamelService *
259 camel_session_get_service_connected (CamelSession *session,
260                                      const char *url_string,
261                                      CamelProviderType type,
262                                      CamelException *ex)
263 {
264         CamelService *svc;
265
266         svc = camel_session_get_service (session, url_string, type, ex);
267         if (svc == NULL)
268                 return NULL;
269
270         if (svc->status != CAMEL_SERVICE_CONNECTED) {
271                 if (camel_service_connect (svc, ex) == FALSE) {
272                         camel_object_unref (svc);
273                         return NULL;
274                 }
275         }
276
277         return svc;
278 }
279
280
281 static char *
282 get_storage_path (CamelSession *session, CamelService *service, CamelException *ex)
283 {
284         char *path, *p;
285
286         p = camel_service_get_path (service);
287         path = g_strdup_printf ("%s/%s", session->storage_path, p);
288         g_free (p);
289
290 #ifdef G_OS_WIN32 
291         if (g_access (path, F_OK) == 0)
292 #else
293         if (access (path, F_OK) == 0)
294 #endif
295                 return path;
296
297         if (g_mkdir_with_parents (path, S_IRWXU) == -1) {
298                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
299                                       _("Could not create directory %s:\n%s"),
300                                       path, g_strerror (errno));
301                 g_free (path);
302                 return NULL;
303         }
304
305         return path;
306 }
307
308 /**
309  * camel_session_get_storage_path:
310  * @session: a #CamelSession object
311  * @service: a #CamelService
312  * @ex: a #CamelException
313  *
314  * This returns the path to a directory which the service can use for
315  * its own purposes. Data stored there will remain between Evolution
316  * sessions. No code outside of that service should ever touch the
317  * files in this directory. If the directory does not exist, it will
318  * be created.
319  *
320  * Returns the path (which the caller must free), or %NULL if an error
321  * occurs.
322  **/
323 char *
324 camel_session_get_storage_path (CamelSession *session, CamelService *service,
325                                 CamelException *ex)
326 {
327         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
328         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
329
330         return CS_CLASS (session)->get_storage_path (session, service, ex);
331 }
332
333
334 /**
335  * camel_session_get_password:
336  * @session: a #CamelSession object
337  * @service: the #CamelService this query is being made by
338  * @domain: domain of password request.  May be null to use the default.
339  * @prompt: prompt to provide to user
340  * @item: an identifier, unique within this service, for the information
341  * @flags: #CAMEL_SESSION_PASSWORD_REPROMPT, the prompt should force a reprompt
342  * #CAMEL_SESSION_PASSWORD_SECRET, whether the password is secret
343  * #CAMEL_SESSION_PASSWORD_STATIC, the password is remembered externally
344  * @ex: a #CamelException
345  *
346  * This function is used by a #CamelService to ask the application and
347  * the user for a password or other authentication data.
348  *
349  * @service and @item together uniquely identify the piece of data the
350  * caller is concerned with.
351  *
352  * @prompt is a question to ask the user (if the application doesn't
353  * already have the answer cached). If #CAMEL_SESSION_PASSWORD_SECRET
354  * is set, the user's input will not be echoed back.
355  *
356  * If #CAMEL_SESSION_PASSWORD_STATIC is set, it means the password returned
357  * will be stored statically by the caller automatically, for the current
358  * session.
359  * 
360  * The authenticator should set @ex to #CAMEL_EXCEPTION_USER_CANCEL if
361  * the user did not provide the information. The caller must #g_free
362  * the information returned when it is done with it.
363  *
364  * Returns the authentication information or %NULL
365  **/
366 char *
367 camel_session_get_password (CamelSession *session, CamelService *service,
368                             const char *domain, const char *prompt, const char *item,
369                             guint32 flags,
370                             CamelException *ex)
371 {
372         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
373         g_return_val_if_fail (prompt != NULL, NULL);
374         g_return_val_if_fail (item != NULL, NULL);
375         
376         return CS_CLASS (session)->get_password (session, service, domain, prompt, item, flags, ex);
377 }
378
379
380 /**
381  * camel_session_forget_password:
382  * @session: a #CamelSession object
383  * @service: the #CamelService rejecting the password
384  * @item: an identifier, unique within this service, for the information
385  * @ex: a #CamelException
386  *
387  * This function is used by a #CamelService to tell the application
388  * that the authentication information it provided via
389  * #camel_session_get_password was rejected by the service. If the
390  * application was caching this information, it should stop,
391  * and if the service asks for it again, it should ask the user.
392  *
393  * @service and @item identify the rejected authentication information,
394  * as with #camel_session_get_password.
395  **/
396 void
397 camel_session_forget_password (CamelSession *session, CamelService *service,
398                                const char *domain, const char *item, CamelException *ex)
399 {
400         g_return_if_fail (CAMEL_IS_SESSION (session));
401         g_return_if_fail (item != NULL);
402
403         CS_CLASS (session)->forget_password (session, service, domain, item, ex);
404 }
405
406
407 /**
408  * camel_session_alert_user:
409  * @session: a #CamelSession object
410  * @type: the type of alert (info, warning, or error)
411  * @prompt: the message for the user
412  * @cancel: whether or not to provide a "Cancel" option in addition to
413  * an "OK" option.
414  *
415  * Presents the given @prompt to the user, in the style indicated by
416  * @type. If @cancel is %TRUE, the user will be able to accept or
417  * cancel. Otherwise, the message is purely informational.
418  *
419  * Returns %TRUE if the user accepts, %FALSE if they cancel.
420  */
421 gboolean
422 camel_session_alert_user (CamelSession *session, CamelSessionAlertType type,
423                           const char *prompt, gboolean cancel)
424 {
425         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
426         g_return_val_if_fail (prompt != NULL, FALSE);
427
428         return CS_CLASS (session)->alert_user (session, type, prompt, cancel);
429 }
430
431
432 /**
433  * camel_session_is_online:
434  * @session: a #CamelSession object
435  *
436  * Returns whether or not @session is online
437  **/
438 gboolean
439 camel_session_is_online (CamelSession *session)
440 {
441         return session->online;
442 }
443
444
445 /**
446  * camel_session_set_online:
447  * @session: a #CamelSession object
448  * @online: whether or not the session should be online
449  *
450  * Sets the online status of @session to @online.
451  **/
452 void
453 camel_session_set_online (CamelSession *session, gboolean online)
454 {
455         session->online = online;
456
457         camel_object_trigger_event(session, "online", GINT_TO_POINTER(online));
458 }
459
460 /**
461  * camel_session_get_filter_driver:
462  * @session: a #CamelSession object
463  * @type: the type of filter (eg, "incoming")
464  * @ex: a #CamelException
465  *
466  * Returns a filter driver, loaded with applicable rules
467  **/
468 CamelFilterDriver *
469 camel_session_get_filter_driver (CamelSession *session,
470                                  const char *type,
471                                  CamelException *ex)
472 {
473         return CS_CLASS (session)->get_filter_driver (session, type, ex);
474 }
475
476 static void
477 cs_thread_status(CamelOperation *op, const char *what, int pc, void *data)
478 {
479         CamelSessionThreadMsg *m = data;
480
481         CS_CLASS(m->session)->thread_status(m->session, m, what, pc);
482 }
483
484 static void *session_thread_msg_new(CamelSession *session, CamelSessionThreadOps *ops, unsigned int size)
485 {
486         CamelSessionThreadMsg *m;
487
488         g_assert(size >= sizeof(*m));
489
490         m = g_malloc0(size);
491         m->ops = ops;
492         m->session = session;
493         camel_object_ref(session);
494         m->op = camel_operation_new(cs_thread_status, m);
495         camel_exception_init(&m->ex);
496         CAMEL_SESSION_LOCK(session, thread_lock);
497         m->id = session->priv->thread_id++;
498         g_hash_table_insert(session->priv->thread_active, GINT_TO_POINTER(m->id), m);
499         CAMEL_SESSION_UNLOCK(session, thread_lock);
500
501         return m;
502 }
503
504 static void session_thread_msg_free(CamelSession *session, CamelSessionThreadMsg *msg)
505 {
506         g_assert(msg->ops != NULL);
507
508         d(printf("free message %p session %p\n", msg, session));
509
510         CAMEL_SESSION_LOCK(session, thread_lock);
511         g_hash_table_remove(session->priv->thread_active, GINT_TO_POINTER(msg->id));
512         CAMEL_SESSION_UNLOCK(session, thread_lock);
513
514         d(printf("free msg, ops->free = %p\n", msg->ops->free));
515         
516         if (msg->ops->free)
517                 msg->ops->free(session, msg);
518         if (msg->op)
519                 camel_operation_unref(msg->op);
520         camel_exception_clear(&msg->ex);
521         camel_object_unref(msg->session);
522         g_free(msg);
523 }
524
525 static void
526 session_thread_proxy(CamelSessionThreadMsg *msg, CamelSession *session)
527 {
528         if (msg->ops->receive) {
529                 CamelOperation *oldop;
530
531                 oldop = camel_operation_register(msg->op);
532                 msg->ops->receive(session, msg);
533                 camel_operation_register(oldop);
534         }
535
536         camel_session_thread_msg_free(session, msg);
537 }
538
539 static int session_thread_queue(CamelSession *session, CamelSessionThreadMsg *msg, int flags)
540 {
541         GThreadPool *thread_pool;
542         int id;
543
544         CAMEL_SESSION_LOCK(session, thread_lock);
545         thread_pool = session->priv->thread_pool;
546         if (thread_pool == NULL) {
547                 thread_pool = g_thread_pool_new (
548                         (GFunc) session_thread_proxy,
549                         session, 1, FALSE, NULL);
550                 session->priv->thread_pool = thread_pool;
551         }
552         CAMEL_SESSION_UNLOCK(session, thread_lock);
553
554         id = msg->id;
555         g_thread_pool_push(thread_pool, msg, NULL);
556
557         return id;
558 }
559
560 static void session_thread_wait(CamelSession *session, int id)
561 {
562         int wait;
563
564         /* we just busy wait, only other alternative is to setup a reply port? */
565         do {
566                 CAMEL_SESSION_LOCK(session, thread_lock);
567                 wait = g_hash_table_lookup(session->priv->thread_active, GINT_TO_POINTER(id)) != NULL;
568                 CAMEL_SESSION_UNLOCK(session, thread_lock);
569                 if (wait) {
570                         g_usleep(20000);
571                 }
572         } while (wait);
573 }
574
575 static void session_thread_status(CamelSession *session, CamelSessionThreadMsg *msg, const char *text, int pc)
576 {
577 }
578
579 /**
580  * camel_session_thread_msg_new:
581  * @session: a #CamelSession object
582  * @ops: thread operations
583  * @size: number of bytes
584  * 
585  * Create a new thread message, using ops as the receive/reply/free
586  * ops, of @size bytes.
587  *
588  * @ops points to the operations used to recieve/process and finally
589  * free the message.
590  *
591  * Returns a new #CamelSessionThreadMsg
592  **/
593 void *
594 camel_session_thread_msg_new(CamelSession *session, CamelSessionThreadOps *ops, unsigned int size)
595 {
596         g_assert(CAMEL_IS_SESSION(session));
597         g_assert(ops != NULL);
598         g_assert(size >= sizeof(CamelSessionThreadMsg));
599                  
600         return CS_CLASS (session)->thread_msg_new(session, ops, size);
601 }
602
603 /**
604  * camel_session_thread_msg_free:
605  * @session: a #CamelSession object
606  * @msg: a #CamelSessionThreadMsg
607  * 
608  * Free a @msg.  Note that the message must have been allocated using
609  * msg_new, and must nto have been submitted to any queue function.
610  **/
611 void
612 camel_session_thread_msg_free(CamelSession *session, CamelSessionThreadMsg *msg)
613 {
614         g_assert(CAMEL_IS_SESSION(session));
615         g_assert(msg != NULL);
616         g_assert(msg->ops != NULL);
617
618         CS_CLASS (session)->thread_msg_free(session, msg);
619 }
620
621 /**
622  * camel_session_thread_queue:
623  * @session: a #CamelSession object
624  * @msg: a #CamelSessionThreadMsg
625  * @flags: queue type flags, currently 0.
626  * 
627  * Queue a thread message in another thread for processing.
628  * The operation should be (but needn't) run in a queued manner
629  * with other operations queued in this manner.
630  * 
631  * Returns the id of the operation queued
632  **/
633 int
634 camel_session_thread_queue(CamelSession *session, CamelSessionThreadMsg *msg, int flags)
635 {
636         g_assert(CAMEL_IS_SESSION(session));
637         g_assert(msg != NULL);
638
639         return CS_CLASS (session)->thread_queue(session, msg, flags);
640 }
641
642 /**
643  * camel_session_thread_wait:
644  * @session: a #CamelSession object
645  * @id: id of the operation to wait on
646  * 
647  * Wait on an operation to complete (by id).
648  **/
649 void
650 camel_session_thread_wait(CamelSession *session, int id)
651 {
652         g_assert(CAMEL_IS_SESSION(session));
653         
654         if (id == -1)
655                 return;
656
657         CS_CLASS (session)->thread_wait(session, id);
658 }
659
660 /**
661  * camel_session_check_junk:
662  * @session: a #CamelSession object
663  * 
664  * Do we have to check incoming messages to be junk?
665  *
666  * Returns whether or not we are checking incoming messages for junk
667  **/
668 gboolean
669 camel_session_check_junk (CamelSession *session)
670 {
671         g_assert(CAMEL_IS_SESSION(session));
672
673         return session->check_junk;
674 }
675
676 /**
677  * camel_session_set_check_junk:
678  * @session: a #CamelSession object
679  * @check_junk: state
680  * 
681  * Set check_junk flag, if set, incoming mail will be checked for being junk.
682  **/
683 void
684 camel_session_set_check_junk (CamelSession *session, gboolean check_junk)
685 {
686         g_assert(CAMEL_IS_SESSION(session));
687
688         session->check_junk = check_junk;
689 }
690
691 gboolean
692 camel_session_get_network_state (CamelSession *session)
693 {
694         g_return_val_if_fail (CAMEL_IS_SESSION(session), FALSE);
695         
696         return session->network_state;
697 }
698
699 void
700 camel_session_set_network_state (CamelSession *session, gboolean network_state)
701 {
702         g_return_if_fail (CAMEL_IS_SESSION(session));
703         
704         session->network_state = network_state;
705 }