Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[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 (C) 1999-2008 Novell, Inc. (www.novell.com)
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/gi18n-lib.h>
38 #include <glib/gstdio.h>
39
40 #include "camel-debug.h"
41 #include "camel-file-utils.h"
42 #include "camel-folder.h"
43 #include "camel-marshal.h"
44 #include "camel-mime-message.h"
45 #include "camel-sasl.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 CAMEL_SESSION_GET_PRIVATE(obj) \
53         (G_TYPE_INSTANCE_GET_PRIVATE \
54         ((obj), CAMEL_TYPE_SESSION, CamelSessionPrivate))
55
56 #define JOB_PRIORITY G_PRIORITY_LOW
57
58 #define d(x)
59
60 typedef struct _AsyncContext AsyncContext;
61 typedef struct _JobData JobData;
62
63 struct _CamelSessionPrivate {
64         gchar *user_data_dir;
65         gchar *user_cache_dir;
66
67         GHashTable *services;
68         GMutex services_lock;
69
70         GHashTable *junk_headers;
71         CamelJunkFilter *junk_filter;
72
73         GMainContext *main_context;
74
75         guint check_junk        : 1;
76         guint network_available : 1;
77         guint online            : 1;
78 };
79
80 struct _AsyncContext {
81         CamelFolder *folder;
82         CamelMimeMessage *message;
83         CamelService *service;
84         gchar *address;
85         gchar *auth_mechanism;
86 };
87
88 struct _JobData {
89         CamelSession *session;
90         GCancellable *cancellable;
91         CamelSessionCallback callback;
92         gpointer user_data;
93         GDestroyNotify notify;
94 };
95
96 enum {
97         PROP_0,
98         PROP_CHECK_JUNK,
99         PROP_JUNK_FILTER,
100         PROP_MAIN_CONTEXT,
101         PROP_NETWORK_AVAILABLE,
102         PROP_ONLINE,
103         PROP_USER_DATA_DIR,
104         PROP_USER_CACHE_DIR
105 };
106
107 enum {
108         JOB_STARTED,
109         JOB_FINISHED,
110         LAST_SIGNAL
111 };
112
113 static guint signals[LAST_SIGNAL];
114
115 G_DEFINE_TYPE (CamelSession, camel_session, CAMEL_TYPE_OBJECT)
116
117 static void
118 async_context_free (AsyncContext *async_context)
119 {
120         if (async_context->folder != NULL)
121                 g_object_unref (async_context->folder);
122
123         if (async_context->message != NULL)
124                 g_object_unref (async_context->message);
125
126         if (async_context->service != NULL)
127                 g_object_unref (async_context->service);
128
129         g_free (async_context->address);
130         g_free (async_context->auth_mechanism);
131
132         g_slice_free (AsyncContext, async_context);
133 }
134
135 static void
136 job_data_free (JobData *job_data)
137 {
138         g_object_unref (job_data->session);
139         g_object_unref (job_data->cancellable);
140
141         if (job_data->notify != NULL)
142                 job_data->notify (job_data->user_data);
143
144         g_slice_free (JobData, job_data);
145 }
146
147 static void
148 session_finish_job_cb (CamelSession *session,
149                        GSimpleAsyncResult *simple)
150 {
151         JobData *job_data;
152         GError *error = NULL;
153
154         g_simple_async_result_propagate_error (simple, &error);
155         job_data = g_simple_async_result_get_op_res_gpointer (simple);
156
157         g_signal_emit (
158                 job_data->session,
159                 signals[JOB_FINISHED], 0,
160                 job_data->cancellable, error);
161
162         g_clear_error (&error);
163 }
164
165 static void
166 session_do_job_cb (GSimpleAsyncResult *simple,
167                    CamelSession *session,
168                    GCancellable *cancellable)
169 {
170         JobData *job_data;
171         GError *error = NULL;
172
173         job_data = g_simple_async_result_get_op_res_gpointer (simple);
174
175         job_data->callback (
176                 session, cancellable,
177                 job_data->user_data, &error);
178
179         if (error != NULL)
180                 g_simple_async_result_take_error (simple, error);
181 }
182
183 static gboolean
184 session_start_job_cb (gpointer user_data)
185 {
186         JobData *job_data = user_data;
187         GSimpleAsyncResult *simple;
188
189         g_signal_emit (
190                 job_data->session,
191                 signals[JOB_STARTED], 0,
192                 job_data->cancellable);
193
194         simple = g_simple_async_result_new (
195                 G_OBJECT (job_data->session),
196                 (GAsyncReadyCallback) session_finish_job_cb,
197                 NULL, camel_session_submit_job);
198
199         g_simple_async_result_set_check_cancellable (
200                 simple, job_data->cancellable);
201
202         g_simple_async_result_set_op_res_gpointer (
203                 simple, job_data, (GDestroyNotify) job_data_free);
204
205         g_simple_async_result_run_in_thread (
206                 simple, (GSimpleAsyncThreadFunc)
207                 session_do_job_cb, JOB_PRIORITY,
208                 job_data->cancellable);
209
210         g_object_unref (simple);
211
212         return FALSE;
213 }
214
215 static void
216 session_set_user_data_dir (CamelSession *session,
217                            const gchar *user_data_dir)
218 {
219         g_return_if_fail (user_data_dir != NULL);
220         g_return_if_fail (session->priv->user_data_dir == NULL);
221
222         session->priv->user_data_dir = g_strdup (user_data_dir);
223 }
224
225 static void
226 session_set_user_cache_dir (CamelSession *session,
227                             const gchar *user_cache_dir)
228 {
229         g_return_if_fail (user_cache_dir != NULL);
230         g_return_if_fail (session->priv->user_cache_dir == NULL);
231
232         session->priv->user_cache_dir = g_strdup (user_cache_dir);
233 }
234
235 static void
236 session_set_property (GObject *object,
237                       guint property_id,
238                       const GValue *value,
239                       GParamSpec *pspec)
240 {
241         switch (property_id) {
242                 case PROP_CHECK_JUNK:
243                         camel_session_set_check_junk (
244                                 CAMEL_SESSION (object),
245                                 g_value_get_boolean (value));
246                         return;
247
248                 case PROP_JUNK_FILTER:
249                         camel_session_set_junk_filter (
250                                 CAMEL_SESSION (object),
251                                 g_value_get_object (value));
252                         return;
253
254                 case PROP_NETWORK_AVAILABLE:
255                         camel_session_set_network_available (
256                                 CAMEL_SESSION (object),
257                                 g_value_get_boolean (value));
258                         return;
259
260                 case PROP_ONLINE:
261                         camel_session_set_online (
262                                 CAMEL_SESSION (object),
263                                 g_value_get_boolean (value));
264                         return;
265
266                 case PROP_USER_DATA_DIR:
267                         session_set_user_data_dir (
268                                 CAMEL_SESSION (object),
269                                 g_value_get_string (value));
270                         return;
271
272                 case PROP_USER_CACHE_DIR:
273                         session_set_user_cache_dir (
274                                 CAMEL_SESSION (object),
275                                 g_value_get_string (value));
276                         return;
277         }
278
279         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
280 }
281
282 static void
283 session_get_property (GObject *object,
284                       guint property_id,
285                       GValue *value,
286                       GParamSpec *pspec)
287 {
288         switch (property_id) {
289                 case PROP_CHECK_JUNK:
290                         g_value_set_boolean (
291                                 value, camel_session_get_check_junk (
292                                 CAMEL_SESSION (object)));
293                         return;
294
295                 case PROP_JUNK_FILTER:
296                         g_value_set_object (
297                                 value, camel_session_get_junk_filter (
298                                 CAMEL_SESSION (object)));
299                         return;
300
301                 case PROP_MAIN_CONTEXT:
302                         g_value_set_boxed (
303                                 value, camel_session_get_main_context (
304                                 CAMEL_SESSION (object)));
305                         return;
306
307                 case PROP_NETWORK_AVAILABLE:
308                         g_value_set_boolean (
309                                 value, camel_session_get_network_available (
310                                 CAMEL_SESSION (object)));
311                         return;
312
313                 case PROP_ONLINE:
314                         g_value_set_boolean (
315                                 value, camel_session_get_online (
316                                 CAMEL_SESSION (object)));
317                         return;
318
319                 case PROP_USER_DATA_DIR:
320                         g_value_set_string (
321                                 value, camel_session_get_user_data_dir (
322                                 CAMEL_SESSION (object)));
323                         return;
324
325                 case PROP_USER_CACHE_DIR:
326                         g_value_set_string (
327                                 value, camel_session_get_user_cache_dir (
328                                 CAMEL_SESSION (object)));
329                         return;
330         }
331
332         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
333 }
334
335 static void
336 session_dispose (GObject *object)
337 {
338         CamelSessionPrivate *priv;
339
340         priv = CAMEL_SESSION_GET_PRIVATE (object);
341
342         g_hash_table_remove_all (priv->services);
343
344         if (priv->junk_filter != NULL) {
345                 g_object_unref (priv->junk_filter);
346                 priv->junk_filter = NULL;
347         }
348
349         /* Chain up to parent's dispose() method. */
350         G_OBJECT_CLASS (camel_session_parent_class)->dispose (object);
351 }
352
353 static void
354 session_finalize (GObject *object)
355 {
356         CamelSessionPrivate *priv;
357
358         priv = CAMEL_SESSION_GET_PRIVATE (object);
359
360         g_free (priv->user_data_dir);
361         g_free (priv->user_cache_dir);
362
363         g_hash_table_destroy (priv->services);
364
365         if (priv->main_context != NULL)
366                 g_main_context_unref (priv->main_context);
367
368         g_mutex_clear (&priv->services_lock);
369
370         if (priv->junk_headers) {
371                 g_hash_table_remove_all (priv->junk_headers);
372                 g_hash_table_destroy (priv->junk_headers);
373         }
374
375         /* Chain up to parent's finalize() method. */
376         G_OBJECT_CLASS (camel_session_parent_class)->finalize (object);
377 }
378
379 static CamelService *
380 session_add_service (CamelSession *session,
381                      const gchar *uid,
382                      const gchar *protocol,
383                      CamelProviderType type,
384                      GError **error)
385 {
386         CamelService *service;
387         CamelProvider *provider;
388         GType service_type = G_TYPE_INVALID;
389
390         service = camel_session_ref_service (session, uid);
391         if (CAMEL_IS_SERVICE (service))
392                 return service;
393
394         /* Try to find a suitable CamelService subclass. */
395         provider = camel_provider_get (protocol, error);
396         if (provider != NULL)
397                 service_type = provider->object_types[type];
398
399         if (error && *error)
400                 return NULL;
401
402         if (service_type == G_TYPE_INVALID) {
403                 g_set_error (
404                         error, CAMEL_SERVICE_ERROR,
405                         CAMEL_SERVICE_ERROR_URL_INVALID,
406                         _("No provider available for protocol '%s'"),
407                         protocol);
408                 return NULL;
409         }
410
411         if (!g_type_is_a (service_type, CAMEL_TYPE_SERVICE)) {
412                 g_set_error (
413                         error, CAMEL_SERVICE_ERROR,
414                         CAMEL_SERVICE_ERROR_INVALID,
415                         _("Invalid GType registered for protocol '%s'"),
416                         protocol);
417                 return NULL;
418         }
419
420         service = g_initable_new (
421                 service_type, NULL, error,
422                 "provider", provider, "session",
423                 session, "uid", uid, NULL);
424
425         if (service != NULL) {
426                 g_mutex_lock (&session->priv->services_lock);
427
428                 g_hash_table_insert (
429                         session->priv->services,
430                         g_strdup (uid),
431                         g_object_ref (service));
432
433                 g_mutex_unlock (&session->priv->services_lock);
434         }
435
436         return service;
437 }
438
439 static void
440 session_remove_service (CamelSession *session,
441                         CamelService *service)
442 {
443         const gchar *uid;
444
445         g_mutex_lock (&session->priv->services_lock);
446
447         uid = camel_service_get_uid (service);
448         g_hash_table_remove (session->priv->services, uid);
449
450         g_mutex_unlock (&session->priv->services_lock);
451 }
452
453 static void
454 session_get_socks_proxy (CamelSession *session,
455                          const gchar *for_host,
456                          gchar **host_ret,
457                          gint *port_ret)
458 {
459         *host_ret = NULL;
460         *port_ret = 0;
461 }
462
463 static gboolean
464 session_authenticate_sync (CamelSession *session,
465                            CamelService *service,
466                            const gchar *mechanism,
467                            GCancellable *cancellable,
468                            GError **error)
469 {
470         CamelServiceAuthType *authtype = NULL;
471         CamelAuthenticationResult result;
472         GError *local_error = NULL;
473
474         /* XXX This authenticate_sync() implementation serves only as
475          *     a rough example and is not intended to be used as is.
476          *
477          *     Any CamelSession subclass should override this method
478          *     and implement a more complete authentication loop that
479          *     handles user prompts and password storage.
480          */
481
482         g_warning (
483                 "The default CamelSession.authenticate_sync() "
484                 "method is not intended for production use.");
485
486         /* If a SASL mechanism was given and we can't find
487          * a CamelServiceAuthType for it, fail immediately. */
488         if (mechanism != NULL) {
489                 authtype = camel_sasl_authtype (mechanism);
490                 if (authtype == NULL) {
491                         g_set_error (
492                                 error, CAMEL_SERVICE_ERROR,
493                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
494                                 _("No support for %s authentication"),
495                                 mechanism);
496                         return FALSE;
497                 }
498         }
499
500         /* If the SASL mechanism does not involve a user
501          * password, then it gets one shot to authenticate. */
502         if (authtype != NULL && !authtype->need_password) {
503                 result = camel_service_authenticate_sync (
504                         service, mechanism, cancellable, error);
505                 if (result == CAMEL_AUTHENTICATION_REJECTED)
506                         g_set_error (
507                                 error, CAMEL_SERVICE_ERROR,
508                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
509                                 _("%s authentication failed"), mechanism);
510                 return (result == CAMEL_AUTHENTICATION_ACCEPTED);
511         }
512
513         /* Some SASL mechanisms can attempt to authenticate without a
514          * user password being provided (e.g. single-sign-on credentials),
515          * but can fall back to a user password.  Handle that case next. */
516         if (mechanism != NULL) {
517                 CamelProvider *provider;
518                 CamelSasl *sasl;
519                 const gchar *service_name;
520                 gboolean success = FALSE;
521
522                 provider = camel_service_get_provider (service);
523                 service_name = provider->protocol;
524
525                 /* XXX Would be nice if camel_sasl_try_empty_password_sync()
526                  *     returned CamelAuthenticationResult so it's easier to
527                  *     detect errors. */
528                 sasl = camel_sasl_new (service_name, mechanism, service);
529                 if (sasl != NULL) {
530                         success = camel_sasl_try_empty_password_sync (
531                                 sasl, cancellable, &local_error);
532                         g_object_unref (sasl);
533                 }
534
535                 if (success)
536                         return TRUE;
537         }
538
539         /* Abort authentication if we got cancelled.
540          * Otherwise clear any errors and press on. */
541         if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
542                 return FALSE;
543
544         g_clear_error (&local_error);
545
546 retry:
547         /* XXX This is where things get bogus.  In a real implementation you
548          *     would want to fetch a stored password or prompt the user here.
549          *     Password should be stashed using camel_service_set_password()
550          *     before calling camel_service_authenticate_sync(). */
551
552         result = camel_service_authenticate_sync (
553                 service, mechanism, cancellable, error);
554
555         if (result == CAMEL_AUTHENTICATION_REJECTED) {
556                 /* XXX Request a different password here. */
557                 goto retry;
558         }
559
560         if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
561                 /* XXX Possibly store the password here using
562                  *     GNOME Keyring or something equivalent. */
563         }
564
565         return (result == CAMEL_AUTHENTICATION_ACCEPTED);
566 }
567
568 static void
569 session_authenticate_thread (GSimpleAsyncResult *simple,
570                              GObject *object,
571                              GCancellable *cancellable)
572 {
573         AsyncContext *async_context;
574         GError *error = NULL;
575
576         async_context = g_simple_async_result_get_op_res_gpointer (simple);
577
578         camel_session_authenticate_sync (
579                 CAMEL_SESSION (object),
580                 async_context->service,
581                 async_context->auth_mechanism,
582                 cancellable, &error);
583
584         if (error != NULL)
585                 g_simple_async_result_take_error (simple, error);
586 }
587
588 static void
589 session_authenticate (CamelSession *session,
590                       CamelService *service,
591                       const gchar *mechanism,
592                       gint io_priority,
593                       GCancellable *cancellable,
594                       GAsyncReadyCallback callback,
595                       gpointer user_data)
596 {
597         GSimpleAsyncResult *simple;
598         AsyncContext *async_context;
599
600         async_context = g_slice_new0 (AsyncContext);
601         async_context->service = g_object_ref (service);
602         async_context->auth_mechanism = g_strdup (mechanism);
603
604         simple = g_simple_async_result_new (
605                 G_OBJECT (session), callback, user_data, session_authenticate);
606
607         g_simple_async_result_set_check_cancellable (simple, cancellable);
608
609         g_simple_async_result_set_op_res_gpointer (
610                 simple, async_context, (GDestroyNotify) async_context_free);
611
612         g_simple_async_result_run_in_thread (
613                 simple, session_authenticate_thread, io_priority, cancellable);
614
615         g_object_unref (simple);
616 }
617
618 static gboolean
619 session_authenticate_finish (CamelSession *session,
620                              GAsyncResult *result,
621                              GError **error)
622 {
623         GSimpleAsyncResult *simple;
624
625         g_return_val_if_fail (
626                 g_simple_async_result_is_valid (
627                 result, G_OBJECT (session), session_authenticate), FALSE);
628
629         simple = G_SIMPLE_ASYNC_RESULT (result);
630
631         /* Assume success unless a GError is set. */
632         return !g_simple_async_result_propagate_error (simple, error);
633 }
634
635 static gboolean
636 session_forward_to_sync (CamelSession *session,
637                          CamelFolder *folder,
638                          CamelMimeMessage *message,
639                          const gchar *address,
640                          GCancellable *cancellable,
641                          GError **error)
642 {
643         g_set_error_literal (
644                 error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
645                 _("Forwarding messages is not supported"));
646
647         return FALSE;
648 }
649
650 static void
651 session_forward_to_thread (GSimpleAsyncResult *simple,
652                            GObject *object,
653                            GCancellable *cancellable)
654 {
655         AsyncContext *async_context;
656         GError *error = NULL;
657
658         async_context = g_simple_async_result_get_op_res_gpointer (simple);
659
660         camel_session_forward_to_sync (
661                 CAMEL_SESSION (object),
662                 async_context->folder,
663                 async_context->message,
664                 async_context->address,
665                 cancellable, &error);
666
667         if (error != NULL)
668                 g_simple_async_result_take_error (simple, error);
669 }
670
671 static void
672 session_forward_to (CamelSession *session,
673                     CamelFolder *folder,
674                     CamelMimeMessage *message,
675                     const gchar *address,
676                     gint io_priority,
677                     GCancellable *cancellable,
678                     GAsyncReadyCallback callback,
679                     gpointer user_data)
680 {
681         GSimpleAsyncResult *simple;
682         AsyncContext *async_context;
683
684         async_context = g_slice_new0 (AsyncContext);
685         async_context->folder = g_object_ref (folder);
686         async_context->message = g_object_ref (message);
687         async_context->address = g_strdup (address);
688
689         simple = g_simple_async_result_new (
690                 G_OBJECT (session), callback, user_data, session_forward_to);
691
692         g_simple_async_result_set_check_cancellable (simple, cancellable);
693
694         g_simple_async_result_set_op_res_gpointer (
695                 simple, async_context, (GDestroyNotify) async_context_free);
696
697         g_simple_async_result_run_in_thread (
698                 simple, session_forward_to_thread, io_priority, cancellable);
699
700         g_object_unref (simple);
701 }
702
703 static gboolean
704 session_forward_to_finish (CamelSession *session,
705                            GAsyncResult *result,
706                            GError **error)
707 {
708         GSimpleAsyncResult *simple;
709
710         g_return_val_if_fail (
711                 g_simple_async_result_is_valid (
712                 result, G_OBJECT (session), session_forward_to), FALSE);
713
714         simple = G_SIMPLE_ASYNC_RESULT (result);
715
716         /* Assume success unless a GError is set. */
717         return !g_simple_async_result_propagate_error (simple, error);
718 }
719
720 static void
721 camel_session_class_init (CamelSessionClass *class)
722 {
723         GObjectClass *object_class;
724
725         g_type_class_add_private (class, sizeof (CamelSessionPrivate));
726
727         object_class = G_OBJECT_CLASS (class);
728         object_class->set_property = session_set_property;
729         object_class->get_property = session_get_property;
730         object_class->dispose = session_dispose;
731         object_class->finalize = session_finalize;
732
733         class->add_service = session_add_service;
734         class->remove_service = session_remove_service;
735         class->get_socks_proxy = session_get_socks_proxy;
736
737         class->authenticate_sync = session_authenticate_sync;
738         class->forward_to_sync = session_forward_to_sync;
739
740         class->authenticate = session_authenticate;
741         class->authenticate_finish = session_authenticate_finish;
742         class->forward_to = session_forward_to;
743         class->forward_to_finish = session_forward_to_finish;
744
745         g_object_class_install_property (
746                 object_class,
747                 PROP_CHECK_JUNK,
748                 g_param_spec_boolean (
749                         "check-junk",
750                         "Check Junk",
751                         "Check incoming messages for junk",
752                         FALSE,
753                         G_PARAM_READWRITE |
754                         G_PARAM_CONSTRUCT |
755                         G_PARAM_STATIC_STRINGS));
756
757         g_object_class_install_property (
758                 object_class,
759                 PROP_JUNK_FILTER,
760                 g_param_spec_object (
761                         "junk-filter",
762                         "Junk Filter",
763                         "Classifies messages as junk or not junk",
764                         CAMEL_TYPE_JUNK_FILTER,
765                         G_PARAM_READWRITE |
766                         G_PARAM_STATIC_STRINGS));
767
768         g_object_class_install_property (
769                 object_class,
770                 PROP_MAIN_CONTEXT,
771                 g_param_spec_boxed (
772                         "main-context",
773                         "Main Context",
774                         "The GMainContext used for signal emissions",
775                         G_TYPE_MAIN_CONTEXT,
776                         G_PARAM_READABLE |
777                         G_PARAM_STATIC_STRINGS));
778
779         g_object_class_install_property (
780                 object_class,
781                 PROP_NETWORK_AVAILABLE,
782                 g_param_spec_boolean (
783                         "network-available",
784                         "Network Available",
785                         "Whether the network is available",
786                         TRUE,
787                         G_PARAM_READWRITE |
788                         G_PARAM_CONSTRUCT |
789                         G_PARAM_STATIC_STRINGS));
790
791         g_object_class_install_property (
792                 object_class,
793                 PROP_ONLINE,
794                 g_param_spec_boolean (
795                         "online",
796                         "Online",
797                         "Whether the shell is online",
798                         TRUE,
799                         G_PARAM_READWRITE |
800                         G_PARAM_CONSTRUCT |
801                         G_PARAM_STATIC_STRINGS));
802
803         g_object_class_install_property (
804                 object_class,
805                 PROP_USER_DATA_DIR,
806                 g_param_spec_string (
807                         "user-data-dir",
808                         "User Data Directory",
809                         "User-specific base directory for mail data",
810                         NULL,
811                         G_PARAM_READWRITE |
812                         G_PARAM_CONSTRUCT |
813                         G_PARAM_STATIC_STRINGS));
814
815         g_object_class_install_property (
816                 object_class,
817                 PROP_USER_CACHE_DIR,
818                 g_param_spec_string (
819                         "user-cache-dir",
820                         "User Cache Directory",
821                         "User-specific base directory for mail cache",
822                         NULL,
823                         G_PARAM_READWRITE |
824                         G_PARAM_CONSTRUCT |
825                         G_PARAM_STATIC_STRINGS));
826
827         signals[JOB_STARTED] = g_signal_new (
828                 "job-started",
829                 G_OBJECT_CLASS_TYPE (class),
830                 G_SIGNAL_RUN_LAST,
831                 G_STRUCT_OFFSET (CamelSessionClass, job_started),
832                 NULL, NULL,
833                 g_cclosure_marshal_VOID__OBJECT,
834                 G_TYPE_NONE, 1,
835                 G_TYPE_CANCELLABLE);
836
837         signals[JOB_FINISHED] = g_signal_new (
838                 "job-finished",
839                 G_OBJECT_CLASS_TYPE (class),
840                 G_SIGNAL_RUN_LAST,
841                 G_STRUCT_OFFSET (CamelSessionClass, job_finished),
842                 NULL, NULL,
843                 camel_marshal_VOID__OBJECT_POINTER,
844                 G_TYPE_NONE, 2,
845                 G_TYPE_CANCELLABLE,
846                 G_TYPE_POINTER);
847 }
848
849 static void
850 camel_session_init (CamelSession *session)
851 {
852         GHashTable *services;
853
854         services = g_hash_table_new_full (
855                 (GHashFunc) g_str_hash,
856                 (GEqualFunc) g_str_equal,
857                 (GDestroyNotify) g_free,
858                 (GDestroyNotify) g_object_unref);
859
860         session->priv = CAMEL_SESSION_GET_PRIVATE (session);
861
862         session->priv->services = services;
863         g_mutex_init (&session->priv->services_lock);
864         session->priv->junk_headers = NULL;
865
866         session->priv->main_context = g_main_context_ref_thread_default ();
867 }
868
869 /**
870  * camel_session_get_main_context:
871  * @session: a #CamelSession
872  *
873  * Returns the #GMainContext from which signals are emitted.  This
874  * was the thread-default #GMainContext for the thread where @session
875  * was instantiated.
876  *
877  * Returns: the #GMainContext for signal emissions
878  *
879  * Since: 3.6
880  **/
881 GMainContext *
882 camel_session_get_main_context (CamelSession *session)
883 {
884         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
885
886         return session->priv->main_context;
887 }
888
889 /**
890  * camel_session_get_user_data_dir:
891  * @session: a #CamelSession
892  *
893  * Returns the base directory under which to store user-specific mail data.
894  *
895  * Returns: the base directory for mail data
896  *
897  * Since: 3.2
898  **/
899 const gchar *
900 camel_session_get_user_data_dir (CamelSession *session)
901 {
902         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
903
904         return session->priv->user_data_dir;
905 }
906
907 /**
908  * camel_session_get_user_cache_dir:
909  * @session: a #CamelSession
910  *
911  * Returns the base directory under which to store user-specific mail cache.
912  *
913  * Returns: the base directory for mail cache
914  *
915  * Since: 3.4
916  **/
917 const gchar *
918 camel_session_get_user_cache_dir (CamelSession *session)
919 {
920         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
921
922         return session->priv->user_cache_dir;
923 }
924
925 /**
926  * camel_session_add_service:
927  * @session: a #CamelSession
928  * @uid: a unique identifier string
929  * @protocol: the service protocol
930  * @type: the service type
931  * @error: return location for a #GError, or %NULL
932  *
933  * Instantiates a new #CamelService for @session.  The @uid identifies the
934  * service for future lookup.  The @protocol indicates which #CamelProvider
935  * holds the #GType of the #CamelService subclass to instantiate.  The @type
936  * explicitly designates the service as a #CamelStore or #CamelTransport.
937  *
938  * If the given @uid has already been added, the existing #CamelService
939  * with that @uid is returned regardless of whether it agrees with the
940  * given @protocol and @type.
941  *
942  * If no #CamelProvider is available to handle the given @protocol, or
943  * if the #CamelProvider does not specify a valid #GType for @type, the
944  * function sets @error and returns %NULL.
945  *
946  * The returned #CamelService is referenced for thread-safety and must be
947  * unreferenced with g_object_unref() when finished with it.
948  *
949  * Returns: a #CamelService instance, or %NULL
950  *
951  * Since: 3.2
952  **/
953 CamelService *
954 camel_session_add_service (CamelSession *session,
955                            const gchar *uid,
956                            const gchar *protocol,
957                            CamelProviderType type,
958                            GError **error)
959 {
960         CamelSessionClass *class;
961         CamelService *service;
962
963         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
964         g_return_val_if_fail (uid != NULL, NULL);
965         g_return_val_if_fail (protocol != NULL, NULL);
966
967         class = CAMEL_SESSION_GET_CLASS (session);
968         g_return_val_if_fail (class->add_service != NULL, NULL);
969
970         service = class->add_service (session, uid, protocol, type, error);
971         CAMEL_CHECK_GERROR (session, add_service, service != NULL, error);
972
973         return service;
974 }
975
976 /**
977  * camel_session_remove_service:
978  * @session: a #CamelSession
979  * @service: the #CamelService to remove
980  *
981  * Removes a #CamelService previously added by camel_session_add_service().
982  *
983  * Since: 3.2
984  **/
985 void
986 camel_session_remove_service (CamelSession *session,
987                               CamelService *service)
988 {
989         CamelSessionClass *class;
990
991         g_return_if_fail (CAMEL_IS_SESSION (session));
992         g_return_if_fail (CAMEL_IS_SERVICE (service));
993
994         /* Verify the service belongs to this session. */
995         g_return_if_fail (camel_service_get_session (service) == session);
996
997         class = CAMEL_SESSION_GET_CLASS (session);
998         g_return_if_fail (class->remove_service != NULL);
999
1000         class->remove_service (session, service);
1001 }
1002
1003 /**
1004  * camel_session_ref_service:
1005  * @session: a #CamelSession
1006  * @uid: a unique identifier string
1007  *
1008  * Looks up a #CamelService by its unique identifier string.  The service
1009  * must have been previously added using camel_session_add_service().
1010  *
1011  * The returned #CamelService is referenced for thread-safety and must be
1012  * unreferenced with g_object_unref() when finished with it.
1013  *
1014  * Returns: a #CamelService instance, or %NULL
1015  *
1016  * Since: 3.6
1017  **/
1018 CamelService *
1019 camel_session_ref_service (CamelSession *session,
1020                            const gchar *uid)
1021 {
1022         CamelService *service;
1023
1024         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1025         g_return_val_if_fail (uid != NULL, NULL);
1026
1027         g_mutex_lock (&session->priv->services_lock);
1028
1029         service = g_hash_table_lookup (session->priv->services, uid);
1030
1031         if (service != NULL)
1032                 g_object_ref (service);
1033
1034         g_mutex_unlock (&session->priv->services_lock);
1035
1036         return service;
1037 }
1038
1039 /**
1040  * camel_session_ref_service_by_url:
1041  * @session: a #CamelSession
1042  * @url: a #CamelURL
1043  * @type: a #CamelProviderType
1044  *
1045  * Looks up a #CamelService by trying to match its #CamelURL against the
1046  * given @url and then checking that the object is of the desired @type.
1047  * The service must have been previously added using
1048  * camel_session_add_service().
1049  *
1050  * The returned #CamelService is referenced for thread-safety and must be
1051  * unreferenced with g_object_unref() when finished with it.
1052  *
1053  * Note this function is significantly slower than camel_session_ref_service().
1054  *
1055  * Returns: a #CamelService instance, or %NULL
1056  *
1057  * Since: 3.6
1058  **/
1059 CamelService *
1060 camel_session_ref_service_by_url (CamelSession *session,
1061                                   CamelURL *url,
1062                                   CamelProviderType type)
1063 {
1064         CamelService *match = NULL;
1065         GList *list, *iter;
1066
1067         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1068         g_return_val_if_fail (url != NULL, NULL);
1069
1070         list = camel_session_list_services (session);
1071
1072         for (iter = list; iter != NULL; iter = g_list_next (iter)) {
1073                 CamelProvider *provider;
1074                 CamelService *service;
1075                 CamelURL *service_url;
1076                 gboolean url_equal;
1077
1078                 service = CAMEL_SERVICE (iter->data);
1079                 provider = camel_service_get_provider (service);
1080
1081                 if (provider == NULL)
1082                         continue;
1083
1084                 if (provider->url_equal == NULL)
1085                         continue;
1086
1087                 service_url = camel_service_new_camel_url (service);
1088                 url_equal = provider->url_equal (url, service_url);
1089                 camel_url_free (service_url);
1090
1091                 if (!url_equal)
1092                         continue;
1093
1094                 switch (type) {
1095                         case CAMEL_PROVIDER_STORE:
1096                                 if (CAMEL_IS_STORE (service))
1097                                         match = g_object_ref (service);
1098                                 break;
1099                         case CAMEL_PROVIDER_TRANSPORT:
1100                                 if (CAMEL_IS_TRANSPORT (service))
1101                                         match = g_object_ref (service);
1102                                 break;
1103                         default:
1104                                 g_warn_if_reached ();
1105                                 break;
1106                 }
1107
1108                 if (match != NULL)
1109                         break;
1110         }
1111
1112         g_list_free_full (list, (GDestroyNotify) g_object_unref);
1113
1114         return match;
1115 }
1116
1117 /**
1118  * camel_session_list_services:
1119  * @session: a #CamelSession
1120  *
1121  * Returns a list of all #CamelService objects previously added using
1122  * camel_session_add_service().
1123  *
1124  * The services returned in the list are referenced for thread-safety.
1125  * They must each be unreferenced with g_object_unref() when finished
1126  * with them.  Free the returned list itself with g_list_free().
1127  *
1128  * An easy way to free the list property in one step is as follows:
1129  *
1130  * |[
1131  *   g_list_free_full (list, g_object_unref);
1132  * ]|
1133  *
1134  * Returns: an unsorted list of #CamelService objects
1135  *
1136  * Since: 3.2
1137  **/
1138 GList *
1139 camel_session_list_services (CamelSession *session)
1140 {
1141         GList *list;
1142
1143         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1144
1145         g_mutex_lock (&session->priv->services_lock);
1146
1147         list = g_hash_table_get_values (session->priv->services);
1148
1149         g_list_foreach (list, (GFunc) g_object_ref, NULL);
1150
1151         g_mutex_unlock (&session->priv->services_lock);
1152
1153         return list;
1154 }
1155
1156 /**
1157  * camel_session_remove_services:
1158  * @session: a #CamelSession
1159  *
1160  * Removes all #CamelService instances added by camel_session_add_service().
1161  *
1162  * This can be useful during application shutdown to ensure all #CamelService
1163  * instances are freed properly, especially since #CamelSession instances are
1164  * prone to reference cycles.
1165  *
1166  * Since: 3.2
1167  **/
1168 void
1169 camel_session_remove_services (CamelSession *session)
1170 {
1171         g_return_if_fail (CAMEL_IS_SESSION (session));
1172
1173         g_mutex_lock (&session->priv->services_lock);
1174
1175         g_hash_table_remove_all (session->priv->services);
1176
1177         g_mutex_unlock (&session->priv->services_lock);
1178 }
1179
1180 /**
1181  * camel_session_get_password:
1182  * @session: a #CamelSession
1183  * @service: the #CamelService this query is being made by
1184  * @prompt: prompt to provide to user
1185  * @item: an identifier, unique within this service, for the information
1186  * @flags: %CAMEL_SESSION_PASSWORD_REPROMPT, the prompt should force a reprompt
1187  * %CAMEL_SESSION_PASSWORD_SECRET, whether the password is secret
1188  * %CAMEL_SESSION_PASSWORD_STATIC, the password is remembered externally
1189  * @error: return location for a #GError, or %NULL
1190  *
1191  * This function is used by a #CamelService to ask the application and
1192  * the user for a password or other authentication data.
1193  *
1194  * @service and @item together uniquely identify the piece of data the
1195  * caller is concerned with.
1196  *
1197  * @prompt is a question to ask the user (if the application doesn't
1198  * already have the answer cached). If %CAMEL_SESSION_PASSWORD_SECRET
1199  * is set, the user's input will not be echoed back.
1200  *
1201  * If %CAMEL_SESSION_PASSWORD_STATIC is set, it means the password returned
1202  * will be stored statically by the caller automatically, for the current
1203  * session.
1204  *
1205  * The authenticator should set @error to %G_IO_ERROR_CANCELLED if
1206  * the user did not provide the information. The caller must g_free()
1207  * the information returned when it is done with it.
1208  *
1209  * Returns: the authentication information or %NULL
1210  **/
1211 gchar *
1212 camel_session_get_password (CamelSession *session,
1213                             CamelService *service,
1214                             const gchar *prompt,
1215                             const gchar *item,
1216                             guint32 flags,
1217                             GError **error)
1218 {
1219         CamelSessionClass *class;
1220         gchar *password;
1221
1222         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1223         g_return_val_if_fail (prompt != NULL, NULL);
1224         g_return_val_if_fail (item != NULL, NULL);
1225
1226         class = CAMEL_SESSION_GET_CLASS (session);
1227         g_return_val_if_fail (class->get_password != NULL, NULL);
1228
1229         password = class->get_password (
1230                 session, service, prompt, item, flags, error);
1231         CAMEL_CHECK_GERROR (session, get_password, password != NULL, error);
1232
1233         return password;
1234 }
1235
1236 /**
1237  * camel_session_forget_password:
1238  * @session: a #CamelSession
1239  * @service: the #CamelService rejecting the password
1240  * @item: an identifier, unique within this service, for the information
1241  * @error: return location for a #GError, or %NULL
1242  *
1243  * This function is used by a #CamelService to tell the application
1244  * that the authentication information it provided via
1245  * camel_session_get_password() was rejected by the service. If the
1246  * application was caching this information, it should stop,
1247  * and if the service asks for it again, it should ask the user.
1248  *
1249  * @service and @item identify the rejected authentication information,
1250  * as with camel_session_get_password().
1251  *
1252  * Returns: %TRUE on success, %FALSE on failure
1253  **/
1254 gboolean
1255 camel_session_forget_password (CamelSession *session,
1256                                CamelService *service,
1257                                const gchar *item,
1258                                GError **error)
1259 {
1260         CamelSessionClass *class;
1261         gboolean success;
1262
1263         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1264         g_return_val_if_fail (item != NULL, FALSE);
1265
1266         class = CAMEL_SESSION_GET_CLASS (session);
1267         g_return_val_if_fail (class->forget_password, FALSE);
1268
1269         success = class->forget_password (session, service, item, error);
1270         CAMEL_CHECK_GERROR (session, forget_password, success, error);
1271
1272         return success;
1273 }
1274
1275 /**
1276  * camel_session_alert_user:
1277  * @session: a #CamelSession
1278  * @type: the type of alert (info, warning, or error)
1279  * @prompt: the message for the user
1280  * @button_captions: List of button captions to use. If NULL, only "Dismiss" button is shown.
1281  *
1282  * Presents the given @prompt to the user, in the style indicated by
1283  * @type. If @cancel is %TRUE, the user will be able to accept or
1284  * cancel. Otherwise, the message is purely informational.
1285  *
1286  * Returns: Index of pressed button from @button_captions, -1 if NULL.
1287  */
1288 gint
1289 camel_session_alert_user (CamelSession *session,
1290                           CamelSessionAlertType type,
1291                           const gchar *prompt,
1292                           GSList *button_captions)
1293 {
1294         CamelSessionClass *class;
1295
1296         g_return_val_if_fail (CAMEL_IS_SESSION (session), -1);
1297         g_return_val_if_fail (prompt != NULL, -1);
1298
1299         class = CAMEL_SESSION_GET_CLASS (session);
1300         g_return_val_if_fail (class->alert_user != NULL, -1);
1301
1302         return class->alert_user (session, type, prompt, button_captions);
1303 }
1304
1305 /**
1306  * camel_session_lookup_addressbook:
1307  *
1308  * Since: 2.22
1309  **/
1310 gboolean
1311 camel_session_lookup_addressbook (CamelSession *session,
1312                                   const gchar *name)
1313 {
1314         CamelSessionClass *class;
1315
1316         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1317         g_return_val_if_fail (name != NULL, FALSE);
1318
1319         class = CAMEL_SESSION_GET_CLASS (session);
1320         g_return_val_if_fail (class->lookup_addressbook != NULL, FALSE);
1321
1322         return class->lookup_addressbook (session, name);
1323 }
1324
1325 /**
1326  * camel_session_build_password_prompt:
1327  * @type: account type (e.g. "IMAP")
1328  * @user: user name for the account
1329  * @host: host name for the account
1330  *
1331  * Constructs a localized password prompt from @type, @user and @host,
1332  * suitable for passing to camel_session_get_password().  The resulting
1333  * string contains markup tags.  Use g_free() to free it.
1334  *
1335  * Returns: a newly-allocated password prompt string
1336  *
1337  * Since: 2.22
1338  **/
1339 gchar *
1340 camel_session_build_password_prompt (const gchar *type,
1341                                      const gchar *user,
1342                                      const gchar *host)
1343 {
1344         gchar *user_markup;
1345         gchar *host_markup;
1346         gchar *prompt;
1347
1348         g_return_val_if_fail (type != NULL, NULL);
1349         g_return_val_if_fail (user != NULL, NULL);
1350         g_return_val_if_fail (host != NULL, NULL);
1351
1352         /* Add bold tags to the "user" and "host" strings.  We use
1353          * separate strings here to avoid putting markup tags in the
1354          * translatable string below. */
1355         user_markup = g_markup_printf_escaped ("<b>%s</b>", user);
1356         host_markup = g_markup_printf_escaped ("<b>%s</b>", host);
1357
1358         /* Translators: The first argument is the account type
1359          * (e.g. "IMAP"), the second is the user name, and the
1360          * third is the host name. */
1361         prompt = g_strdup_printf (
1362                 _("Please enter the %s password for %s on host %s."),
1363                 type, user_markup, host_markup);
1364
1365         g_free (user_markup);
1366         g_free (host_markup);
1367
1368         return prompt;
1369 }
1370
1371 /**
1372  * camel_session_get_online:
1373  * @session: a #CamelSession
1374  *
1375  * Returns: whether or not @session is online
1376  **/
1377 gboolean
1378 camel_session_get_online (CamelSession *session)
1379 {
1380         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1381
1382         return session->priv->online;
1383 }
1384
1385 /**
1386  * camel_session_set_online:
1387  * @session: a #CamelSession
1388  * @online: whether or not the session should be online
1389  *
1390  * Sets the online status of @session to @online.
1391  **/
1392 void
1393 camel_session_set_online (CamelSession *session,
1394                           gboolean online)
1395 {
1396         g_return_if_fail (CAMEL_IS_SESSION (session));
1397
1398         if (online == session->priv->online)
1399                 return;
1400
1401         session->priv->online = online;
1402
1403         g_object_notify (G_OBJECT (session), "online");
1404 }
1405
1406 /**
1407  * camel_session_get_filter_driver:
1408  * @session: a #CamelSession
1409  * @type: the type of filter (eg, "incoming")
1410  * @error: return location for a #GError, or %NULL
1411  *
1412  * Returns: a filter driver, loaded with applicable rules
1413  **/
1414 CamelFilterDriver *
1415 camel_session_get_filter_driver (CamelSession *session,
1416                                  const gchar *type,
1417                                  GError **error)
1418 {
1419         CamelSessionClass *class;
1420         CamelFilterDriver *driver;
1421
1422         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1423         g_return_val_if_fail (type != NULL, NULL);
1424
1425         class = CAMEL_SESSION_GET_CLASS (session);
1426         g_return_val_if_fail (class->get_filter_driver != NULL, NULL);
1427
1428         driver = class->get_filter_driver (session, type, error);
1429         CAMEL_CHECK_GERROR (session, get_filter_driver, driver != NULL, error);
1430
1431         return driver;
1432 }
1433
1434 /**
1435  * camel_session_get_junk_filter:
1436  * @session: a #CamelSession
1437  *
1438  * Returns the #CamelJunkFilter instance used to classify messages as
1439  * junk or not junk during filtering.
1440  *
1441  * Note that #CamelJunkFilter itself is just an interface.  The application
1442  * must implement the interface and install a #CamelJunkFilter instance for
1443  * junk filtering to take place.
1444  *
1445  * Returns: a #CamelJunkFilter, or %NULL
1446  *
1447  * Since: 3.2
1448  **/
1449 CamelJunkFilter *
1450 camel_session_get_junk_filter (CamelSession *session)
1451 {
1452         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1453
1454         return session->priv->junk_filter;
1455 }
1456
1457 /**
1458  * camel_session_set_junk_filter:
1459  * @session: a #CamelSession
1460  * @junk_filter: a #CamelJunkFilter, or %NULL
1461  *
1462  * Installs the #CamelJunkFilter instance used to classify messages as
1463  * junk or not junk during filtering.
1464  *
1465  * Note that #CamelJunkFilter itself is just an interface.  The application
1466  * must implement the interface and install a #CamelJunkFilter instance for
1467  * junk filtering to take place.
1468  *
1469  * Since: 3.2
1470  **/
1471 void
1472 camel_session_set_junk_filter (CamelSession *session,
1473                                CamelJunkFilter *junk_filter)
1474 {
1475         g_return_if_fail (CAMEL_IS_SESSION (session));
1476
1477         if (junk_filter != NULL) {
1478                 g_return_if_fail (CAMEL_IS_JUNK_FILTER (junk_filter));
1479                 g_object_ref (junk_filter);
1480         }
1481
1482         if (session->priv->junk_filter != NULL)
1483                 g_object_unref (session->priv->junk_filter);
1484
1485         session->priv->junk_filter = junk_filter;
1486
1487         g_object_notify (G_OBJECT (session), "junk-filter");
1488 }
1489
1490 /**
1491  * camel_session_idle_add:
1492  * @session: a #CamelSession
1493  * @priority: the priority of the idle source
1494  * @function: a function to call
1495  * @data: data to pass to @function
1496  * @notify: function to call when the idle is removed, or %NULL
1497  *
1498  * Adds a function to be called whenever there are no higher priority events
1499  * pending.  If @function returns %FALSE it is automatically removed from the
1500  * list of event sources and will not be called again.
1501  *
1502  * This internally creates a main loop source using g_idle_source_new()
1503  * and attaches it to @session's own #CamelSession:main-context using
1504  * g_source_attach().
1505  *
1506  * The @priority is typically in the range between %G_PRIORITY_DEFAULT_IDLE
1507  * and %G_PRIORITY_HIGH_IDLE.
1508  *
1509  * Returns: the ID (greater than 0) of the event source
1510  *
1511  * Since: 3.6
1512  **/
1513 guint
1514 camel_session_idle_add (CamelSession *session,
1515                         gint priority,
1516                         GSourceFunc function,
1517                         gpointer data,
1518                         GDestroyNotify notify)
1519 {
1520         GMainContext *main_context;
1521         GSource *source;
1522         guint source_id;
1523
1524         g_return_val_if_fail (CAMEL_IS_SESSION (session), 0);
1525         g_return_val_if_fail (function != NULL, 0);
1526
1527         main_context = camel_session_get_main_context (session);
1528
1529         source = g_idle_source_new ();
1530
1531         if (priority != G_PRIORITY_DEFAULT_IDLE)
1532                 g_source_set_priority (source, priority);
1533
1534         g_source_set_callback (source, function, data, notify);
1535
1536         source_id = g_source_attach (source, main_context);
1537
1538         g_source_unref (source);
1539
1540         return source_id;
1541 }
1542
1543 /**
1544  * camel_session_submit_job:
1545  * @session: a #CamelSession
1546  * @callback: a #CamelSessionCallback
1547  * @user_data: user data passed to the callback
1548  * @notify: a #GDestroyNotify function
1549  *
1550  * This function provides a simple mechanism for providers to initiate
1551  * low-priority background jobs.  Jobs can be submitted from any thread,
1552  * but execution of the jobs is always as follows:
1553  *
1554  * 1) The #CamelSession:job-started signal is emitted from the thread
1555  *    in which @session was created.  This is typically the same thread
1556  *    that hosts the global default #GMainContext, or "main" thread.
1557  *
1558  * 2) The @callback function is invoked from a different thread where
1559  *    it's safe to call synchronous functions.
1560  *
1561  * 3) Once @callback has returned, the #CamelSesson:job-finished signal
1562  *    is emitted from the same thread as #CamelSession:job-started was
1563  *    emitted.
1564  *
1565  * 4) Finally if a @notify function was provided, it is invoked and
1566  *    passed @user_data so that @user_data can be freed.
1567  *
1568  * Since: 3.2
1569  **/
1570 void
1571 camel_session_submit_job (CamelSession *session,
1572                           CamelSessionCallback callback,
1573                           gpointer user_data,
1574                           GDestroyNotify notify)
1575 {
1576         JobData *job_data;
1577
1578         g_return_if_fail (CAMEL_IS_SESSION (session));
1579         g_return_if_fail (callback != NULL);
1580
1581         job_data = g_slice_new0 (JobData);
1582         job_data->session = g_object_ref (session);
1583         job_data->cancellable = camel_operation_new ();
1584         job_data->callback = callback;
1585         job_data->user_data = user_data;
1586         job_data->notify = notify;
1587
1588         camel_session_idle_add (
1589                 session, JOB_PRIORITY,
1590                 session_start_job_cb,
1591                 job_data, (GDestroyNotify) NULL);
1592 }
1593
1594 /**
1595  * camel_session_get_check_junk:
1596  * @session: a #CamelSession
1597  *
1598  * Do we have to check incoming messages to be junk?
1599  *
1600  * Returns: whether or not we are checking incoming messages for junk
1601  **/
1602 gboolean
1603 camel_session_get_check_junk (CamelSession *session)
1604 {
1605         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1606
1607         return session->priv->check_junk;
1608 }
1609
1610 /**
1611  * camel_session_set_check_junk:
1612  * @session: a #CamelSession
1613  * @check_junk: whether to check incoming messages for junk
1614  *
1615  * Set check_junk flag, if set, incoming mail will be checked for being junk.
1616  **/
1617 void
1618 camel_session_set_check_junk (CamelSession *session,
1619                               gboolean check_junk)
1620 {
1621         g_return_if_fail (CAMEL_IS_SESSION (session));
1622
1623         session->priv->check_junk = check_junk;
1624
1625         g_object_notify (G_OBJECT (session), "check-junk");
1626 }
1627
1628 /**
1629  * camel_session_get_network_available:
1630  * @session: a #CamelSession
1631  *
1632  * Since: 2.32
1633  **/
1634 gboolean
1635 camel_session_get_network_available (CamelSession *session)
1636 {
1637         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1638
1639         return session->priv->network_available;
1640 }
1641
1642 /**
1643  * camel_session_set_network_available:
1644  * @session: a #CamelSession
1645  * @network_available: whether a network is available
1646  *
1647  * Since: 2.32
1648  **/
1649 void
1650 camel_session_set_network_available (CamelSession *session,
1651                                      gboolean network_available)
1652 {
1653         g_return_if_fail (CAMEL_IS_SESSION (session));
1654
1655         session->priv->network_available = network_available;
1656
1657         g_object_notify (G_OBJECT (session), "network-available");
1658 }
1659
1660 /**
1661  * camel_session_set_junk_headers:
1662  *
1663  * Since: 2.22
1664  **/
1665 void
1666 camel_session_set_junk_headers (CamelSession *session,
1667                                 const gchar **headers,
1668                                 const gchar **values,
1669                                 gint len)
1670 {
1671         gint i;
1672
1673         g_return_if_fail (CAMEL_IS_SESSION (session));
1674
1675         if (session->priv->junk_headers) {
1676                 g_hash_table_remove_all (session->priv->junk_headers);
1677                 g_hash_table_destroy (session->priv->junk_headers);
1678         }
1679
1680         session->priv->junk_headers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1681
1682         for (i = 0; i < len; i++) {
1683                 g_hash_table_insert (session->priv->junk_headers, g_strdup (headers[i]), g_strdup (values[i]));
1684         }
1685 }
1686
1687 /**
1688  * camel_session_get_junk_headers:
1689  *
1690  * Since: 2.22
1691  **/
1692 const GHashTable *
1693 camel_session_get_junk_headers (CamelSession *session)
1694 {
1695         g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1696
1697         return session->priv->junk_headers;
1698 }
1699
1700 /**
1701  * camel_session_get_socks_proxy:
1702  * @session: A #CamelSession
1703  * @for_host: Host name to which the connection will be requested
1704  * @host_ret: Location to return the SOCKS proxy hostname
1705  * @port_ret: Location to return the SOCKS proxy port
1706  *
1707  * Queries the SOCKS proxy that is configured for a @session.  This will
1708  * put %NULL in @hosts_ret if there is no proxy configured or when
1709  * the @for_host is listed in proxy ignore list.
1710  *
1711  * Since: 2.32
1712  */
1713 void
1714 camel_session_get_socks_proxy (CamelSession *session,
1715                                const gchar *for_host,
1716                                gchar **host_ret,
1717                                gint *port_ret)
1718 {
1719         CamelSessionClass *class;
1720
1721         g_return_if_fail (CAMEL_IS_SESSION (session));
1722         g_return_if_fail (for_host != NULL);
1723         g_return_if_fail (host_ret != NULL);
1724         g_return_if_fail (port_ret != NULL);
1725
1726         class = CAMEL_SESSION_GET_CLASS (session);
1727         g_return_if_fail (class->get_socks_proxy != NULL);
1728
1729         class->get_socks_proxy (session, for_host, host_ret, port_ret);
1730 }
1731
1732 /**
1733  * camel_session_authenticate_sync:
1734  * @session: a #CamelSession
1735  * @service: a #CamelService
1736  * @mechanism: a SASL mechanism name, or %NULL
1737  * @cancellable: optional #GCancellable object, or %NULL
1738  * @error: return location for a #GError, or %NULL
1739  *
1740  * Authenticates @service, which may involve repeated calls to
1741  * camel_service_authenticate() or camel_service_authenticate_sync().
1742  * A #CamelSession subclass is largely responsible for implementing this,
1743  * and should handle things like user prompts and secure password storage.
1744  * These issues are out-of-scope for Camel.
1745  *
1746  * If an error occurs, or if authentication is aborted, the function sets
1747  * @error and returns %FALSE.
1748  *
1749  * Returns: %TRUE on success, %FALSE on failure
1750  *
1751  * Since: 3.4
1752  **/
1753 gboolean
1754 camel_session_authenticate_sync (CamelSession *session,
1755                                  CamelService *service,
1756                                  const gchar *mechanism,
1757                                  GCancellable *cancellable,
1758                                  GError **error)
1759 {
1760         CamelSessionClass *class;
1761         gboolean success;
1762
1763         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1764         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
1765
1766         class = CAMEL_SESSION_GET_CLASS (session);
1767         g_return_val_if_fail (class->authenticate_sync != NULL, FALSE);
1768
1769         success = class->authenticate_sync (
1770                 session, service, mechanism, cancellable, error);
1771         CAMEL_CHECK_GERROR (session, authenticate_sync, success, error);
1772
1773         return success;
1774 }
1775
1776 /**
1777  * camel_session_forward_to_sync:
1778  * @session: a #CamelSession
1779  * @folder: the #CamelFolder where @message is located
1780  * @message: the #CamelMimeMessage to forward
1781  * @address: the recipient's email address
1782  * @cancellable: optional #GCancellable object, or %NULL
1783  * @error: return location for a #GError, or %NULL
1784  *
1785  * Forwards @message in @folder to the email address(es) given by @address.
1786  *
1787  * If an error occurs, the function sets @error and returns %FALSE.
1788  *
1789  * Returns: %TRUE on success, %FALSE on failure
1790  *
1791  * Since: 3.6
1792  **/
1793 gboolean
1794 camel_session_forward_to_sync (CamelSession *session,
1795                                CamelFolder *folder,
1796                                CamelMimeMessage *message,
1797                                const gchar *address,
1798                                GCancellable *cancellable,
1799                                GError **error)
1800 {
1801         CamelSessionClass *class;
1802         gboolean success;
1803
1804         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1805         g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1806         g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
1807         g_return_val_if_fail (address != NULL, FALSE);
1808
1809         class = CAMEL_SESSION_GET_CLASS (session);
1810         g_return_val_if_fail (class->forward_to_sync != NULL, FALSE);
1811
1812         success = class->forward_to_sync (
1813                 session, folder, message, address, cancellable, error);
1814         CAMEL_CHECK_GERROR (session, forward_to_sync, success, error);
1815
1816         return success;
1817 }
1818
1819 /**
1820  * camel_session_authenticate:
1821  * @session: a #CamelSession
1822  * @service: a #CamelService
1823  * @mechanism: a SASL mechanism name, or %NULL
1824  * @io_priority: the I/O priority for the request
1825  * @cancellable: optional #GCancellable object, or %NULL
1826  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1827  * @user_data: data to pass to the callback function
1828  *
1829  * Asynchronously authenticates @service, which may involve repeated calls
1830  * to camel_service_authenticate() or camel_service_authenticate_sync().
1831  * A #CamelSession subclass is largely responsible for implementing this,
1832  * and should handle things like user prompts and secure password storage.
1833  * These issues are out-of-scope for Camel.
1834  *
1835  * When the operation is finished, @callback will be called.  You can
1836  * then call camel_session_authenticate_finish() to get the result of
1837  * the operation.
1838  *
1839  * Since: 3.4
1840  **/
1841 void
1842 camel_session_authenticate (CamelSession *session,
1843                             CamelService *service,
1844                             const gchar *mechanism,
1845                             gint io_priority,
1846                             GCancellable *cancellable,
1847                             GAsyncReadyCallback callback,
1848                             gpointer user_data)
1849 {
1850         CamelSessionClass *class;
1851
1852         g_return_if_fail (CAMEL_IS_SESSION (session));
1853         g_return_if_fail (CAMEL_IS_SERVICE (service));
1854
1855         class = CAMEL_SESSION_GET_CLASS (session);
1856         g_return_if_fail (class->authenticate != NULL);
1857
1858         class->authenticate (
1859                 session, service, mechanism, io_priority,
1860                 cancellable, callback, user_data);
1861 }
1862
1863 /**
1864  * camel_session_authenticate_finish:
1865  * @session: a #CamelSession
1866  * @result: a #GAsyncResult
1867  * @error: return location for a #GError, or %NULL
1868  *
1869  * Finishes the operation started with camel_session_authenticate().
1870  *
1871  * If an error occurred, or if authentication was aborted, the function
1872  * sets @error and returns %FALSE.
1873  *
1874  * Returns: %TRUE on success, %FALSE on failure
1875  *
1876  * Since: 3.4
1877  **/
1878 gboolean
1879 camel_session_authenticate_finish (CamelSession *session,
1880                                    GAsyncResult *result,
1881                                    GError **error)
1882 {
1883         CamelSessionClass *class;
1884
1885         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1886         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
1887
1888         class = CAMEL_SESSION_GET_CLASS (session);
1889         g_return_val_if_fail (class->authenticate_finish != NULL, FALSE);
1890
1891         return class->authenticate_finish (session, result, error);
1892 }
1893
1894 /**
1895  * camel_session_forward_to:
1896  * @session: a #CamelSession
1897  * @folder: the #CamelFolder where @message is located
1898  * @message: the #CamelMimeMessage to forward
1899  * @address: the recipient's email address
1900  * @io_priority: the I/O priority for the request
1901  * @cancellable: optional #GCancellable object, or %NULL
1902  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1903  * @user_data: data to pass to the callback function
1904  *
1905  * Asynchronously forwards @message in @folder to the email address(s)
1906  * given by @address.
1907  *
1908  * When the operation is finished, @callback will be called.  You can
1909  * then call camel_session_forward_to_finish() to get the result of the
1910  * operation.
1911  *
1912  * Since: 3.6
1913  **/
1914 void
1915 camel_session_forward_to (CamelSession *session,
1916                           CamelFolder *folder,
1917                           CamelMimeMessage *message,
1918                           const gchar *address,
1919                           gint io_priority,
1920                           GCancellable *cancellable,
1921                           GAsyncReadyCallback callback,
1922                           gpointer user_data)
1923 {
1924         CamelSessionClass *class;
1925
1926         g_return_if_fail (CAMEL_IS_SESSION (session));
1927         g_return_if_fail (CAMEL_IS_FOLDER (folder));
1928         g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
1929         g_return_if_fail (address != NULL);
1930
1931         class = CAMEL_SESSION_GET_CLASS (session);
1932         g_return_if_fail (class->forward_to != NULL);
1933
1934         class->forward_to (
1935                 session, folder, message, address,
1936                 io_priority, cancellable, callback, user_data);
1937 }
1938
1939 /**
1940  * camel_session_forward_to_finish:
1941  * @session: a #CamelSession
1942  * @result: a #GAsyncResult
1943  * @error: return location for a #GError, or %NULL
1944  *
1945  * Finishes the operation started with camel_session_forward_to().
1946  *
1947  * If an error occurred, the function sets @error and returns %FALSE.
1948  *
1949  * Returns: %TRUE on success, %FALSE on failure
1950  *
1951  * Since: 3.6
1952  **/
1953 gboolean
1954 camel_session_forward_to_finish (CamelSession *session,
1955                                  GAsyncResult *result,
1956                                  GError **error)
1957 {
1958         CamelSessionClass *class;
1959
1960         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1961         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
1962
1963         class = CAMEL_SESSION_GET_CLASS (session);
1964         g_return_val_if_fail (class->forward_to_finish != NULL, FALSE);
1965
1966         return class->forward_to_finish (session, result, error);
1967 }
1968