1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-session.c : Abstract class for an email session */
6 * Dan Winship <danw@ximian.com>
7 * Jeffrey Stedfast <fejj@ximian.com>
8 * Bertrand Guiheneuf <bertrand@helixcode.com>
10 * Copyright 1999 - 2003 Ximian, Inc.
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.
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.
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
38 #include <glib/gi18n-lib.h>
39 #include <glib/gstdio.h>
41 #include <libedataserver/e-data-server-util.h>
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"
54 #define CS_CLASS(so) ((CamelSessionClass *)((CamelObject *)so)->klass)
56 static CamelService *get_service (CamelSession *session,
57 const char *url_string,
58 CamelProviderType type,
60 static char *get_storage_path (CamelSession *session,
61 CamelService *service,
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);
71 camel_session_init (CamelSession *session)
73 session->online = TRUE;
74 session->network_state = TRUE;
75 session->priv = g_malloc0(sizeof(*session->priv));
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;
85 camel_session_finalise (CamelObject *o)
87 CamelSession *session = (CamelSession *)o;
88 GThreadPool *thread_pool = session->priv->thread_pool;
90 g_hash_table_destroy(session->priv->thread_active);
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);
98 g_free(session->storage_path);
100 g_mutex_free(session->priv->lock);
101 g_mutex_free(session->priv->thread_lock);
103 g_free(session->priv);
107 camel_session_class_init (CamelSessionClass *camel_session_class)
109 /* virtual method definition */
110 camel_session_class->get_service = get_service;
111 camel_session_class->get_storage_path = get_storage_path;
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;
119 camel_object_class_add_event((CamelObjectClass *)camel_session_class, "online", NULL);
123 camel_session_get_type (void)
125 static CamelType camel_session_type = CAMEL_INVALID_TYPE;
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,
134 (CamelObjectInitFunc) camel_session_init,
135 (CamelObjectFinalizeFunc) camel_session_finalise);
138 return camel_session_type;
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.)
147 * Constructs @session.
150 camel_session_construct (CamelSession *session, const char *storage_path)
152 session->storage_path = g_strdup (storage_path);
155 static CamelService *
156 get_service (CamelSession *session, const char *url_string,
157 CamelProviderType type, CamelException *ex)
160 CamelProvider *provider;
161 CamelService *service;
162 CamelException internal_ex;
164 url = camel_url_new (url_string, ex);
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'"),
179 camel_url_free (url);
183 /* If the provider doesn't use paths but the URL contains one,
186 if (url->path && !CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_PATH))
187 camel_url_set_path (url, NULL);
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);
199 camel_object_bag_abort(provider->service_cache[type], url);
201 camel_object_bag_add(provider->service_cache[type], url, service);
205 camel_url_free (url);
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
219 * This resolves a #CamelURL into a #CamelService, including loading the
220 * provider library for that service if it has not already been loaded.
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.
227 * Returns the requested #CamelService, or %NULL
230 camel_session_get_service (CamelSession *session, const char *url_string,
231 CamelProviderType type, CamelException *ex)
233 CamelService *service;
235 g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
236 g_return_val_if_fail (url_string != NULL, NULL);
238 CAMEL_SESSION_LOCK (session, lock);
239 service = CS_CLASS (session)->get_service (session, url_string, type, ex);
240 CAMEL_SESSION_UNLOCK (session, lock);
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
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.)
256 * Returns the requested #CamelService, or %NULL
259 camel_session_get_service_connected (CamelSession *session,
260 const char *url_string,
261 CamelProviderType type,
266 svc = camel_session_get_service (session, url_string, type, ex);
270 if (svc->status != CAMEL_SERVICE_CONNECTED) {
271 if (camel_service_connect (svc, ex) == FALSE) {
272 camel_object_unref (svc);
282 get_storage_path (CamelSession *session, CamelService *service, CamelException *ex)
286 p = camel_service_get_path (service);
287 path = g_strdup_printf ("%s/%s", session->storage_path, p);
291 if (g_access (path, F_OK) == 0)
293 if (access (path, F_OK) == 0)
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));
309 * camel_session_get_storage_path:
310 * @session: a #CamelSession object
311 * @service: a #CamelService
312 * @ex: a #CamelException
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
320 * Returns the path (which the caller must free), or %NULL if an error
324 camel_session_get_storage_path (CamelSession *session, CamelService *service,
327 g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
328 g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
330 return CS_CLASS (session)->get_storage_path (session, service, ex);
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
346 * This function is used by a #CamelService to ask the application and
347 * the user for a password or other authentication data.
349 * @service and @item together uniquely identify the piece of data the
350 * caller is concerned with.
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.
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
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.
364 * Returns the authentication information or %NULL
367 camel_session_get_password (CamelSession *session, CamelService *service,
368 const char *domain, const char *prompt, const char *item,
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);
376 return CS_CLASS (session)->get_password (session, service, domain, prompt, item, flags, ex);
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
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.
393 * @service and @item identify the rejected authentication information,
394 * as with #camel_session_get_password.
397 camel_session_forget_password (CamelSession *session, CamelService *service,
398 const char *domain, const char *item, CamelException *ex)
400 g_return_if_fail (CAMEL_IS_SESSION (session));
401 g_return_if_fail (item != NULL);
403 CS_CLASS (session)->forget_password (session, service, domain, item, ex);
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
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.
419 * Returns %TRUE if the user accepts, %FALSE if they cancel.
422 camel_session_alert_user (CamelSession *session, CamelSessionAlertType type,
423 const char *prompt, gboolean cancel)
425 g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
426 g_return_val_if_fail (prompt != NULL, FALSE);
428 return CS_CLASS (session)->alert_user (session, type, prompt, cancel);
433 * camel_session_is_online:
434 * @session: a #CamelSession object
436 * Returns whether or not @session is online
439 camel_session_is_online (CamelSession *session)
441 return session->online;
446 * camel_session_set_online:
447 * @session: a #CamelSession object
448 * @online: whether or not the session should be online
450 * Sets the online status of @session to @online.
453 camel_session_set_online (CamelSession *session, gboolean online)
455 session->online = online;
457 camel_object_trigger_event(session, "online", GINT_TO_POINTER(online));
461 * camel_session_get_filter_driver:
462 * @session: a #CamelSession object
463 * @type: the type of filter (eg, "incoming")
464 * @ex: a #CamelException
466 * Returns a filter driver, loaded with applicable rules
469 camel_session_get_filter_driver (CamelSession *session,
473 return CS_CLASS (session)->get_filter_driver (session, type, ex);
477 cs_thread_status(CamelOperation *op, const char *what, int pc, void *data)
479 CamelSessionThreadMsg *m = data;
481 CS_CLASS(m->session)->thread_status(m->session, m, what, pc);
484 static void *session_thread_msg_new(CamelSession *session, CamelSessionThreadOps *ops, unsigned int size)
486 CamelSessionThreadMsg *m;
488 g_assert(size >= sizeof(*m));
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);
504 static void session_thread_msg_free(CamelSession *session, CamelSessionThreadMsg *msg)
506 g_assert(msg->ops != NULL);
508 d(printf("free message %p session %p\n", msg, session));
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);
514 d(printf("free msg, ops->free = %p\n", msg->ops->free));
517 msg->ops->free(session, msg);
519 camel_operation_unref(msg->op);
520 camel_exception_clear(&msg->ex);
521 camel_object_unref(msg->session);
526 session_thread_proxy(CamelSessionThreadMsg *msg, CamelSession *session)
528 if (msg->ops->receive) {
529 CamelOperation *oldop;
531 oldop = camel_operation_register(msg->op);
532 msg->ops->receive(session, msg);
533 camel_operation_register(oldop);
536 camel_session_thread_msg_free(session, msg);
539 static int session_thread_queue(CamelSession *session, CamelSessionThreadMsg *msg, int flags)
541 GThreadPool *thread_pool;
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;
552 CAMEL_SESSION_UNLOCK(session, thread_lock);
555 g_thread_pool_push(thread_pool, msg, NULL);
560 static void session_thread_wait(CamelSession *session, int id)
564 /* we just busy wait, only other alternative is to setup a reply port? */
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);
575 static void session_thread_status(CamelSession *session, CamelSessionThreadMsg *msg, const char *text, int pc)
580 * camel_session_thread_msg_new:
581 * @session: a #CamelSession object
582 * @ops: thread operations
583 * @size: number of bytes
585 * Create a new thread message, using ops as the receive/reply/free
586 * ops, of @size bytes.
588 * @ops points to the operations used to recieve/process and finally
591 * Returns a new #CamelSessionThreadMsg
594 camel_session_thread_msg_new(CamelSession *session, CamelSessionThreadOps *ops, unsigned int size)
596 g_assert(CAMEL_IS_SESSION(session));
597 g_assert(ops != NULL);
598 g_assert(size >= sizeof(CamelSessionThreadMsg));
600 return CS_CLASS (session)->thread_msg_new(session, ops, size);
604 * camel_session_thread_msg_free:
605 * @session: a #CamelSession object
606 * @msg: a #CamelSessionThreadMsg
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.
612 camel_session_thread_msg_free(CamelSession *session, CamelSessionThreadMsg *msg)
614 g_assert(CAMEL_IS_SESSION(session));
615 g_assert(msg != NULL);
616 g_assert(msg->ops != NULL);
618 CS_CLASS (session)->thread_msg_free(session, msg);
622 * camel_session_thread_queue:
623 * @session: a #CamelSession object
624 * @msg: a #CamelSessionThreadMsg
625 * @flags: queue type flags, currently 0.
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.
631 * Returns the id of the operation queued
634 camel_session_thread_queue(CamelSession *session, CamelSessionThreadMsg *msg, int flags)
636 g_assert(CAMEL_IS_SESSION(session));
637 g_assert(msg != NULL);
639 return CS_CLASS (session)->thread_queue(session, msg, flags);
643 * camel_session_thread_wait:
644 * @session: a #CamelSession object
645 * @id: id of the operation to wait on
647 * Wait on an operation to complete (by id).
650 camel_session_thread_wait(CamelSession *session, int id)
652 g_assert(CAMEL_IS_SESSION(session));
657 CS_CLASS (session)->thread_wait(session, id);
661 * camel_session_check_junk:
662 * @session: a #CamelSession object
664 * Do we have to check incoming messages to be junk?
666 * Returns whether or not we are checking incoming messages for junk
669 camel_session_check_junk (CamelSession *session)
671 g_assert(CAMEL_IS_SESSION(session));
673 return session->check_junk;
677 * camel_session_set_check_junk:
678 * @session: a #CamelSession object
681 * Set check_junk flag, if set, incoming mail will be checked for being junk.
684 camel_session_set_check_junk (CamelSession *session, gboolean check_junk)
686 g_assert(CAMEL_IS_SESSION(session));
688 session->check_junk = check_junk;
692 camel_session_get_network_state (CamelSession *session)
694 g_return_val_if_fail (CAMEL_IS_SESSION(session), FALSE);
696 return session->network_state;
700 camel_session_set_network_state (CamelSession *session, gboolean network_state)
702 g_return_if_fail (CAMEL_IS_SESSION(session));
704 session->network_state = network_state;