aboutsummaryrefslogtreecommitdiffstats
path: root/libemail-engine
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2012-01-18 00:07:19 +0800
committerMatthew Barnes <mbarnes@redhat.com>2012-01-19 12:48:47 +0800
commit61ae36351b24cc676f60483d576706bf827f2987 (patch)
treec55d9e000efd47fa14865fad2defa79b5ed61ffd /libemail-engine
parent37644b9d257369c5c158121ca4807cafbe844595 (diff)
downloadgsoc2013-evolution-61ae36351b24cc676f60483d576706bf827f2987.tar
gsoc2013-evolution-61ae36351b24cc676f60483d576706bf827f2987.tar.gz
gsoc2013-evolution-61ae36351b24cc676f60483d576706bf827f2987.tar.bz2
gsoc2013-evolution-61ae36351b24cc676f60483d576706bf827f2987.tar.lz
gsoc2013-evolution-61ae36351b24cc676f60483d576706bf827f2987.tar.xz
gsoc2013-evolution-61ae36351b24cc676f60483d576706bf827f2987.tar.zst
gsoc2013-evolution-61ae36351b24cc676f60483d576706bf827f2987.zip
Introduce libemail-engine and libemail-utils.
These libraries are bound for E-D-S so they live at the lowest layer of Evolution for now -- even libeutil can link to them (but please don't). This is the first step toward moving mail handing to a D-Bus service.
Diffstat (limited to 'libemail-engine')
-rw-r--r--libemail-engine/Makefile.am71
-rw-r--r--libemail-engine/e-mail-enums.h74
-rw-r--r--libemail-engine/e-mail-folder-utils.c1666
-rw-r--r--libemail-engine/e-mail-folder-utils.h164
-rw-r--r--libemail-engine/e-mail-junk-filter.c82
-rw-r--r--libemail-engine/e-mail-junk-filter.h74
-rw-r--r--libemail-engine/e-mail-session-utils.c931
-rw-r--r--libemail-engine/e-mail-session-utils.h97
-rw-r--r--libemail-engine/e-mail-session.c1969
-rw-r--r--libemail-engine/e-mail-session.h135
-rw-r--r--libemail-engine/e-mail-store-utils.c385
-rw-r--r--libemail-engine/e-mail-store-utils.h74
-rw-r--r--libemail-engine/e-mail-utils.c1062
-rw-r--r--libemail-engine/e-mail-utils.h56
-rw-r--r--libemail-engine/libemail-engine.pc.in15
-rw-r--r--libemail-engine/mail-config.c294
-rw-r--r--libemail-engine/mail-config.h49
-rw-r--r--libemail-engine/mail-folder-cache.c1841
-rw-r--r--libemail-engine/mail-folder-cache.h156
-rw-r--r--libemail-engine/mail-ops.c1691
-rw-r--r--libemail-engine/mail-ops.h98
-rw-r--r--libemail-engine/mail-tools.c244
-rw-r--r--libemail-engine/mail-tools.h41
23 files changed, 11269 insertions, 0 deletions
diff --git a/libemail-engine/Makefile.am b/libemail-engine/Makefile.am
new file mode 100644
index 0000000000..88a0227aab
--- /dev/null
+++ b/libemail-engine/Makefile.am
@@ -0,0 +1,71 @@
+NULL =
+
+lib_LTLIBRARIES = libemail-engine.la
+
+include $(top_srcdir)/glib-gen.mak
+glib_enum_headers=e-mail-enums.h
+glib_enum_output=e-mail-enumtypes
+glib_enum_define=E_MAIL
+glib_enum_prefix=e_mail
+
+ENUM_GENERATED = e-mail-enumtypes.h e-mail-enumtypes.c
+
+libemail_engine_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ $(EVOLUTION_DATA_SERVER_CFLAGS) \
+ $(GNOME_PLATFORM_CFLAGS) \
+ $(NULL)
+
+libmailengineincludedir = $(privincludedir)/libemail-engine
+libmailengineinclude_HEADERS = \
+ e-mail-enums.h \
+ e-mail-enumtypes.h \
+ e-mail-folder-utils.h \
+ e-mail-junk-filter.h \
+ e-mail-session-utils.h \
+ e-mail-session.h \
+ e-mail-store-utils.h \
+ e-mail-utils.h \
+ mail-config.h \
+ mail-folder-cache.h \
+ mail-ops.h \
+ mail-tools.h \
+ $(NULL)
+
+libemail_engine_la_SOURCES = \
+ $(libmailengineinclude_HEADERS) \
+ e-mail-enumtypes.c \
+ e-mail-folder-utils.c \
+ e-mail-junk-filter.c \
+ e-mail-session-utils.c \
+ e-mail-session.c \
+ e-mail-store-utils.c \
+ e-mail-utils.c \
+ mail-config.c \
+ mail-folder-cache.c \
+ mail-ops.c \
+ mail-tools.c \
+ $(NULL)
+
+libemail_engine_la_LIBADD = \
+ $(top_builddir)/libemail-utils/libemail-utils.la \
+ $(EVOLUTION_DATA_SERVER_LIBS) \
+ $(GNOME_PLATFORM_LIBS) \
+ $(NULL)
+
+libemail_engine_la_LDFLAGS = $(NO_UNDEFINED)
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libemail-engine.pc
+
+BUILT_SOURCES = $(ENUM_GENERATED)
+
+CLEANFILES = $(BUILT_SOURCES)
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+dist-hook:
+ cd $(distdir); rm -f $(BUILT_SOURCES)
+
+-include $(top_srcdir)/git.mk
diff --git a/libemail-engine/e-mail-enums.h b/libemail-engine/e-mail-enums.h
new file mode 100644
index 0000000000..e0ad3ad86f
--- /dev/null
+++ b/libemail-engine/e-mail-enums.h
@@ -0,0 +1,74 @@
+/*
+ * e-mail-enums.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_ENUMS_H
+#define E_MAIL_ENUMS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ E_MAIL_DISPLAY_STYLE_NORMAL,
+ E_MAIL_DISPLAY_STYLE_FULL_HEADERS,
+ E_MAIL_DISPLAY_STYLE_SOURCE
+} EMailDisplayStyle;
+
+typedef enum {
+ E_MAIL_FORWARD_STYLE_ATTACHED,
+ E_MAIL_FORWARD_STYLE_INLINE,
+ E_MAIL_FORWARD_STYLE_QUOTED
+} EMailForwardStyle;
+
+typedef enum {
+ E_MAIL_IMAGE_LOADING_POLICY_NEVER,
+ E_MAIL_IMAGE_LOADING_POLICY_SOMETIMES,
+ E_MAIL_IMAGE_LOADING_POLICY_ALWAYS
+} EMailImageLoadingPolicy;
+
+/* XXX E_MAIL_FOLDER_TEMPLATES is a prime example of why templates
+ * should be a core feature: the mailer now has to know about
+ * this specific plugin, which defeats the purpose of plugins. */
+typedef enum {
+ E_MAIL_LOCAL_FOLDER_INBOX,
+ E_MAIL_LOCAL_FOLDER_DRAFTS,
+ E_MAIL_LOCAL_FOLDER_OUTBOX,
+ E_MAIL_LOCAL_FOLDER_SENT,
+ E_MAIL_LOCAL_FOLDER_TEMPLATES,
+ E_MAIL_LOCAL_FOLDER_LOCAL_INBOX,
+ E_MAIL_NUM_LOCAL_FOLDERS
+} EMailLocalFolder;
+
+typedef enum {
+ E_MAIL_REPLY_STYLE_QUOTED,
+ E_MAIL_REPLY_STYLE_DO_NOT_QUOTE,
+ E_MAIL_REPLY_STYLE_ATTACH,
+ E_MAIL_REPLY_STYLE_OUTLOOK
+} EMailReplyStyle;
+
+typedef enum {
+ E_MAIL_REPLY_TO_SENDER,
+ E_MAIL_REPLY_TO_RECIPIENT,
+ E_MAIL_REPLY_TO_FROM,
+ E_MAIL_REPLY_TO_ALL,
+ E_MAIL_REPLY_TO_LIST
+} EMailReplyType;
+
+G_END_DECLS
+
+#endif /* E_MAIL_ENUMS_H */
diff --git a/libemail-engine/e-mail-folder-utils.c b/libemail-engine/e-mail-folder-utils.c
new file mode 100644
index 0000000000..25754f4f96
--- /dev/null
+++ b/libemail-engine/e-mail-folder-utils.c
@@ -0,0 +1,1666 @@
+/*
+ * e-mail-folder-utils.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-mail-folder-utils.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <libemail-engine/mail-tools.h>
+
+/* X-Mailer header value */
+#define X_MAILER ("Evolution " VERSION SUB_VERSION " " VERSION_COMMENT)
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _AsyncContext {
+ CamelMimeMessage *message;
+ CamelMessageInfo *info;
+ CamelMimePart *part;
+ GHashTable *hash_table;
+ GPtrArray *ptr_array;
+ GFile *destination;
+ gchar *fwd_subject;
+ gchar *message_uid;
+};
+
+static void
+async_context_free (AsyncContext *context)
+{
+ if (context->message != NULL)
+ g_object_unref (context->message);
+
+ if (context->info != NULL)
+ camel_message_info_free (context->info);
+
+ if (context->part != NULL)
+ g_object_unref (context->part);
+
+ if (context->hash_table != NULL)
+ g_hash_table_unref (context->hash_table);
+
+ if (context->ptr_array != NULL)
+ g_ptr_array_unref (context->ptr_array);
+
+ if (context->destination != NULL)
+ g_object_unref (context->destination);
+
+ g_free (context->fwd_subject);
+ g_free (context->message_uid);
+
+ g_slice_free (AsyncContext, context);
+}
+
+static void
+mail_folder_append_message_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ e_mail_folder_append_message_sync (
+ CAMEL_FOLDER (object), context->message,
+ context->info, &context->message_uid,
+ cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+gboolean
+e_mail_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMedium *medium;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+
+ medium = CAMEL_MEDIUM (message);
+
+ camel_operation_push_message (
+ cancellable,
+ _("Saving message to folder '%s'"),
+ camel_folder_get_full_name (folder));
+
+ if (camel_medium_get_header (medium, "X-Mailer") == NULL)
+ camel_medium_set_header (medium, "X-Mailer", X_MAILER);
+
+ camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
+
+ success = camel_folder_append_message_sync (
+ folder, message, info, appended_uid, cancellable, error);
+
+ camel_operation_pop_message (cancellable);
+
+ return success;
+}
+
+void
+e_mail_folder_append_message (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ context = g_slice_new0 (AsyncContext);
+ context->message = g_object_ref (message);
+
+ if (info != NULL)
+ context->info = camel_message_info_ref (info);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (folder), callback, user_data,
+ e_mail_folder_append_message);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, mail_folder_append_message_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_folder_append_message_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ gchar **appended_uid,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (folder),
+ e_mail_folder_append_message), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (appended_uid != NULL) {
+ *appended_uid = context->message_uid;
+ context->message_uid = NULL;
+ }
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_folder_build_attachment_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ context->part = e_mail_folder_build_attachment_sync (
+ CAMEL_FOLDER (object), context->ptr_array,
+ &context->fwd_subject, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+CamelMimePart *
+e_mail_folder_build_attachment_sync (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gchar **fwd_subject,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GHashTable *hash_table;
+ CamelMimeMessage *message;
+ CamelMimePart *part;
+ const gchar *uid;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (message_uids != NULL, NULL);
+
+ /* Need at least one message UID to make an attachment. */
+ g_return_val_if_fail (message_uids->len > 0, NULL);
+
+ hash_table = e_mail_folder_get_multiple_messages_sync (
+ folder, message_uids, cancellable, error);
+
+ if (hash_table == NULL)
+ return NULL;
+
+ /* Create the forward subject from the first message. */
+
+ uid = g_ptr_array_index (message_uids, 0);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ message = g_hash_table_lookup (hash_table, uid);
+ g_return_val_if_fail (message != NULL, NULL);
+
+ if (fwd_subject != NULL)
+ *fwd_subject = mail_tool_generate_forward_subject (message);
+
+ if (message_uids->len == 1) {
+ part = mail_tool_make_message_attachment (message);
+
+ } else {
+ CamelMultipart *multipart;
+ guint ii;
+
+ multipart = camel_multipart_new ();
+ camel_data_wrapper_set_mime_type (
+ CAMEL_DATA_WRAPPER (multipart), "multipart/digest");
+ camel_multipart_set_boundary (multipart, NULL);
+
+ for (ii = 0; ii < message_uids->len; ii++) {
+ uid = g_ptr_array_index (message_uids, ii);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ message = g_hash_table_lookup (hash_table, uid);
+ g_return_val_if_fail (message != NULL, NULL);
+
+ part = mail_tool_make_message_attachment (message);
+ camel_multipart_add_part (multipart, part);
+ g_object_unref (part);
+ }
+
+ part = camel_mime_part_new ();
+
+ camel_medium_set_content (
+ CAMEL_MEDIUM (part),
+ CAMEL_DATA_WRAPPER (multipart));
+
+ camel_mime_part_set_description (
+ part, _("Forwarded messages"));
+
+ g_object_unref (multipart);
+ }
+
+ g_hash_table_unref (hash_table);
+
+ return part;
+}
+
+void
+e_mail_folder_build_attachment (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message_uids != NULL);
+
+ /* Need at least one message UID to make an attachment. */
+ g_return_if_fail (message_uids->len > 0);
+
+ context = g_slice_new0 (AsyncContext);
+ context->ptr_array = g_ptr_array_ref (message_uids);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (folder), callback, user_data,
+ e_mail_folder_build_attachment);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, mail_folder_build_attachment_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+CamelMimePart *
+e_mail_folder_build_attachment_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ gchar **fwd_subject,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (folder),
+ e_mail_folder_build_attachment), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ if (fwd_subject != NULL) {
+ *fwd_subject = context->fwd_subject;
+ context->fwd_subject = NULL;
+ }
+
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (context->part), NULL);
+
+ return g_object_ref (context->part);
+}
+
+static void
+mail_folder_find_duplicate_messages_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ context->hash_table = e_mail_folder_find_duplicate_messages_sync (
+ CAMEL_FOLDER (object), context->ptr_array,
+ cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+GHashTable *
+e_mail_folder_find_duplicate_messages_sync (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GQueue trash = G_QUEUE_INIT;
+ GHashTable *hash_table;
+ GHashTable *unique_ids;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (message_uids != NULL, NULL);
+
+ /* hash_table = { MessageUID : CamelMessage } */
+ hash_table = e_mail_folder_get_multiple_messages_sync (
+ folder, message_uids, cancellable, error);
+
+ if (hash_table == NULL)
+ return NULL;
+
+ camel_operation_push_message (
+ cancellable, _("Scanning messages for duplicates"));
+
+ unique_ids = g_hash_table_new_full (
+ (GHashFunc) g_int64_hash,
+ (GEqualFunc) g_int64_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ g_hash_table_iter_init (&iter, hash_table);
+
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ const CamelSummaryMessageID *message_id;
+ CamelDataWrapper *content;
+ CamelMessageFlags flags;
+ CamelMessageInfo *info;
+ CamelStream *stream;
+ GByteArray *buffer;
+ gboolean duplicate;
+ gssize n_bytes;
+ gchar *digest;
+
+ info = camel_folder_get_message_info (folder, key);
+ message_id = camel_message_info_message_id (info);
+ flags = camel_message_info_flags (info);
+
+ /* Skip messages marked for deletion. */
+ if (flags & CAMEL_MESSAGE_DELETED) {
+ g_queue_push_tail (&trash, key);
+ camel_message_info_free (info);
+ continue;
+ }
+
+ /* Generate a digest string from the message's content. */
+
+ content = camel_medium_get_content (CAMEL_MEDIUM (value));
+
+ if (content == NULL) {
+ g_queue_push_tail (&trash, key);
+ camel_message_info_free (info);
+ continue;
+ }
+
+ stream = camel_stream_mem_new ();
+
+ n_bytes = camel_data_wrapper_decode_to_stream_sync (
+ content, stream, cancellable, error);
+
+ if (n_bytes < 0) {
+ camel_message_info_free (info);
+ g_object_unref (stream);
+ goto fail;
+ }
+
+ /* The CamelStreamMem owns the buffer. */
+ buffer = camel_stream_mem_get_byte_array (
+ CAMEL_STREAM_MEM (stream));
+ g_return_val_if_fail (buffer != NULL, NULL);
+
+ digest = g_compute_checksum_for_data (
+ G_CHECKSUM_SHA256, buffer->data, buffer->len);
+
+ g_object_unref (stream);
+
+ /* Determine if the message a duplicate. */
+
+ value = g_hash_table_lookup (unique_ids, &message_id->id.id);
+ duplicate = (value != NULL) && g_str_equal (digest, value);
+
+ if (duplicate)
+ g_free (digest);
+ else {
+ gint64 *v_int64;
+
+ /* XXX Might be better to create a GArray
+ * of 64-bit integers and have the hash
+ * table keys point to array elements. */
+ v_int64 = g_new0 (gint64, 1);
+ *v_int64 = (gint64) message_id->id.id;
+
+ g_hash_table_insert (unique_ids, v_int64, digest);
+ g_queue_push_tail (&trash, key);
+ }
+
+ camel_message_info_free (info);
+ }
+
+ /* Delete all non-duplicate messages from the hash table. */
+ while ((key = g_queue_pop_head (&trash)) != NULL)
+ g_hash_table_remove (hash_table, key);
+
+ goto exit;
+
+fail:
+ g_hash_table_destroy (hash_table);
+ hash_table = NULL;
+
+exit:
+ camel_operation_pop_message (cancellable);
+
+ g_hash_table_destroy (unique_ids);
+
+ return hash_table;
+}
+
+void
+e_mail_folder_find_duplicate_messages (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message_uids != NULL);
+
+ context = g_slice_new0 (AsyncContext);
+ context->ptr_array = g_ptr_array_ref (message_uids);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (folder), callback, user_data,
+ e_mail_folder_find_duplicate_messages);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, mail_folder_find_duplicate_messages_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+GHashTable *
+e_mail_folder_find_duplicate_messages_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (folder),
+ e_mail_folder_find_duplicate_messages), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ return g_hash_table_ref (context->hash_table);
+}
+
+static void
+mail_folder_get_multiple_messages_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ context->hash_table = e_mail_folder_get_multiple_messages_sync (
+ CAMEL_FOLDER (object), context->ptr_array,
+ cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+GHashTable *
+e_mail_folder_get_multiple_messages_sync (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GHashTable *hash_table;
+ CamelMimeMessage *message;
+ guint ii;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (message_uids != NULL, NULL);
+
+ camel_operation_push_message (
+ cancellable,
+ ngettext (
+ "Retrieving %d message",
+ "Retrieving %d messages",
+ message_uids->len),
+ message_uids->len);
+
+ hash_table = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ /* This is an all or nothing operation. Destroy the
+ * hash table if we fail to retrieve any message. */
+
+ for (ii = 0; ii < message_uids->len; ii++) {
+ const gchar *uid;
+ gint percent;
+
+ uid = g_ptr_array_index (message_uids, ii);
+ percent = ((ii + 1) * 100) / message_uids->len;
+
+ message = camel_folder_get_message_sync (
+ folder, uid, cancellable, error);
+
+ camel_operation_progress (cancellable, percent);
+
+ if (CAMEL_IS_MIME_MESSAGE (message)) {
+ g_hash_table_insert (
+ hash_table, g_strdup (uid), message);
+ } else {
+ g_hash_table_destroy (hash_table);
+ hash_table = NULL;
+ break;
+ }
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ return hash_table;
+}
+
+void
+e_mail_folder_get_multiple_messages (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message_uids != NULL);
+
+ context = g_slice_new0 (AsyncContext);
+ context->ptr_array = g_ptr_array_ref (message_uids);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (folder), callback, user_data,
+ e_mail_folder_get_multiple_messages);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, mail_folder_get_multiple_messages_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+GHashTable *
+e_mail_folder_get_multiple_messages_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (folder),
+ e_mail_folder_get_multiple_messages), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ return g_hash_table_ref (context->hash_table);
+}
+
+static void
+mail_folder_remove_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+
+ e_mail_folder_remove_sync (
+ CAMEL_FOLDER (object), cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+static gboolean
+mail_folder_remove_recursive (CamelStore *store,
+ CamelFolderInfo *folder_info,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ while (folder_info != NULL) {
+ CamelFolder *folder;
+
+ if (folder_info->child != NULL) {
+ success = mail_folder_remove_recursive (
+ store, folder_info->child, cancellable, error);
+ if (!success)
+ break;
+ }
+
+ folder = camel_store_get_folder_sync (
+ store, folder_info->full_name, 0, cancellable, error);
+ if (folder == NULL) {
+ success = FALSE;
+ break;
+ }
+
+ if (!CAMEL_IS_VEE_FOLDER (folder)) {
+ GPtrArray *uids;
+ guint ii;
+
+ /* Delete every message in this folder,
+ * then expunge it. */
+
+ camel_folder_freeze (folder);
+
+ uids = camel_folder_get_uids (folder);
+
+ for (ii = 0; ii < uids->len; ii++)
+ camel_folder_delete_message (
+ folder, uids->pdata[ii]);
+
+ camel_folder_free_uids (folder, uids);
+
+ success = camel_folder_synchronize_sync (
+ folder, TRUE, cancellable, error);
+
+ camel_folder_thaw (folder);
+ }
+
+ g_object_unref (folder);
+
+ if (!success)
+ break;
+
+ /* If the store supports subscriptions,
+ * then unsubscribe from this folder. */
+ if (CAMEL_IS_SUBSCRIBABLE (store)) {
+ success = camel_subscribable_unsubscribe_folder_sync (
+ CAMEL_SUBSCRIBABLE (store),
+ folder_info->full_name,
+ cancellable, error);
+ if (!success)
+ break;
+ }
+
+ success = camel_store_delete_folder_sync (
+ store, folder_info->full_name, cancellable, error);
+ if (!success)
+ break;
+
+ folder_info = folder_info->next;
+ }
+
+ return success;
+}
+
+static void
+follow_cancel_cb (GCancellable *cancellable,
+ GCancellable *transparent_cancellable)
+{
+ g_cancellable_cancel (transparent_cancellable);
+}
+
+gboolean
+e_mail_folder_remove_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderInfo *folder_info;
+ CamelFolderInfo *to_remove;
+ CamelFolderInfo *next = NULL;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ gboolean success = TRUE;
+ GCancellable *transparent_cancellable = NULL;
+ gulong cbid = 0;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ folder_info = camel_store_get_folder_info_sync (
+ parent_store, full_name,
+ CAMEL_STORE_FOLDER_INFO_FAST |
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE |
+ CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
+ cancellable, error);
+
+ if (folder_info == NULL)
+ return FALSE;
+
+ to_remove = folder_info;
+
+ /* For cases when the top-level folder_info contains siblings,
+ * such as when full_name contains a wildcard letter, compare
+ * the folder name against folder_info->full_name to avoid
+ * removing more folders than requested. */
+ if (folder_info->next != NULL) {
+ while (to_remove != NULL) {
+ if (g_strcmp0 (to_remove->full_name, full_name) == 0)
+ break;
+ to_remove = to_remove->next;
+ }
+
+ /* XXX Should we set a GError and return FALSE here? */
+ if (to_remove == NULL) {
+ g_warning (
+ "%s: Failed to find folder '%s'",
+ G_STRFUNC, full_name);
+ camel_store_free_folder_info (
+ parent_store, folder_info);
+ return TRUE;
+ }
+
+ /* Prevent iterating over siblings. */
+ next = to_remove->next;
+ to_remove->next = NULL;
+ }
+
+ camel_operation_push_message (
+ cancellable, _("Removing folder '%s'"),
+ camel_folder_get_full_name (folder));
+
+ if (cancellable) {
+ transparent_cancellable = g_cancellable_new ();
+ cbid = g_cancellable_connect (cancellable, G_CALLBACK (follow_cancel_cb), transparent_cancellable, NULL);
+ }
+
+ success = mail_folder_remove_recursive (
+ parent_store, to_remove, transparent_cancellable, error);
+
+ if (transparent_cancellable) {
+ g_cancellable_disconnect (cancellable, cbid);
+ g_object_unref (transparent_cancellable);
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ /* Restore the folder_info tree to its original
+ * state so we don't leak folder_info nodes. */
+ to_remove->next = next;
+
+ camel_store_free_folder_info (parent_store, folder_info);
+
+ return success;
+}
+
+void
+e_mail_folder_remove (CamelFolder *folder,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (folder), callback,
+ user_data, e_mail_folder_remove);
+
+ g_simple_async_result_run_in_thread (
+ simple, mail_folder_remove_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_folder_remove_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (folder),
+ e_mail_folder_remove), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_folder_remove_attachments_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ e_mail_folder_remove_attachments_sync (
+ CAMEL_FOLDER (object), context->ptr_array,
+ cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+/* Helper for e_mail_folder_remove_attachments_sync() */
+static gboolean
+mail_folder_strip_message (CamelFolder *folder,
+ CamelMimeMessage *message,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapper *content;
+ CamelMultipart *multipart;
+ gboolean modified = FALSE;
+ gboolean success = TRUE;
+ guint ii, n_parts;
+
+ content = camel_medium_get_content (CAMEL_MEDIUM (message));
+
+ if (!CAMEL_IS_MULTIPART (content))
+ return TRUE;
+
+ multipart = CAMEL_MULTIPART (content);
+ n_parts = camel_multipart_get_number (multipart);
+
+ /* Replace MIME parts with "attachment" or "inline" dispositions
+ * with a small "text/plain" part saying the file was removed. */
+ for (ii = 0; ii < n_parts; ii++) {
+ CamelMimePart *mime_part;
+ const gchar *disposition;
+ gboolean is_attachment;
+
+ mime_part = camel_multipart_get_part (multipart, ii);
+ disposition = camel_mime_part_get_disposition (mime_part);
+
+ is_attachment =
+ (g_strcmp0 (disposition, "attachment") == 0) ||
+ (g_strcmp0 (disposition, "inline") == 0);
+
+ if (is_attachment) {
+ const gchar *filename;
+ const gchar *content_type;
+ gchar *content;
+
+ disposition = "inline";
+ content_type = "text/plain";
+ filename = camel_mime_part_get_filename (mime_part);
+
+ if (filename != NULL && *filename != '\0')
+ content = g_strdup_printf (
+ _("File \"%s\" has been removed."),
+ filename);
+ else
+ content = g_strdup (
+ _("File has been removed."));
+
+ camel_mime_part_set_content (
+ mime_part, content,
+ strlen (content), content_type);
+ camel_mime_part_set_content_type (
+ mime_part, content_type);
+ camel_mime_part_set_disposition (
+ mime_part, disposition);
+
+ modified = TRUE;
+ }
+ }
+
+ /* Append the modified message with removed attachments to
+ * the folder and mark the original message for deletion. */
+ if (modified) {
+ CamelMessageInfo *orig_info;
+ CamelMessageInfo *copy_info;
+ CamelMessageFlags flags;
+
+ orig_info =
+ camel_folder_get_message_info (folder, message_uid);
+ copy_info =
+ camel_message_info_new_from_header (
+ NULL, CAMEL_MIME_PART (message)->headers);
+
+ flags = camel_folder_get_message_flags (folder, message_uid);
+ camel_message_info_set_flags (copy_info, flags, flags);
+
+ success = camel_folder_append_message_sync (
+ folder, message, copy_info, NULL, cancellable, error);
+ if (success)
+ camel_message_info_set_flags (
+ orig_info,
+ CAMEL_MESSAGE_DELETED,
+ CAMEL_MESSAGE_DELETED);
+
+ camel_folder_free_message_info (folder, orig_info);
+ camel_message_info_free (copy_info);
+ }
+
+ return success;
+}
+
+gboolean
+e_mail_folder_remove_attachments_sync (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+ guint ii;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (message_uids != NULL, FALSE);
+
+ camel_folder_freeze (folder);
+
+ camel_operation_push_message (cancellable, _("Removing attachments"));
+
+ for (ii = 0; success && ii < message_uids->len; ii++) {
+ CamelMimeMessage *message;
+ const gchar *uid;
+ gint percent;
+
+ uid = g_ptr_array_index (message_uids, ii);
+
+ message = camel_folder_get_message_sync (
+ folder, uid, cancellable, error);
+
+ if (message == NULL) {
+ success = FALSE;
+ break;
+ }
+
+ success = mail_folder_strip_message (
+ folder, message, uid, cancellable, error);
+
+ percent = ((ii + 1) * 100) / message_uids->len;
+ camel_operation_progress (cancellable, percent);
+
+ g_object_unref (message);
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ if (success)
+ camel_folder_synchronize_sync (
+ folder, FALSE, cancellable, error);
+
+ camel_folder_thaw (folder);
+
+ return success;
+}
+
+void
+e_mail_folder_remove_attachments (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message_uids != NULL);
+
+ context = g_slice_new0 (AsyncContext);
+ context->ptr_array = g_ptr_array_ref (message_uids);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (folder), callback, user_data,
+ e_mail_folder_remove_attachments);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, mail_folder_remove_attachments_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_folder_remove_attachments_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (folder),
+ e_mail_folder_remove_attachments), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_folder_save_messages_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ e_mail_folder_save_messages_sync (
+ CAMEL_FOLDER (object), context->ptr_array,
+ context->destination, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+/* Helper for e_mail_folder_save_messages_sync() */
+static void
+mail_folder_save_prepare_part (CamelMimePart *mime_part)
+{
+ CamelDataWrapper *content;
+
+ content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
+
+ if (content == NULL)
+ return;
+
+ if (CAMEL_IS_MULTIPART (content)) {
+ guint n_parts, ii;
+
+ n_parts = camel_multipart_get_number (
+ CAMEL_MULTIPART (content));
+ for (ii = 0; ii < n_parts; ii++) {
+ mime_part = camel_multipart_get_part (
+ CAMEL_MULTIPART (content), ii);
+ mail_folder_save_prepare_part (mime_part);
+ }
+
+ } else if (CAMEL_IS_MIME_MESSAGE (content)) {
+ mail_folder_save_prepare_part (CAMEL_MIME_PART (content));
+
+ } else {
+ CamelContentType *type;
+
+ /* Save textual parts as 8-bit, not encoded. */
+ type = camel_data_wrapper_get_mime_type_field (content);
+ if (camel_content_type_is (type, "text", "*"))
+ camel_mime_part_set_encoding (
+ mime_part, CAMEL_TRANSFER_ENCODING_8BIT);
+ }
+}
+
+gboolean
+e_mail_folder_save_messages_sync (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GFile *destination,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GFileOutputStream *file_output_stream;
+ GByteArray *byte_array;
+ CamelStream *base_stream;
+ gboolean success = TRUE;
+ guint ii;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (message_uids != NULL, FALSE);
+ g_return_val_if_fail (G_IS_FILE (destination), FALSE);
+
+ /* Need at least one message UID to save. */
+ g_return_val_if_fail (message_uids->len > 0, FALSE);
+
+ camel_operation_push_message (
+ cancellable, ngettext (
+ "Saving %d message",
+ "Saving %d messages",
+ message_uids->len),
+ message_uids->len);
+
+ file_output_stream = g_file_replace (
+ destination, NULL, FALSE,
+ G_FILE_CREATE_PRIVATE |
+ G_FILE_CREATE_REPLACE_DESTINATION,
+ cancellable, error);
+
+ if (file_output_stream == NULL) {
+ camel_operation_pop_message (cancellable);
+ return FALSE;
+ }
+
+ /* CamelStreamMem takes ownership of the GByteArray. */
+ byte_array = g_byte_array_new ();
+ base_stream = camel_stream_mem_new_with_byte_array (byte_array);
+
+ for (ii = 0; ii < message_uids->len; ii++) {
+ CamelMimeMessage *message;
+ CamelMimeFilter *filter;
+ CamelStream *stream;
+ const gchar *uid;
+ gchar *from_line;
+ gint percent;
+ gint retval;
+
+ uid = g_ptr_array_index (message_uids, ii);
+
+ message = camel_folder_get_message_sync (
+ folder, uid, cancellable, error);
+ if (message == NULL) {
+ success = FALSE;
+ goto exit;
+ }
+
+ mail_folder_save_prepare_part (CAMEL_MIME_PART (message));
+
+ from_line = camel_mime_message_build_mbox_from (message);
+ g_return_val_if_fail (from_line != NULL, FALSE);
+
+ success = g_output_stream_write_all (
+ G_OUTPUT_STREAM (file_output_stream),
+ from_line, strlen (from_line), NULL,
+ cancellable, error);
+
+ g_free (from_line);
+
+ if (!success) {
+ g_object_unref (message);
+ goto exit;
+ }
+
+ filter = camel_mime_filter_from_new ();
+ stream = camel_stream_filter_new (base_stream);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
+
+ retval = camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message),
+ stream, cancellable, error);
+
+ g_object_unref (filter);
+ g_object_unref (stream);
+
+ if (retval == -1) {
+ g_object_unref (message);
+ goto exit;
+ }
+
+ g_byte_array_append (byte_array, (guint8 *) "\n", 1);
+
+ success = g_output_stream_write_all (
+ G_OUTPUT_STREAM (file_output_stream),
+ byte_array->data, byte_array->len,
+ NULL, cancellable, error);
+
+ if (!success) {
+ g_object_unref (message);
+ goto exit;
+ }
+
+ percent = ((ii + 1) * 100) / message_uids->len;
+ camel_operation_progress (cancellable, percent);
+
+ /* Flush the buffer for the next message.
+ * For memory streams this never fails. */
+ g_seekable_seek (
+ G_SEEKABLE (base_stream),
+ 0, G_SEEK_SET, NULL, NULL);
+
+ g_object_unref (message);
+ }
+
+exit:
+ g_object_unref (file_output_stream);
+ g_object_unref (base_stream);
+
+ camel_operation_pop_message (cancellable);
+
+ if (!success) {
+ /* Try deleting the destination file. */
+ g_file_delete (destination, NULL, NULL);
+ }
+
+ return success;
+}
+
+void
+e_mail_folder_save_messages (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GFile *destination,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message_uids != NULL);
+ g_return_if_fail (G_IS_FILE (destination));
+
+ /* Need at least one message UID to save. */
+ g_return_if_fail (message_uids->len > 0);
+
+ context = g_slice_new0 (AsyncContext);
+ context->ptr_array = g_ptr_array_ref (message_uids);
+ context->destination = g_object_ref (destination);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (folder), callback, user_data,
+ e_mail_folder_save_messages);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, mail_folder_save_messages_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_folder_save_messages_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (folder),
+ e_mail_folder_save_messages), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+/**
+ * e_mail_folder_uri_build:
+ * @store: a #CamelStore
+ * @folder_name: a folder name
+ *
+ * Builds a folder URI string from @store and @folder_name.
+ *
+ * Returns: a newly-allocated folder URI string
+ **/
+gchar *
+e_mail_folder_uri_build (CamelStore *store,
+ const gchar *folder_name)
+{
+ const gchar *uid;
+ gchar *encoded_name;
+ gchar *encoded_uid;
+ gchar *uri;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (folder_name != NULL, NULL);
+
+ /* Skip the leading slash, if present. */
+ if (*folder_name == '/')
+ folder_name++;
+
+ uid = camel_service_get_uid (CAMEL_SERVICE (store));
+
+ encoded_uid = camel_url_encode (uid, ":;@/");
+ encoded_name = camel_url_encode (folder_name, "#");
+
+ uri = g_strdup_printf ("folder://%s/%s", encoded_uid, encoded_name);
+
+ g_free (encoded_uid);
+ g_free (encoded_name);
+
+ return uri;
+}
+
+/**
+ * e_mail_folder_uri_parse:
+ * @session: a #CamelSession
+ * @folder_uri: a folder URI
+ * @out_store: return location for a #CamelStore, or %NULL
+ * @out_folder_name: return location for a folder name, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Parses a folder URI generated by e_mail_folder_uri_build() and
+ * returns the corresponding #CamelStore instance in @out_store and
+ * folder name string in @out_folder_name. If the URI is malformed
+ * or no corresponding store exists, the function sets @error and
+ * returns %FALSE.
+ *
+ * If the function is able to parse the URI, the #CamelStore instance
+ * set in @out_store should be unreferenced with g_object_unref() when
+ * done with it, and the folder name string set in @out_folder_name
+ * should be freed with g_free().
+ *
+ * The function also handles older style URIs, such as ones where the
+ * #CamelStore's #CamelStore::uri string was embedded directly in the
+ * folder URI, and account-based URIs that used an "email://" prefix.
+ *
+ * Returns: %TRUE if @folder_uri could be parsed, %FALSE otherwise
+ **/
+gboolean
+e_mail_folder_uri_parse (CamelSession *session,
+ const gchar *folder_uri,
+ CamelStore **out_store,
+ gchar **out_folder_name,
+ GError **error)
+{
+ CamelURL *url;
+ CamelService *service = NULL;
+ gchar *folder_name = NULL;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (folder_uri != NULL, FALSE);
+
+ url = camel_url_new (folder_uri, error);
+ if (url == NULL)
+ return FALSE;
+
+ /* Current URI Format: 'folder://' STORE_UID '/' FOLDER_PATH */
+ if (g_strcmp0 (url->protocol, "folder") == 0) {
+
+ if (url->host != NULL) {
+ gchar *uid;
+
+ if (url->user == NULL || *url->user == '\0')
+ uid = g_strdup (url->host);
+ else
+ uid = g_strconcat (
+ url->user, "@", url->host, NULL);
+
+ service = camel_session_get_service (session, uid);
+ g_free (uid);
+ }
+
+ if (url->path != NULL && *url->path == '/')
+ folder_name = camel_url_decode_path (url->path + 1);
+
+ /* This style was used to reference accounts by UID before
+ * CamelServices themselves had UIDs. Some examples are:
+ *
+ * Special cases:
+ *
+ * 'email://local@local/' FOLDER_PATH
+ * 'email://vfolder@local/' FOLDER_PATH
+ *
+ * General case:
+ *
+ * 'email://' ACCOUNT_UID '/' FOLDER_PATH
+ *
+ * Note: ACCOUNT_UID is now equivalent to STORE_UID, and
+ * the STORE_UIDs for the special cases are 'local'
+ * and 'vfolder'.
+ */
+ } else if (g_strcmp0 (url->protocol, "email") == 0) {
+ gchar *uid = NULL;
+
+ /* Handle the special cases. */
+ if (g_strcmp0 (url->host, "local") == 0) {
+ if (g_strcmp0 (url->user, "local") == 0)
+ uid = g_strdup ("local");
+ if (g_strcmp0 (url->user, "vfolder") == 0)
+ uid = g_strdup ("vfolder");
+ }
+
+ /* Handle the general case. */
+ if (uid == NULL && url->host != NULL) {
+ if (url->user == NULL)
+ uid = g_strdup (url->host);
+ else
+ uid = g_strdup_printf (
+ "%s@%s", url->user, url->host);
+ }
+
+ if (uid != NULL) {
+ service = camel_session_get_service (session, uid);
+ g_free (uid);
+ }
+
+ if (url->path != NULL && *url->path == '/')
+ folder_name = camel_url_decode_path (url->path + 1);
+
+ /* CamelFolderInfo URIs used to embed the store's URI, so the
+ * folder name is appended as either a path part or a fragment
+ * part, depending whether the store's URI used the path part.
+ * To determine which it is, you have to check the provider
+ * flags for CAMEL_URL_FRAGMENT_IS_PATH. */
+ } else {
+ service = camel_session_get_service_by_url (
+ session, url, CAMEL_PROVIDER_STORE);
+
+ if (CAMEL_IS_STORE (service)) {
+ CamelProvider *provider;
+
+ provider = camel_service_get_provider (service);
+
+ if (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)
+ folder_name = g_strdup (url->fragment);
+ else if (url->path != NULL && *url->path == '/')
+ folder_name = g_strdup (url->path + 1);
+ }
+ }
+
+ if (CAMEL_IS_STORE (service) && folder_name != NULL) {
+ if (out_store != NULL)
+ *out_store = g_object_ref (service);
+
+ if (out_folder_name != NULL) {
+ *out_folder_name = folder_name;
+ folder_name = NULL;
+ }
+
+ success = TRUE;
+ } else {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID,
+ _("Invalid folder URI '%s'"),
+ folder_uri);
+ }
+
+ g_free (folder_name);
+
+ camel_url_free (url);
+
+ return success;
+}
+
+/**
+ * e_mail_folder_uri_equal:
+ * @session: a #CamelSession
+ * @folder_uri_a: a folder URI
+ * @folder_uri_b: another folder URI
+ *
+ * Compares two folder URIs for equality. If either URI is invalid,
+ * the function returns %FALSE.
+ *
+ * Returns: %TRUE if the URIs are equal, %FALSE if not
+ **/
+gboolean
+e_mail_folder_uri_equal (CamelSession *session,
+ const gchar *folder_uri_a,
+ const gchar *folder_uri_b)
+{
+ CamelStore *store_a;
+ CamelStore *store_b;
+ CamelStoreClass *class;
+ gchar *folder_name_a;
+ gchar *folder_name_b;
+ gboolean success_a;
+ gboolean success_b;
+ gboolean equal = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (folder_uri_a != NULL, FALSE);
+ g_return_val_if_fail (folder_uri_b != NULL, FALSE);
+
+ success_a = e_mail_folder_uri_parse (
+ session, folder_uri_a, &store_a, &folder_name_a, NULL);
+
+ success_b = e_mail_folder_uri_parse (
+ session, folder_uri_b, &store_b, &folder_name_b, NULL);
+
+ if (!success_a || !success_b)
+ goto exit;
+
+ if (store_a != store_b)
+ goto exit;
+
+ /* Doesn't matter which store we use since they're the same. */
+ class = CAMEL_STORE_GET_CLASS (store_a);
+ g_return_val_if_fail (class->compare_folder_name != NULL, FALSE);
+
+ equal = class->compare_folder_name (folder_name_a, folder_name_b);
+
+exit:
+ if (success_a) {
+ g_object_unref (store_a);
+ g_free (folder_name_a);
+ }
+
+ if (success_b) {
+ g_object_unref (store_b);
+ g_free (folder_name_b);
+ }
+
+ return equal;
+}
+
+/**
+ * e_mail_folder_uri_from_folder:
+ * @folder: a #CamelFolder
+ *
+ * Convenience function for building a folder URI from a #CamelFolder.
+ * Free the returned URI string with g_free().
+ *
+ * Returns: a newly-allocated folder URI string
+ **/
+gchar *
+e_mail_folder_uri_from_folder (CamelFolder *folder)
+{
+ CamelStore *store;
+ const gchar *folder_name;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ store = camel_folder_get_parent_store (folder);
+ folder_name = camel_folder_get_full_name (folder);
+
+ return e_mail_folder_uri_build (store, folder_name);
+}
+
+/**
+ * e_mail_folder_uri_to_markup:
+ * @session: a #CamelSession
+ * @folder_uri: a folder URI
+ * @error: return location for a #GError, or %NULL
+ *
+ * Converts @folder_uri to a markup string suitable for displaying to users.
+ * The string consists of the #CamelStore display name (in bold), followed
+ * by the folder path. If the URI is malformed or no corresponding store
+ * exists, the function sets @error and returns %NULL. Free the returned
+ * string with g_free().
+ *
+ * Returns: a newly-allocated markup string, or %NULL
+ **/
+gchar *
+e_mail_folder_uri_to_markup (CamelSession *session,
+ const gchar *folder_uri,
+ GError **error)
+{
+ CamelStore *store = NULL;
+ const gchar *display_name;
+ gchar *folder_name = NULL;
+ gchar *markup;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+ g_return_val_if_fail (folder_uri != NULL, NULL);
+
+ success = e_mail_folder_uri_parse (
+ session, folder_uri, &store, &folder_name, error);
+
+ if (!success)
+ return NULL;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (folder_name != NULL, NULL);
+
+ display_name = camel_service_get_display_name (CAMEL_SERVICE (store));
+
+ markup = g_markup_printf_escaped (
+ "<b>%s</b> : %s", display_name, folder_name);
+
+ g_object_unref (store);
+ g_free (folder_name);
+
+ return markup;
+}
diff --git a/libemail-engine/e-mail-folder-utils.h b/libemail-engine/e-mail-folder-utils.h
new file mode 100644
index 0000000000..9e8dd0f050
--- /dev/null
+++ b/libemail-engine/e-mail-folder-utils.h
@@ -0,0 +1,164 @@
+/*
+ * e-mail-folder-utils.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_FOLDER_UTILS_H
+#define E_MAIL_FOLDER_UTILS_H
+
+/* CamelFolder wrappers with Evolution-specific policies. */
+
+#include <camel/camel.h>
+
+G_BEGIN_DECLS
+
+gboolean e_mail_folder_append_message_sync
+ (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_folder_append_message (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_folder_append_message_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ gchar **appended_uid,
+ GError **error);
+
+CamelMimePart * e_mail_folder_build_attachment_sync
+ (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gchar **fwd_subject,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_folder_build_attachment (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelMimePart * e_mail_folder_build_attachment_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ gchar **fwd_subject,
+ GError **error);
+
+GHashTable * e_mail_folder_find_duplicate_messages_sync
+ (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_folder_find_duplicate_messages
+ (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GHashTable * e_mail_folder_find_duplicate_messages_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+
+GHashTable * e_mail_folder_get_multiple_messages_sync
+ (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_folder_get_multiple_messages
+ (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GHashTable * e_mail_folder_get_multiple_messages_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+
+gboolean e_mail_folder_remove_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_folder_remove (CamelFolder *folder,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_folder_remove_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+
+gboolean e_mail_folder_remove_attachments_sync
+ (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_folder_remove_attachments
+ (CamelFolder *folder,
+ GPtrArray *message_uids,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_folder_remove_attachments_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+
+gboolean e_mail_folder_save_messages_sync
+ (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GFile *destination,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_folder_save_messages (CamelFolder *folder,
+ GPtrArray *message_uids,
+ GFile *destination,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_folder_save_messages_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+
+gchar * e_mail_folder_uri_build (CamelStore *store,
+ const gchar *folder_name);
+gboolean e_mail_folder_uri_parse (CamelSession *session,
+ const gchar *folder_uri,
+ CamelStore **out_store,
+ gchar **out_folder_name,
+ GError **error);
+gboolean e_mail_folder_uri_equal (CamelSession *session,
+ const gchar *folder_uri_a,
+ const gchar *folder_uri_b);
+gchar * e_mail_folder_uri_from_folder (CamelFolder *folder);
+gchar * e_mail_folder_uri_to_markup (CamelSession *session,
+ const gchar *folder_uri,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_FOLDER_UTILS_H */
diff --git a/libemail-engine/e-mail-junk-filter.c b/libemail-engine/e-mail-junk-filter.c
new file mode 100644
index 0000000000..fda8efb163
--- /dev/null
+++ b/libemail-engine/e-mail-junk-filter.c
@@ -0,0 +1,82 @@
+/*
+ * e-mail-junk-filter.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-junk-filter.h"
+
+#include <libemail-engine/e-mail-session.h>
+
+G_DEFINE_ABSTRACT_TYPE (
+ EMailJunkFilter,
+ e_mail_junk_filter,
+ E_TYPE_EXTENSION)
+
+static void
+e_mail_junk_filter_class_init (EMailJunkFilterClass *class)
+{
+ EExtensionClass *extension_class;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_MAIL_SESSION;
+}
+
+static void
+e_mail_junk_filter_init (EMailJunkFilter *junk_filter)
+{
+}
+
+gboolean
+e_mail_junk_filter_available (EMailJunkFilter *junk_filter)
+{
+ EMailJunkFilterClass *class;
+
+ g_return_val_if_fail (E_IS_MAIL_JUNK_FILTER (junk_filter), FALSE);
+
+ class = E_MAIL_JUNK_FILTER_GET_CLASS (junk_filter);
+ g_return_val_if_fail (class->available != NULL, FALSE);
+
+ return class->available (junk_filter);
+}
+
+GtkWidget *
+e_mail_junk_filter_new_config_widget (EMailJunkFilter *junk_filter)
+{
+ EMailJunkFilterClass *class;
+ GtkWidget *widget = NULL;
+
+ g_return_val_if_fail (E_IS_MAIL_JUNK_FILTER (junk_filter), NULL);
+
+ class = E_MAIL_JUNK_FILTER_GET_CLASS (junk_filter);
+
+ if (class->new_config_widget != NULL)
+ widget = class->new_config_widget (junk_filter);
+
+ return widget;
+}
+
+gint
+e_mail_junk_filter_compare (EMailJunkFilter *junk_filter_a,
+ EMailJunkFilter *junk_filter_b)
+{
+ EMailJunkFilterClass *class_a;
+ EMailJunkFilterClass *class_b;
+
+ class_a = E_MAIL_JUNK_FILTER_GET_CLASS (junk_filter_a);
+ class_b = E_MAIL_JUNK_FILTER_GET_CLASS (junk_filter_b);
+
+ return g_utf8_collate (class_a->display_name, class_b->display_name);
+}
diff --git a/libemail-engine/e-mail-junk-filter.h b/libemail-engine/e-mail-junk-filter.h
new file mode 100644
index 0000000000..74a7840c2d
--- /dev/null
+++ b/libemail-engine/e-mail-junk-filter.h
@@ -0,0 +1,74 @@
+/*
+ * e-mail-junk-filter.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_JUNK_FILTER_H
+#define E_MAIL_JUNK_FILTER_H
+
+#include <gtk/gtk.h>
+#include <libebackend/e-extension.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_JUNK_FILTER \
+ (e_mail_junk_filter_get_type ())
+#define E_MAIL_JUNK_FILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_JUNK_FILTER, EMailJunkFilter))
+#define E_MAIL_JUNK_FILTER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_JUNK_FILTER, EMailJunkFilterClass))
+#define E_IS_MAIL_JUNK_FILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_JUNK_FILTER))
+#define E_IS_MAIL_JUNK_FILTER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MAIL_JUNK_FILTER))
+#define E_MAIL_JUNK_FILTER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MAIL_JUNK_FILTER, EMailJunkFilterClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailJunkFilter EMailJunkFilter;
+typedef struct _EMailJunkFilterClass EMailJunkFilterClass;
+typedef struct _EMailJunkFilterPrivate EMailJunkFilterPrivate;
+
+struct _EMailJunkFilter {
+ EExtension parent;
+ EMailJunkFilterPrivate *priv;
+};
+
+struct _EMailJunkFilterClass {
+ EExtensionClass parent_class;
+
+ const gchar *filter_name;
+ const gchar *display_name;
+
+ gboolean (*available) (EMailJunkFilter *junk_filter);
+ GtkWidget * (*new_config_widget) (EMailJunkFilter *junk_filter);
+};
+
+GType e_mail_junk_filter_get_type (void) G_GNUC_CONST;
+gboolean e_mail_junk_filter_available (EMailJunkFilter *junk_filter);
+GtkWidget * e_mail_junk_filter_new_config_widget
+ (EMailJunkFilter *junk_filter);
+gint e_mail_junk_filter_compare (EMailJunkFilter *junk_filter_a,
+ EMailJunkFilter *junk_filter_b);
+
+G_END_DECLS
+
+#endif /* E_MAIL_JUNK_FILTER_H */
diff --git a/libemail-engine/e-mail-session-utils.c b/libemail-engine/e-mail-session-utils.c
new file mode 100644
index 0000000000..979d15388e
--- /dev/null
+++ b/libemail-engine/e-mail-session-utils.c
@@ -0,0 +1,931 @@
+/*
+ * e-mail-session-utils.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-mail-session-utils.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <libemail-engine/e-mail-folder-utils.h>
+#include <libemail-engine/e-mail-utils.h>
+#include <libemail-engine/mail-tools.h>
+#include <libemail-utils/e-account-utils.h>
+
+/* X-Mailer header value */
+#define X_MAILER ("Evolution " VERSION SUB_VERSION " " VERSION_COMMENT)
+
+/* FIXME: Temporary - remove this after we move filter/ to eds */
+#define E_FILTER_SOURCE_OUTGOING "outgoing"
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _AsyncContext {
+ CamelFolder *sent_folder;
+
+ CamelMimeMessage *message;
+ CamelMessageInfo *info;
+
+ CamelAddress *from;
+ CamelAddress *recipients;
+
+ CamelFilterDriver *driver;
+
+ GCancellable *cancellable;
+ gint io_priority;
+
+ /* X-Evolution headers */
+ struct _camel_header_raw *xev;
+
+ GPtrArray *post_to_uris;
+
+ gchar *folder_uri;
+ gchar *message_uid;
+ gchar *transport_uid;
+ gchar *sent_folder_uri;
+};
+
+static void
+async_context_free (AsyncContext *context)
+{
+ if (context->sent_folder != NULL)
+ g_object_unref (context->sent_folder);
+
+ if (context->message != NULL)
+ g_object_unref (context->message);
+
+ if (context->info != NULL)
+ camel_message_info_free (context->info);
+
+ if (context->from != NULL)
+ g_object_unref (context->from);
+
+ if (context->recipients != NULL)
+ g_object_unref (context->recipients);
+
+ if (context->driver != NULL)
+ g_object_unref (context->driver);
+
+ if (context->cancellable != NULL) {
+ camel_operation_pop_message (context->cancellable);
+ g_object_unref (context->cancellable);
+ }
+
+ if (context->xev != NULL)
+ camel_header_raw_clear (&context->xev);
+
+ if (context->post_to_uris != NULL) {
+ g_ptr_array_foreach (
+ context->post_to_uris, (GFunc) g_free, NULL);
+ g_ptr_array_free (context->post_to_uris, TRUE);
+ }
+
+ g_free (context->folder_uri);
+ g_free (context->message_uid);
+ g_free (context->transport_uid);
+ g_free (context->sent_folder_uri);
+
+ g_slice_free (AsyncContext, context);
+}
+
+GQuark
+e_mail_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0)) {
+ const gchar *string = "e-mail-error-quark";
+ quark = g_quark_from_static_string (string);
+ }
+
+ return quark;
+}
+
+static void
+mail_session_handle_draft_headers_thread (GSimpleAsyncResult *simple,
+ EMailSession *session,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ e_mail_session_handle_draft_headers_sync (
+ session, context->message, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+gboolean
+e_mail_session_handle_draft_headers_sync (EMailSession *session,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelMedium *medium;
+ const gchar *folder_uri;
+ const gchar *message_uid;
+ const gchar *header_name;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+
+ medium = CAMEL_MEDIUM (message);
+
+ header_name = "X-Evolution-Draft-Folder";
+ folder_uri = camel_medium_get_header (medium, header_name);
+
+ header_name = "X-Evolution-Draft-Message";
+ message_uid = camel_medium_get_header (medium, header_name);
+
+ /* Don't report errors about missing X-Evolution-Draft
+ * headers. These headers are optional, so their absence
+ * is handled by doing nothing. */
+ if (folder_uri == NULL || message_uid == NULL)
+ return TRUE;
+
+ folder = e_mail_session_uri_to_folder_sync (
+ session, folder_uri, 0, cancellable, error);
+
+ if (folder == NULL)
+ return FALSE;
+
+ camel_folder_set_message_flags (
+ folder, message_uid,
+ CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
+ CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);
+
+ success = camel_folder_synchronize_message_sync (
+ folder, message_uid, cancellable, error);
+
+ g_object_unref (folder);
+
+ return success;
+}
+
+void
+e_mail_session_handle_draft_headers (EMailSession *session,
+ CamelMimeMessage *message,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ context = g_slice_new0 (AsyncContext);
+ context->message = g_object_ref (message);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (session), callback, user_data,
+ e_mail_session_handle_draft_headers);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_session_handle_draft_headers_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_session_handle_draft_headers_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (session),
+ e_mail_session_handle_draft_headers), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_session_handle_source_headers_thread (GSimpleAsyncResult *simple,
+ EMailSession *session,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ e_mail_session_handle_source_headers_sync (
+ session, context->message, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+gboolean
+e_mail_session_handle_source_headers_sync (EMailSession *session,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelMedium *medium;
+ CamelMessageFlags flags = 0;
+ const gchar *folder_uri;
+ const gchar *message_uid;
+ const gchar *flag_string;
+ const gchar *header_name;
+ gboolean success;
+ guint length, ii;
+ gchar **tokens;
+ gchar *string;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+
+ medium = CAMEL_MEDIUM (message);
+
+ header_name = "X-Evolution-Source-Folder";
+ folder_uri = camel_medium_get_header (medium, header_name);
+
+ header_name = "X-Evolution-Source-Message";
+ message_uid = camel_medium_get_header (medium, header_name);
+
+ header_name = "X-Evolution-Source-Flags";
+ flag_string = camel_medium_get_header (medium, header_name);
+
+ /* Don't report errors about missing X-Evolution-Source
+ * headers. These headers are optional, so their absence
+ * is handled by doing nothing. */
+ if (folder_uri == NULL || message_uid == NULL || flag_string == NULL)
+ return TRUE;
+
+ /* Convert the flag string to CamelMessageFlags. */
+
+ string = g_strstrip (g_strdup (flag_string));
+ tokens = g_strsplit (string, " ", 0);
+ g_free (string);
+
+ /* If tokens is NULL, a length of 0 will skip the loop. */
+ length = (tokens != NULL) ? g_strv_length (tokens) : 0;
+
+ for (ii = 0; ii < length; ii++) {
+ /* Note: We're only checking for flags known to
+ * be used in X-Evolution-Source-Flags headers.
+ * Add more as needed. */
+ if (g_strcmp0 (tokens[ii], "ANSWERED") == 0)
+ flags |= CAMEL_MESSAGE_ANSWERED;
+ else if (g_strcmp0 (tokens[ii], "ANSWERED_ALL") == 0)
+ flags |= CAMEL_MESSAGE_ANSWERED_ALL;
+ else if (g_strcmp0 (tokens[ii], "FORWARDED") == 0)
+ flags |= CAMEL_MESSAGE_FORWARDED;
+ else if (g_strcmp0 (tokens[ii], "SEEN") == 0)
+ flags |= CAMEL_MESSAGE_SEEN;
+ else
+ g_warning (
+ "Unknown flag '%s' in %s",
+ tokens[ii], header_name);
+ }
+
+ g_strfreev (tokens);
+
+ folder = e_mail_session_uri_to_folder_sync (
+ session, folder_uri, 0, cancellable, error);
+
+ if (folder == NULL)
+ return FALSE;
+
+ camel_folder_set_message_flags (
+ folder, message_uid, flags, flags);
+
+ success = camel_folder_synchronize_message_sync (
+ folder, message_uid, cancellable, error);
+
+ g_object_unref (folder);
+
+ return success;
+}
+
+void
+e_mail_session_handle_source_headers (EMailSession *session,
+ CamelMimeMessage *message,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ context = g_slice_new0 (AsyncContext);
+ context->message = g_object_ref (message);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (session), callback, user_data,
+ e_mail_session_handle_source_headers);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_session_handle_source_headers_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_session_handle_source_headers_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (session),
+ e_mail_session_handle_draft_headers), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_session_send_to_thread (GSimpleAsyncResult *simple,
+ EMailSession *session,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ CamelFolder *local_sent_folder;
+ GString *error_messages;
+ gboolean copy_to_sent = TRUE;
+ guint ii;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ /* Send the message to all recipients. */
+ if (camel_address_length (context->recipients) > 0) {
+ CamelProvider *provider;
+ CamelService *service;
+ gboolean did_connect = FALSE;
+
+ service = camel_session_get_service (
+ CAMEL_SESSION (session), context->transport_uid);
+
+ if (!CAMEL_IS_TRANSPORT (service)) {
+ g_simple_async_result_set_error (simple,
+ CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("Cannot get transport for account '%s'"),
+ context->transport_uid);
+ return;
+ }
+
+ if (camel_service_get_connection_status (service) != CAMEL_SERVICE_CONNECTED) {
+ did_connect = TRUE;
+
+ /* XXX This API does not allow for cancellation. */
+ if (!em_utils_connect_service_sync (service, cancellable, &error)) {
+ g_simple_async_result_take_error (simple, error);
+ return;
+ }
+ }
+
+ provider = camel_service_get_provider (service);
+
+ if (provider->flags & CAMEL_PROVIDER_DISABLE_SENT_FOLDER)
+ copy_to_sent = FALSE;
+
+ camel_transport_send_to_sync (
+ CAMEL_TRANSPORT (service),
+ context->message, context->from,
+ context->recipients, cancellable, &error);
+
+ if (did_connect)
+ em_utils_disconnect_service_sync (
+ service, error == NULL,
+ cancellable, error ? NULL : &error);
+
+ if (error != NULL) {
+ g_simple_async_result_take_error (simple, error);
+ return;
+ }
+ }
+
+ /* Post the message to requested folders. */
+ for (ii = 0; ii < context->post_to_uris->len; ii++) {
+ CamelFolder *folder;
+ const gchar *folder_uri;
+
+ folder_uri = g_ptr_array_index (context->post_to_uris, ii);
+
+ folder = e_mail_session_uri_to_folder_sync (
+ session, folder_uri, 0, cancellable, &error);
+
+ if (error != NULL) {
+ g_warn_if_fail (folder == NULL);
+ g_simple_async_result_take_error (simple, error);
+ return;
+ }
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ camel_folder_append_message_sync (
+ folder, context->message, context->info,
+ NULL, cancellable, &error);
+
+ g_object_unref (folder);
+
+ if (error != NULL) {
+ g_simple_async_result_take_error (simple, error);
+ return;
+ }
+ }
+
+ /*** Post Processing ***/
+
+ /* This accumulates error messages during post-processing. */
+ error_messages = g_string_sized_new (256);
+
+ mail_tool_restore_xevolution_headers (context->message, context->xev);
+
+ /* Run filters on the outgoing message. */
+ if (context->driver != NULL) {
+ camel_filter_driver_filter_message (
+ context->driver, context->message, context->info,
+ NULL, NULL, NULL, "", cancellable, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto exit;
+
+ if (error != NULL) {
+ g_string_append_printf (
+ error_messages,
+ _("Failed to apply outgoing filters: %s"),
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ if (!copy_to_sent)
+ goto cleanup;
+
+ /* Append the sent message to a Sent folder. */
+
+ local_sent_folder =
+ e_mail_session_get_local_folder (
+ session, E_MAIL_LOCAL_FOLDER_SENT);
+
+ /* Try to extract a CamelFolder from the Sent folder URI. */
+ if (context->sent_folder_uri != NULL) {
+ context->sent_folder = e_mail_session_uri_to_folder_sync (
+ session, context->sent_folder_uri, 0,
+ cancellable, &error);
+ if (error != NULL) {
+ g_warn_if_fail (context->sent_folder == NULL);
+ if (error_messages->len > 0)
+ g_string_append (error_messages, "\n\n");
+ g_string_append_printf (
+ error_messages,
+ _("Failed to append to %s: %s\n"
+ "Appending to local 'Sent' folder instead."),
+ context->sent_folder_uri, error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ /* Fall back to the local Sent folder. */
+ if (context->sent_folder == NULL)
+ context->sent_folder = g_object_ref (local_sent_folder);
+
+ /* Append the message. */
+ camel_folder_append_message_sync (
+ context->sent_folder, context->message,
+ context->info, NULL, cancellable, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto exit;
+
+ if (error == NULL)
+ goto cleanup;
+
+ /* If appending to a remote Sent folder failed,
+ * try appending to the local Sent folder. */
+ if (context->sent_folder != local_sent_folder) {
+ const gchar *description;
+
+ description = camel_folder_get_description (
+ context->sent_folder);
+
+ if (error_messages->len > 0)
+ g_string_append (error_messages, "\n\n");
+ g_string_append_printf (
+ error_messages,
+ _("Failed to append to %s: %s\n"
+ "Appending to local 'Sent' folder instead."),
+ description, error->message);
+ g_clear_error (&error);
+
+ camel_folder_append_message_sync (
+ local_sent_folder, context->message,
+ context->info, NULL, cancellable, &error);
+ }
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto exit;
+
+ /* We can't even append to the local Sent folder?
+ * In that case just leave the message in Outbox. */
+ if (error != NULL) {
+ if (error_messages->len > 0)
+ g_string_append (error_messages, "\n\n");
+ g_string_append_printf (
+ error_messages,
+ _("Failed to append to local 'Sent' folder: %s"),
+ error->message);
+ g_clear_error (&error);
+ goto exit;
+ }
+
+cleanup:
+
+ /* The send operation was successful; ignore cleanup errors. */
+
+ /* Mark the draft message for deletion, if present. */
+ e_mail_session_handle_draft_headers_sync (
+ session, context->message, cancellable, &error);
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+
+ /* Set flags on the original source message, if present.
+ * Source message refers to the message being forwarded
+ * or replied to. */
+ e_mail_session_handle_source_headers_sync (
+ session, context->message, cancellable, &error);
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+
+exit:
+
+ /* If we were cancelled, disregard any other errors. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_simple_async_result_take_error (simple, error);
+
+ /* Stuff the accumulated error messages in a GError. */
+ } else if (error_messages->len > 0) {
+ g_simple_async_result_set_error (
+ simple, E_MAIL_ERROR,
+ E_MAIL_ERROR_POST_PROCESSING,
+ "%s", error_messages->str);
+ }
+
+ /* Synchronize the Sent folder. */
+ if (context->sent_folder != NULL)
+ camel_folder_synchronize_sync (
+ context->sent_folder, FALSE, cancellable, NULL);
+
+ g_string_free (error_messages, TRUE);
+}
+
+void
+e_mail_session_send_to (EMailSession *session,
+ CamelMimeMessage *message,
+ gint io_priority,
+ GCancellable *cancellable,
+ CamelFilterGetFolderFunc get_folder_func,
+ gpointer get_folder_data,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+ CamelAddress *from;
+ CamelAddress *recipients;
+ CamelMedium *medium;
+ CamelMessageInfo *info;
+ EAccount *account = NULL;
+ GPtrArray *post_to_uris;
+ struct _camel_header_raw *xev;
+ struct _camel_header_raw *header;
+ const gchar *string;
+ const gchar *resent_from;
+ gchar *transport_uid = NULL;
+ gchar *sent_folder_uri = NULL;
+ GError *error = NULL;
+
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ medium = CAMEL_MEDIUM (message);
+
+ camel_medium_set_header (medium, "X-Mailer", X_MAILER);
+
+ xev = mail_tool_remove_xevolution_headers (message);
+
+ /* Extract directives from X-Evolution headers. */
+
+ string = camel_header_raw_find (&xev, "X-Evolution-Account", NULL);
+ if (string != NULL) {
+ gchar *account_uid;
+
+ account_uid = g_strstrip (g_strdup (string));
+ account = e_get_account_by_uid (account_uid);
+ g_free (account_uid);
+ }
+
+ if (account != NULL) {
+ if (account->transport != NULL) {
+
+ /* XXX Transport UIDs are kludgy right now. We
+ * use the EAccount's regular UID and tack on
+ * "-transport". Will be better soon. */
+ transport_uid = g_strconcat (
+ account->uid, "-transport", NULL);
+
+ /* to reprompt password on sending if needed */
+ account->transport->get_password_canceled = FALSE;
+ }
+ sent_folder_uri = g_strdup (account->sent_folder_uri);
+ }
+
+ string = camel_header_raw_find (&xev, "X-Evolution-Fcc", NULL);
+ if (sent_folder_uri == NULL && string != NULL)
+ sent_folder_uri = g_strstrip (g_strdup (string));
+
+ string = camel_header_raw_find (&xev, "X-Evolution-Transport", NULL);
+ if (transport_uid == NULL && string != NULL)
+ transport_uid = g_strstrip (g_strdup (string));
+
+ post_to_uris = g_ptr_array_new ();
+ for (header = xev; header != NULL; header = header->next) {
+ gchar *folder_uri;
+
+ if (g_strcmp0 (header->name, "X-Evolution-PostTo") != 0)
+ continue;
+
+ folder_uri = g_strstrip (g_strdup (header->value));
+ g_ptr_array_add (post_to_uris, folder_uri);
+ }
+
+ /* Collect sender and recipients from headers. */
+
+ from = (CamelAddress *) camel_internet_address_new ();
+ recipients = (CamelAddress *) camel_internet_address_new ();
+ resent_from = camel_medium_get_header (medium, "Resent-From");
+
+ if (resent_from != NULL) {
+ const CamelInternetAddress *addr;
+ const gchar *type;
+
+ camel_address_decode (from, resent_from);
+
+ type = CAMEL_RECIPIENT_TYPE_RESENT_TO;
+ addr = camel_mime_message_get_recipients (message, type);
+ camel_address_cat (recipients, CAMEL_ADDRESS (addr));
+
+ type = CAMEL_RECIPIENT_TYPE_RESENT_CC;
+ addr = camel_mime_message_get_recipients (message, type);
+ camel_address_cat (recipients, CAMEL_ADDRESS (addr));
+
+ type = CAMEL_RECIPIENT_TYPE_RESENT_BCC;
+ addr = camel_mime_message_get_recipients (message, type);
+ camel_address_cat (recipients, CAMEL_ADDRESS (addr));
+
+ } else {
+ const CamelInternetAddress *addr;
+ const gchar *type;
+
+ addr = camel_mime_message_get_from (message);
+ camel_address_copy (from, CAMEL_ADDRESS (addr));
+
+ type = CAMEL_RECIPIENT_TYPE_TO;
+ addr = camel_mime_message_get_recipients (message, type);
+ camel_address_cat (recipients, CAMEL_ADDRESS (addr));
+
+ type = CAMEL_RECIPIENT_TYPE_CC;
+ addr = camel_mime_message_get_recipients (message, type);
+ camel_address_cat (recipients, CAMEL_ADDRESS (addr));
+
+ type = CAMEL_RECIPIENT_TYPE_BCC;
+ addr = camel_mime_message_get_recipients (message, type);
+ camel_address_cat (recipients, CAMEL_ADDRESS (addr));
+ }
+
+ /* Miscellaneous preparations. */
+
+ info = camel_message_info_new (NULL);
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0);
+
+ /* The rest of the processing happens in a thread. */
+
+ context = g_slice_new0 (AsyncContext);
+ context->message = g_object_ref (message);
+ context->io_priority = io_priority;
+ context->from = from;
+ context->recipients = recipients;
+ context->message = g_object_ref (message);
+ context->info = info;
+ context->xev = xev;
+ context->post_to_uris = post_to_uris;
+ context->transport_uid = transport_uid;
+ context->sent_folder_uri = sent_folder_uri;
+
+ if (G_IS_CANCELLABLE (cancellable))
+ context->cancellable = g_object_ref (cancellable);
+
+ /* Failure here emits a runtime warning but is non-fatal. */
+ context->driver = camel_session_get_filter_driver (
+ CAMEL_SESSION (session), E_FILTER_SOURCE_OUTGOING, &error);
+ if (context->driver != NULL && get_folder_func)
+ camel_filter_driver_set_folder_func (
+ context->driver, get_folder_func, get_folder_data);
+ if (error != NULL) {
+ g_warn_if_fail (context->driver == NULL);
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ /* This gets popped in async_context_free(). */
+ camel_operation_push_message (
+ context->cancellable, _("Sending message"));
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (session), callback,
+ user_data, e_mail_session_send_to);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_session_send_to_thread,
+ context->io_priority,
+ context->cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_session_send_to_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (session),
+ e_mail_session_send_to), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_session_unsubscribe_folder_thread (GSimpleAsyncResult *simple,
+ EMailSession *session,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ e_mail_session_unsubscribe_folder_sync (
+ session, context->folder_uri, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+gboolean
+e_mail_session_unsubscribe_folder_sync (EMailSession *session,
+ const gchar *folder_uri,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store = NULL;
+ gchar *folder_name = NULL;
+ const gchar *message;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE);
+ g_return_val_if_fail (folder_uri != NULL, FALSE);
+
+ success = e_mail_folder_uri_parse (
+ CAMEL_SESSION (session), folder_uri,
+ &store, &folder_name, error);
+
+ if (!success)
+ return FALSE;
+
+ message = _("Unsubscribing from folder '%s'");
+ camel_operation_push_message (cancellable, message, folder_name);
+
+ /* FIXME This should take our GCancellable. */
+ success =
+ em_utils_connect_service_sync (
+ CAMEL_SERVICE (store), cancellable, error) &&
+ camel_subscribable_unsubscribe_folder_sync (
+ CAMEL_SUBSCRIBABLE (store),
+ folder_name, cancellable, error);
+
+ camel_operation_pop_message (cancellable);
+
+ g_object_unref (store);
+ g_free (folder_name);
+
+ return success;
+}
+
+void
+e_mail_session_unsubscribe_folder (EMailSession *session,
+ const gchar *folder_uri,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+ g_return_if_fail (folder_uri != NULL);
+
+ context = g_slice_new0 (AsyncContext);
+ context->folder_uri = g_strdup (folder_uri);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (session), callback, user_data,
+ e_mail_session_unsubscribe_folder);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_session_unsubscribe_folder_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_session_unsubscribe_folder_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (session),
+ e_mail_session_unsubscribe_folder), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
diff --git a/libemail-engine/e-mail-session-utils.h b/libemail-engine/e-mail-session-utils.h
new file mode 100644
index 0000000000..2c92216533
--- /dev/null
+++ b/libemail-engine/e-mail-session-utils.h
@@ -0,0 +1,97 @@
+/*
+ * e-mail-session-utils.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_SESSION_UTILS_H
+#define E_MAIL_SESSION_UTILS_H
+
+/* High-level operations with Evolution-specific policies. */
+
+#include <libemail-engine/e-mail-session.h>
+
+#define E_MAIL_ERROR (e_mail_error_quark ())
+
+G_BEGIN_DECLS
+
+typedef enum {
+ E_MAIL_ERROR_POST_PROCESSING
+} EMailError;
+
+GQuark e_mail_error_quark (void) G_GNUC_CONST;
+gboolean e_mail_session_handle_draft_headers_sync
+ (EMailSession *session,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_session_handle_draft_headers
+ (EMailSession *session,
+ CamelMimeMessage *message,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_session_handle_draft_headers_finish
+ (EMailSession *session,
+ GAsyncResult *result,
+ GError **error);
+gboolean e_mail_session_handle_source_headers_sync
+ (EMailSession *session,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_session_handle_source_headers
+ (EMailSession *session,
+ CamelMimeMessage *message,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_session_handle_source_headers_finish
+ (EMailSession *session,
+ GAsyncResult *result,
+ GError **error);
+void e_mail_session_send_to (EMailSession *session,
+ CamelMimeMessage *message,
+ gint io_priority,
+ GCancellable *cancellable,
+ CamelFilterGetFolderFunc get_folder_func,
+ gpointer get_folder_data,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_session_send_to_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error);
+gboolean e_mail_session_unsubscribe_folder_sync
+ (EMailSession *session,
+ const gchar *folder_uri,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_session_unsubscribe_folder
+ (EMailSession *session,
+ const gchar *folder_uri,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_session_unsubscribe_folder_finish
+ (EMailSession *session,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SESSION_UTILS_H */
diff --git a/libemail-engine/e-mail-session.c b/libemail-engine/e-mail-session.c
new file mode 100644
index 0000000000..012ad9ba7d
--- /dev/null
+++ b/libemail-engine/e-mail-session.c
@@ -0,0 +1,1969 @@
+/*
+ * e-mail-session.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ * Copyright (C) 2009 Intel Corporation
+ *
+ */
+
+/* mail-session.c: handles the session information and resource manipulation */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <gtk/gtk.h>
+
+#ifdef HAVE_CANBERRA
+#include <canberra-gtk.h>
+#endif
+
+#include <libedataserver/e-flag.h>
+#include <libedataserver/e-proxy.h>
+#include <libebackend/e-extensible.h>
+#include <libedataserverui/e-passwords.h>
+#include <libedataserver/e-data-server-util.h>
+
+#include "libemail-utils/e-account-utils.h"
+#include "libemail-utils/mail-mt.h"
+
+#include "e-mail-junk-filter.h"
+#include "e-mail-session.h"
+#include "e-mail-folder-utils.h"
+#include "e-mail-utils.h"
+#include "mail-config.h"
+#include "mail-ops.h"
+#include "mail-tools.h"
+
+#define E_MAIL_SESSION_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SESSION, EMailSessionPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EMailSessionPrivate {
+ MailFolderCache *folder_cache;
+
+ EAccountList *account_list;
+ gulong account_added_handler_id;
+
+ CamelStore *local_store;
+
+ FILE *filter_logfile;
+ GHashTable *junk_filters;
+ EProxy *proxy;
+
+ /* Local folder cache. */
+ GPtrArray *local_folders;
+ GPtrArray *local_folder_uris;
+};
+
+struct _AsyncContext {
+ /* arguments */
+ CamelStoreGetFolderFlags flags;
+ gchar *uid;
+ gchar *uri;
+
+ /* results */
+ CamelFolder *folder;
+};
+
+enum {
+ PROP_0,
+ PROP_FOLDER_CACHE,
+ PROP_JUNK_FILTER_NAME,
+ PROP_LOCAL_STORE
+};
+
+static const gchar *local_folder_names[E_MAIL_NUM_LOCAL_FOLDERS] = {
+ N_("Inbox"), /* E_MAIL_LOCAL_FOLDER_INBOX */
+ N_("Drafts"), /* E_MAIL_LOCAL_FOLDER_DRAFTS */
+ N_("Outbox"), /* E_MAIL_LOCAL_FOLDER_OUTBOX */
+ N_("Sent"), /* E_MAIL_LOCAL_FOLDER_SENT */
+ N_("Templates"), /* E_MAIL_LOCAL_FOLDER_TEMPLATES */
+ "Inbox" /* E_MAIL_LOCAL_FOLDER_LOCAL_INBOX */
+};
+
+enum {
+ FLUSH_OUTBOX,
+ STORE_ADDED,
+ STORE_REMOVED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static gchar *mail_data_dir;
+static gchar *mail_cache_dir;
+static gchar *mail_config_dir;
+
+G_DEFINE_TYPE_WITH_CODE (
+ EMailSession,
+ e_mail_session,
+ CAMEL_TYPE_SESSION,
+ G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
+
+/* Support for CamelSession.alert_user() *************************************/
+
+static GQueue user_message_queue = { NULL, NULL, 0 };
+
+struct _user_message_msg {
+ MailMsg base;
+
+ CamelSessionAlertType type;
+ gchar *prompt;
+ GSList *button_captions;
+ EFlag *done;
+
+ gint result;
+ guint ismain : 1;
+};
+
+static void user_message_exec (struct _user_message_msg *m,
+ GCancellable *cancellable,
+ GError **error);
+
+static void
+user_message_response_free (struct _user_message_msg *m)
+{
+
+ /* check for pendings */
+ if (!g_queue_is_empty (&user_message_queue)) {
+ GCancellable *cancellable;
+
+ m = g_queue_pop_head (&user_message_queue);
+ cancellable = m->base.cancellable;
+ user_message_exec (m, cancellable, &m->base.error);
+ mail_msg_unref (m);
+ }
+}
+
+/* clicked, send back the reply */
+static void
+user_message_response (struct _user_message_msg *m)
+{
+ /* if !allow_cancel, then we've already replied */
+ if (m->button_captions) {
+ m->result = TRUE; //If Accepted
+ e_flag_set (m->done);
+ }
+
+ user_message_response_free (m);
+}
+
+static void
+user_message_exec (struct _user_message_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* XXX This is a case where we need to be able to construct
+ * custom EAlerts without a predefined XML definition. */
+ if (m->ismain) {
+ /* Use DBUS to raise dialogs in clients and reply back.
+ * For now say accept all. */
+ user_message_response (m);
+ } else
+ g_queue_push_tail (&user_message_queue, mail_msg_ref (m));
+}
+
+static void
+user_message_free (struct _user_message_msg *m)
+{
+ g_free (m->prompt);
+ g_slist_free_full (m->button_captions, g_free);
+ e_flag_free (m->done);
+}
+
+static MailMsgInfo user_message_info = {
+ sizeof (struct _user_message_msg),
+ (MailMsgDescFunc) NULL,
+ (MailMsgExecFunc) user_message_exec,
+ (MailMsgDoneFunc) NULL,
+ (MailMsgFreeFunc) user_message_free
+};
+
+/* Support for CamelSession.get_filter_driver () *****************************/
+
+static CamelFolder *
+get_folder (CamelFilterDriver *d,
+ const gchar *uri,
+ gpointer user_data,
+ GError **error)
+{
+ EMailSession *session = E_MAIL_SESSION (user_data);
+
+ /* FIXME Not passing a GCancellable here. */
+ /* FIXME Need a camel_filter_driver_get_session(). */
+ return e_mail_session_uri_to_folder_sync (
+ session, uri, 0, NULL, error);
+}
+
+static CamelFilterDriver *
+main_get_filter_driver (CamelSession *session,
+ const gchar *type,
+ GError **error)
+{
+ CamelFilterDriver *driver;
+ EMailSession *ms = (EMailSession *)session;
+ GSettings *settings;
+
+ settings = g_settings_new ("org.gnome.evolution.mail");
+
+ driver = camel_filter_driver_new (session);
+ camel_filter_driver_set_folder_func (driver, get_folder, session);
+
+ if (g_settings_get_boolean (settings, "filters-log-actions")) {
+ if (ms->priv->filter_logfile == NULL) {
+ gchar *filename;
+
+ filename = g_settings_get_string (settings, "filters-log-file");
+ if (filename) {
+ ms->priv->filter_logfile = g_fopen (filename, "a+");
+ g_free (filename);
+ }
+ }
+
+ if (ms->priv->filter_logfile)
+ camel_filter_driver_set_logfile (driver, ms->priv->filter_logfile);
+ }
+
+ g_object_unref (settings);
+
+ return driver;
+}
+
+/* Support for CamelSession.forward_to () ************************************/
+
+static guint preparing_flush = 0;
+
+static gboolean
+forward_to_flush_outbox_cb (EMailSession *session)
+{
+
+ preparing_flush = 0;
+
+ /* Connect to this and call mail_send in the main email client.*/
+ g_signal_emit (session, signals[FLUSH_OUTBOX], 0);
+
+ return FALSE;
+}
+
+static void
+ms_forward_to_cb (CamelFolder *folder,
+ GAsyncResult *result,
+ EMailSession *session)
+{
+ GSettings *settings;
+
+ /* FIXME Poor error handling. */
+ if (!e_mail_folder_append_message_finish (folder, result, NULL, NULL))
+ return;
+
+ settings = g_settings_new ("org.gnome.evolution.mail");
+
+ /* do not call mail send immediately, just pile them all in the outbox */
+ if (preparing_flush || g_settings_get_boolean (
+ settings, "flush-outbox")) {
+ if (preparing_flush)
+ g_source_remove (preparing_flush);
+
+ preparing_flush = g_timeout_add_seconds (
+ 60, (GSourceFunc)
+ forward_to_flush_outbox_cb, session);
+ }
+
+ g_object_unref (settings);
+}
+
+static void
+async_context_free (AsyncContext *context)
+{
+ if (context->folder != NULL)
+ g_object_unref (context->folder);
+
+ g_free (context->uid);
+ g_free (context->uri);
+
+ g_slice_free (AsyncContext, context);
+}
+
+static gchar *
+mail_session_make_key (CamelService *service,
+ const gchar *item)
+{
+ gchar *key;
+
+ if (service != NULL) {
+ CamelURL *url;
+
+ url = camel_service_new_camel_url (service);
+ key = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+ } else
+ key = g_strdup (item);
+
+ return key;
+}
+
+static void
+mail_session_check_junk_notify (GSettings *settings,
+ const gchar *key,
+ CamelSession *session)
+{
+ if (strcmp (key, "junk-check-incoming") == 0)
+ camel_session_set_check_junk (
+ session, g_settings_get_boolean (settings, key));
+}
+
+static const gchar *
+mail_session_get_junk_filter_name (EMailSession *session)
+{
+ CamelJunkFilter *junk_filter;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ /* XXX This property can be removed once Evolution moves to
+ * GSettings and can use transform functions when binding
+ * properties to settings. That's why this is private. */
+
+ g_hash_table_iter_init (&iter, session->priv->junk_filters);
+ junk_filter = camel_session_get_junk_filter (CAMEL_SESSION (session));
+
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ if (junk_filter == CAMEL_JUNK_FILTER (value))
+ return (const gchar *) key;
+ }
+
+ if (junk_filter != NULL)
+ g_warning (
+ "Camel is using a junk filter "
+ "unknown to Evolution of type %s",
+ G_OBJECT_TYPE_NAME (junk_filter));
+
+ return "";
+}
+
+static void
+mail_session_set_junk_filter_name (EMailSession *session,
+ const gchar *junk_filter_name)
+{
+ CamelJunkFilter *junk_filter = NULL;
+
+ /* XXX This property can be removed once Evolution moves to
+ * GSettings and can use transform functions when binding
+ * properties to settings. That's why this is private. */
+
+ /* An empty string is equivalent to a NULL string. */
+ if (junk_filter_name != NULL && *junk_filter_name == '\0')
+ junk_filter_name = NULL;
+
+ if (junk_filter_name != NULL) {
+ junk_filter = g_hash_table_lookup (
+ session->priv->junk_filters, junk_filter_name);
+ if (junk_filter != NULL) {
+ if (!e_mail_junk_filter_available (
+ E_MAIL_JUNK_FILTER (junk_filter)))
+ junk_filter = NULL;
+ } else {
+ g_warning (
+ "Unrecognized junk filter name "
+ "'%s' in GSettings", junk_filter_name);
+ }
+ }
+
+ camel_session_set_junk_filter (CAMEL_SESSION (session), junk_filter);
+
+ /* XXX We emit the "notify" signal in mail_session_notify(). */
+}
+
+static void
+mail_session_add_by_account (EMailSession *session,
+ EAccount *account)
+{
+ CamelService *service = NULL;
+ CamelProvider *provider;
+ CamelURL *url;
+ gboolean transport_only;
+ GError *error = NULL;
+
+ /* check whether it's transport-only accounts */
+ transport_only =
+ (account->source == NULL) ||
+ (account->source->url == NULL) ||
+ (*account->source->url == '\0');
+ if (transport_only)
+ goto handle_transport;
+
+ /* Load the service, but don't connect. Check its provider,
+ * and if this belongs in the folder tree model, add it. */
+
+ url = camel_url_new (account->source->url, NULL);
+ if (url != NULL) {
+ provider = camel_provider_get (url->protocol, NULL);
+ camel_url_free (url);
+ } else {
+ provider = NULL;
+ }
+
+ if (provider == NULL) {
+ /* In case we do not have a provider here, we handle
+ * the special case of having multiple mail identities
+ * eg. a dummy account having just SMTP server defined */
+ goto handle_transport;
+ }
+
+ service = camel_session_add_service (
+ CAMEL_SESSION (session),
+ account->uid, provider->protocol,
+ CAMEL_PROVIDER_STORE, &error);
+
+ if (error != NULL) {
+ g_warning (
+ "Failed to add service: %s: %s",
+ account->name, error->message);
+ g_error_free (error);
+ return;
+ }
+
+ camel_service_set_display_name (service, account->name);
+
+handle_transport:
+
+ /* While we're at it, add the account's transport (if it has one)
+ * to the CamelSession. The transport's UID is a kludge for now.
+ * We take the EAccount's UID and tack on "-transport". */
+
+ if (account->transport) {
+ GError *transport_error = NULL;
+
+ url = camel_url_new (
+ account->transport->url,
+ &transport_error);
+
+ if (url != NULL) {
+ provider = camel_provider_get (
+ url->protocol, &transport_error);
+ camel_url_free (url);
+ } else
+ provider = NULL;
+
+ if (provider != NULL) {
+ gchar *transport_uid;
+
+ transport_uid = g_strconcat (
+ account->uid, "-transport", NULL);
+
+ camel_session_add_service (
+ CAMEL_SESSION (session),
+ transport_uid, provider->protocol,
+ CAMEL_PROVIDER_TRANSPORT, &transport_error);
+
+ g_free (transport_uid);
+ }
+
+ if (transport_error) {
+ g_warning (
+ "%s: Failed to add transport service: %s",
+ G_STRFUNC, transport_error->message);
+ g_error_free (transport_error);
+ }
+ }
+}
+
+static void
+mail_session_account_added_cb (EAccountList *account_list,
+ EAccount *account,
+ EMailSession *session)
+{
+ mail_session_add_by_account (session, account);
+}
+
+static void
+mail_session_add_local_store (EMailSession *session)
+{
+ CamelLocalSettings *local_settings;
+ CamelSession *camel_session;
+ CamelSettings *settings;
+ CamelService *service;
+ const gchar *data_dir;
+ gchar *path;
+ gint ii;
+ GError *error = NULL;
+
+ camel_session = CAMEL_SESSION (session);
+
+ service = camel_session_add_service (
+ camel_session, E_MAIL_SESSION_LOCAL_UID,
+ "maildir", CAMEL_PROVIDER_STORE, &error);
+
+ /* XXX One could argue this is a fatal error
+ * since we depend on it in so many places. */
+ if (error != NULL) {
+ g_critical ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ camel_service_set_display_name (service, _("On This Computer"));
+
+ settings = camel_service_get_settings (service);
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ data_dir = camel_session_get_user_data_dir (camel_session);
+
+ path = g_build_filename (data_dir, E_MAIL_SESSION_LOCAL_UID, NULL);
+ camel_local_settings_set_path (local_settings, path);
+ g_free (path);
+
+ /* Shouldn't need to worry about other mail applications
+ * altering files in our local mail store. */
+ g_object_set (service, "need-summary-check", FALSE, NULL);
+
+ /* Populate the local folder cache. */
+ for (ii = 0; ii < E_MAIL_NUM_LOCAL_FOLDERS; ii++) {
+ CamelFolder *folder;
+ gchar *folder_uri;
+ const gchar *display_name;
+ GError *error = NULL;
+
+ display_name = local_folder_names[ii];
+
+ /* XXX This blocks but should be fast. */
+ if (ii == E_MAIL_LOCAL_FOLDER_LOCAL_INBOX)
+ folder = camel_store_get_inbox_folder_sync (
+ CAMEL_STORE (service), NULL, &error);
+ else
+ folder = camel_store_get_folder_sync (
+ CAMEL_STORE (service), display_name,
+ CAMEL_STORE_FOLDER_CREATE, NULL, &error);
+
+ folder_uri = e_mail_folder_uri_build (
+ CAMEL_STORE (service), display_name);
+
+ /* The arrays take ownership of the items added. */
+ g_ptr_array_add (session->priv->local_folders, folder);
+ g_ptr_array_add (session->priv->local_folder_uris, folder_uri);
+
+ if (error != NULL) {
+ g_critical ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+ }
+
+ session->priv->local_store = g_object_ref (service);
+}
+
+static void
+mail_session_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_JUNK_FILTER_NAME:
+ mail_session_set_junk_filter_name (
+ E_MAIL_SESSION (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_session_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FOLDER_CACHE:
+ g_value_set_object (
+ value,
+ e_mail_session_get_folder_cache (
+ E_MAIL_SESSION (object)));
+ return;
+
+ case PROP_JUNK_FILTER_NAME:
+ g_value_set_string (
+ value,
+ mail_session_get_junk_filter_name (
+ E_MAIL_SESSION (object)));
+ return;
+
+ case PROP_LOCAL_STORE:
+ g_value_set_object (
+ value,
+ e_mail_session_get_local_store (
+ E_MAIL_SESSION (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_session_dispose (GObject *object)
+{
+ EMailSessionPrivate *priv;
+
+ priv = E_MAIL_SESSION_GET_PRIVATE (object);
+
+ if (priv->folder_cache != NULL) {
+ g_object_unref (priv->folder_cache);
+ priv->folder_cache = NULL;
+ }
+
+ if (priv->account_list != NULL) {
+ g_signal_handler_disconnect (
+ priv->account_list,
+ priv->account_added_handler_id);
+ g_object_unref (priv->account_list);
+ priv->account_list = NULL;
+ }
+
+ if (priv->local_store != NULL) {
+ g_object_unref (priv->local_store);
+ priv->local_store = NULL;
+ }
+
+ g_ptr_array_set_size (priv->local_folders, 0);
+ g_ptr_array_set_size (priv->local_folder_uris, 0);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_mail_session_parent_class)->dispose (object);
+}
+
+static void
+mail_session_finalize (GObject *object)
+{
+ EMailSessionPrivate *priv;
+
+ priv = E_MAIL_SESSION_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->junk_filters);
+ g_object_unref (priv->proxy);
+
+ g_ptr_array_free (priv->local_folders, TRUE);
+ g_ptr_array_free (priv->local_folder_uris, TRUE);
+
+ g_free (mail_data_dir);
+ g_free (mail_config_dir);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_mail_session_parent_class)->finalize (object);
+}
+
+static void
+mail_session_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ /* GObject does not implement this method; do not chain up. */
+
+ /* XXX Delete this once Evolution moves to GSettings and
+ * we're able to get rid of PROP_JUNK_FILTER_NAME. */
+ if (g_strcmp0 (pspec->name, "junk-filter") == 0)
+ g_object_notify (object, "junk-filter-name");
+}
+
+static gboolean
+mail_session_initialize_stores_idle (gpointer user_data)
+{
+ EMailSession *session = user_data;
+ EAccountList *account_list;
+ EAccount *account;
+ EIterator *iter;
+
+ g_return_val_if_fail (session != NULL, FALSE);
+
+ account_list = e_get_account_list ();
+ iter = e_list_get_iterator (E_LIST (account_list));
+
+ while (e_iterator_is_valid (iter)) {
+ /* XXX EIterator misuses const. */
+ account = (EAccount *) e_iterator_get (iter);
+
+ mail_session_add_by_account (session, account);
+
+ e_iterator_next (iter);
+ }
+
+ g_object_unref (iter);
+
+ return FALSE;
+}
+
+static void
+mail_session_constructed (GObject *object)
+{
+ EMailSession *session;
+ EExtensible *extensible;
+ GType extension_type;
+ GList *list, *link;
+ GSettings *settings;
+ EAccountList *account_list;
+ gulong handler_id;
+
+ session = E_MAIL_SESSION (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_mail_session_parent_class)->constructed (object);
+
+ account_list = e_get_account_list ();
+ session->priv->account_list = g_object_ref (account_list);
+
+ /* This must be created after the account store. */
+ session->priv->folder_cache = mail_folder_cache_new (session);
+
+ /* Add built-in CamelStores. */
+ mail_session_add_local_store (session);
+
+ /* Give it a chance to load user settings, they are not loaded yet.
+ *
+ * XXX Is this the case where hiding such natural things like loading
+ * user setting into an EExtension strikes back and proves itself
+ * being suboptimal?
+ */
+ g_idle_add (mail_session_initialize_stores_idle, object);
+
+ /* Listen for account list updates. */
+
+ handler_id = g_signal_connect (
+ account_list, "account-added",
+ G_CALLBACK (mail_session_account_added_cb), session);
+ session->priv->account_added_handler_id = handler_id;
+
+ extensible = E_EXTENSIBLE (object);
+ e_extensible_load_extensions (extensible);
+
+ /* Add junk filter extensions to an internal hash table. */
+
+ extension_type = E_TYPE_MAIL_JUNK_FILTER;
+ list = e_extensible_list_extensions (extensible, extension_type);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ EMailJunkFilter *junk_filter;
+ EMailJunkFilterClass *class;
+
+ junk_filter = E_MAIL_JUNK_FILTER (link->data);
+ class = E_MAIL_JUNK_FILTER_GET_CLASS (junk_filter);
+
+ if (!CAMEL_IS_JUNK_FILTER (junk_filter)) {
+ g_warning (
+ "Skipping %s: Does not implement "
+ "CamelJunkFilterInterface",
+ G_OBJECT_TYPE_NAME (junk_filter));
+ continue;
+ }
+
+ if (class->filter_name == NULL) {
+ g_warning (
+ "Skipping %s: filter_name unset",
+ G_OBJECT_TYPE_NAME (junk_filter));
+ continue;
+ }
+
+ if (class->display_name == NULL) {
+ g_warning (
+ "Skipping %s: display_name unset",
+ G_OBJECT_TYPE_NAME (junk_filter));
+ continue;
+ }
+
+ /* No need to reference the EMailJunkFilter since
+ * EMailSession owns the reference to it already. */
+ g_hash_table_insert (
+ session->priv->junk_filters,
+ (gpointer) class->filter_name,
+ junk_filter);
+ }
+
+ g_list_free (list);
+
+ /* Bind the "junk-default-plugin" GSettings
+ * key to our "junk-filter-name" property. */
+
+ settings = g_settings_new ("org.gnome.evolution.mail");
+ g_settings_bind (
+ settings, "junk-default-plugin",
+ object, "junk-filter-name",
+ G_SETTINGS_BIND_DEFAULT);
+ g_object_unref (settings);
+}
+
+static CamelService *
+mail_session_add_service (CamelSession *session,
+ const gchar *uid,
+ const gchar *protocol,
+ CamelProviderType type,
+ GError **error)
+{
+ CamelService *service;
+
+ /* Chain up to parents add_service() method. */
+ service = CAMEL_SESSION_CLASS (e_mail_session_parent_class)->
+ add_service (session, uid, protocol, type, error);
+
+ /* Initialize the CamelSettings object from CamelURL parameters.
+ * This is temporary; soon we'll read settings from key files. */
+
+ if (CAMEL_IS_SERVICE (service)) {
+ EAccount *account;
+ CamelURL *url = NULL;
+
+ account = e_get_account_by_uid (uid);
+ if (account != NULL) {
+ const gchar *url_string = NULL;
+
+ switch (type) {
+ case CAMEL_PROVIDER_STORE:
+ url_string = account->source->url;
+ break;
+ case CAMEL_PROVIDER_TRANSPORT:
+ url_string = account->transport->url;
+ break;
+ default:
+ break;
+ }
+
+ if (url_string != NULL) {
+ url = camel_url_new (url_string, error);
+ if (url == NULL) {
+ g_object_unref (service);
+ service = NULL;
+ }
+ }
+ }
+
+ if (url != NULL) {
+ CamelSettings *settings;
+
+ settings = camel_service_get_settings (service);
+ camel_settings_load_from_url (settings, url);
+ camel_url_free (url);
+
+ g_object_notify (G_OBJECT (service), "settings");
+
+ /* Migrate files for this service from its old
+ * URL-based directory to a UID-based directory
+ * if necessary. */
+ camel_service_migrate_files (service);
+ }
+ }
+
+ return service;
+}
+
+static gchar *
+mail_session_get_password (CamelSession *session,
+ CamelService *service,
+ const gchar *prompt,
+ const gchar *item,
+ guint32 flags,
+ GError **error)
+{
+ EAccount *account = NULL;
+ const gchar *display_name = NULL;
+ const gchar *uid = NULL;
+ gchar *ret = NULL;
+
+ if (CAMEL_IS_SERVICE (service)) {
+ display_name = camel_service_get_display_name (service);
+ uid = camel_service_get_uid (service);
+ account = e_get_account_by_uid (uid);
+ }
+
+ if (!strcmp(item, "popb4smtp_uid")) {
+ /* not 100% mt safe, but should be ok */
+ ret = g_strdup ((account != NULL) ? account->uid : uid);
+ } else {
+ gchar *key = mail_session_make_key (service, item);
+ EAccountService *config_service = NULL;
+
+ ret = e_passwords_get_password (NULL, key);
+ if (ret == NULL || (flags & CAMEL_SESSION_PASSWORD_REPROMPT)) {
+ gboolean remember;
+
+ g_free (ret);
+ ret = NULL;
+
+ if (account != NULL) {
+ if (CAMEL_IS_STORE (service))
+ config_service = account->source;
+ if (CAMEL_IS_TRANSPORT (service))
+ config_service = account->transport;
+ }
+
+ remember = config_service ? config_service->save_passwd : FALSE;
+
+ if (!config_service || (config_service &&
+ !config_service->get_password_canceled)) {
+ guint32 eflags;
+ gchar *title;
+
+ if (flags & CAMEL_SESSION_PASSPHRASE) {
+ if (display_name != NULL)
+ title = g_strdup_printf (
+ _("Enter Passphrase for %s"),
+ display_name);
+ else
+ title = g_strdup (
+ _("Enter Passphrase"));
+ } else {
+ if (display_name != NULL)
+ title = g_strdup_printf (
+ _("Enter Password for %s"),
+ display_name);
+ else
+ title = g_strdup (
+ _("Enter Password"));
+ }
+ if ((flags & CAMEL_SESSION_PASSWORD_STATIC) != 0)
+ eflags = E_PASSWORDS_REMEMBER_NEVER;
+ else if (config_service == NULL)
+ eflags = E_PASSWORDS_REMEMBER_SESSION;
+ else
+ eflags = E_PASSWORDS_REMEMBER_FOREVER;
+
+ if (flags & CAMEL_SESSION_PASSWORD_REPROMPT)
+ eflags |= E_PASSWORDS_REPROMPT;
+
+ if (flags & CAMEL_SESSION_PASSWORD_SECRET)
+ eflags |= E_PASSWORDS_SECRET;
+
+ if (flags & CAMEL_SESSION_PASSPHRASE)
+ eflags |= E_PASSWORDS_PASSPHRASE;
+
+ /* HACK: breaks abstraction ...
+ * e_account_writable() doesn't use the
+ * EAccount, it also uses the same writable
+ * key for source and transport. */
+ if (!e_account_writable (NULL, E_ACCOUNT_SOURCE_SAVE_PASSWD))
+ eflags |= E_PASSWORDS_DISABLE_REMEMBER;
+
+ ret = e_passwords_ask_password (
+ title, NULL, key, prompt,
+ eflags, &remember, NULL);
+
+ if (!ret)
+ e_passwords_forget_password (NULL, key);
+
+ g_free (title);
+
+ if (ret && config_service) {
+ config_service->save_passwd = remember;
+ e_account_list_save (e_get_account_list ());
+ }
+
+ if (config_service)
+ config_service->get_password_canceled = ret == NULL;
+ }
+ }
+
+ g_free (key);
+ }
+
+ if (ret == NULL)
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("User canceled operation."));
+
+ return ret;
+}
+
+static gboolean
+mail_session_forget_password (CamelSession *session,
+ CamelService *service,
+ const gchar *item,
+ GError **error)
+{
+ gchar *key;
+
+ key = mail_session_make_key (service, item);
+
+ e_passwords_forget_password (NULL, key);
+
+ g_free (key);
+
+ return TRUE;
+}
+
+static gint
+mail_session_alert_user (CamelSession *session,
+ CamelSessionAlertType type,
+ const gchar *prompt,
+ GSList *button_captions)
+{
+ struct _user_message_msg *m;
+ GCancellable *cancellable;
+ gint result = -1;
+ GSList *iter;
+
+ m = mail_msg_new (&user_message_info);
+ m->ismain = mail_in_main_thread ();
+ m->type = type;
+ m->prompt = g_strdup (prompt);
+ m->done = e_flag_new ();
+ m->button_captions = g_slist_copy (button_captions);
+
+ for (iter = m->button_captions; iter; iter = iter->next)
+ iter->data = g_strdup (iter->data);
+
+ if (g_slist_length (button_captions) > 1)
+ mail_msg_ref (m);
+
+ cancellable = m->base.cancellable;
+
+ if (m->ismain)
+ user_message_exec (m, cancellable, &m->base.error);
+ else
+ mail_msg_main_loop_push (m);
+
+ if (g_slist_length (button_captions) > 1) {
+ e_flag_wait (m->done);
+ result = m->result;
+ mail_msg_unref (m);
+ } else if (m->ismain)
+ mail_msg_unref (m);
+
+ return result;
+}
+
+static CamelFilterDriver *
+mail_session_get_filter_driver (CamelSession *session,
+ const gchar *type,
+ GError **error)
+{
+ return (CamelFilterDriver *) mail_call_main (
+ MAIL_CALL_p_ppp, (MailMainFunc) main_get_filter_driver,
+ session, type, error);
+}
+
+static gboolean
+mail_session_lookup_addressbook (CamelSession *session,
+ const gchar *name)
+{
+ CamelInternetAddress *addr;
+ gboolean ret;
+
+ if (!mail_config_get_lookup_book ())
+ return FALSE;
+
+ addr = camel_internet_address_new ();
+ camel_address_decode ((CamelAddress *) addr, name);
+ ret = em_utils_in_addressbook (
+ addr, mail_config_get_lookup_book_local_only ());
+ g_object_unref (addr);
+
+ return ret;
+}
+
+static gboolean
+mail_session_forward_to (CamelSession *session,
+ CamelFolder *folder,
+ CamelMimeMessage *message,
+ const gchar *address,
+ GError **error)
+{
+ EAccount *account;
+ CamelMimeMessage *forward;
+ CamelStream *mem;
+ CamelInternetAddress *addr;
+ CamelFolder *out_folder;
+ CamelMessageInfo *info;
+ CamelMedium *medium;
+ const gchar *from_address;
+ const gchar *from_name;
+ const gchar *header_name;
+ struct _camel_header_raw *xev;
+ gchar *subject;
+
+ g_return_val_if_fail (folder != NULL, FALSE);
+ g_return_val_if_fail (message != NULL, FALSE);
+ g_return_val_if_fail (address != NULL, FALSE);
+
+ if (!*address) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("No destination address provided, forward "
+ "of the message has been cancelled."));
+ return FALSE;
+ }
+
+ account = em_utils_guess_account_with_recipients (message, folder);
+ if (!account) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("No account found to use, forward of the "
+ "message has been cancelled."));
+ return FALSE;
+ }
+
+ from_address = account->id->address;
+ from_name = account->id->name;
+
+ forward = camel_mime_message_new ();
+
+ /* make copy of the message, because we are going to modify it */
+ mem = camel_stream_mem_new ();
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message), mem, NULL, NULL);
+ g_seekable_seek (G_SEEKABLE (mem), 0, G_SEEK_SET, NULL, NULL);
+ camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (forward), mem, NULL, NULL);
+ g_object_unref (mem);
+
+ /* clear previous recipients */
+ camel_mime_message_set_recipients (
+ forward, CAMEL_RECIPIENT_TYPE_TO, NULL);
+ camel_mime_message_set_recipients (
+ forward, CAMEL_RECIPIENT_TYPE_CC, NULL);
+ camel_mime_message_set_recipients (
+ forward, CAMEL_RECIPIENT_TYPE_BCC, NULL);
+ camel_mime_message_set_recipients (
+ forward, CAMEL_RECIPIENT_TYPE_RESENT_TO, NULL);
+ camel_mime_message_set_recipients (
+ forward, CAMEL_RECIPIENT_TYPE_RESENT_CC, NULL);
+ camel_mime_message_set_recipients (
+ forward, CAMEL_RECIPIENT_TYPE_RESENT_BCC, NULL);
+
+ medium = CAMEL_MEDIUM (forward);
+
+ /* remove all delivery and notification headers */
+ header_name = "Disposition-Notification-To";
+ while (camel_medium_get_header (medium, header_name))
+ camel_medium_remove_header (medium, header_name);
+
+ header_name = "Delivered-To";
+ while (camel_medium_get_header (medium, header_name))
+ camel_medium_remove_header (medium, header_name);
+
+ /* remove any X-Evolution-* headers that may have been set */
+ xev = mail_tool_remove_xevolution_headers (forward);
+ camel_header_raw_clear (&xev);
+
+ /* from */
+ addr = camel_internet_address_new ();
+ camel_internet_address_add (addr, from_name, from_address);
+ camel_mime_message_set_from (forward, addr);
+ g_object_unref (addr);
+
+ /* to */
+ addr = camel_internet_address_new ();
+ camel_address_decode (CAMEL_ADDRESS (addr), address);
+ camel_mime_message_set_recipients (
+ forward, CAMEL_RECIPIENT_TYPE_TO, addr);
+ g_object_unref (addr);
+
+ /* subject */
+ subject = mail_tool_generate_forward_subject (message);
+ camel_mime_message_set_subject (forward, subject);
+ g_free (subject);
+
+ /* and send it */
+ info = camel_message_info_new (NULL);
+ out_folder = e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_OUTBOX);
+ camel_message_info_set_flags (
+ info, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN);
+
+ /* FIXME Pass a GCancellable. */
+ e_mail_folder_append_message (
+ out_folder, forward, info, G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) ms_forward_to_cb, session);
+
+ camel_message_info_free (info);
+
+ return TRUE;
+}
+
+static void
+mail_session_get_socks_proxy (CamelSession *session,
+ const gchar *for_host,
+ gchar **host_ret,
+ gint *port_ret)
+{
+ EMailSession *mail_session;
+ gchar *uri;
+
+ g_return_if_fail (session != NULL);
+ g_return_if_fail (for_host != NULL);
+ g_return_if_fail (host_ret != NULL);
+ g_return_if_fail (port_ret != NULL);
+
+ mail_session = E_MAIL_SESSION (session);
+ g_return_if_fail (mail_session != NULL);
+ g_return_if_fail (mail_session->priv != NULL);
+
+ *host_ret = NULL;
+ *port_ret = 0;
+
+ uri = g_strconcat ("socks://", for_host, NULL);
+
+ if (e_proxy_require_proxy_for_uri (mail_session->priv->proxy, uri)) {
+ SoupURI *suri;
+
+ suri = e_proxy_peek_uri_for (mail_session->priv->proxy, uri);
+ if (suri) {
+ *host_ret = g_strdup (suri->host);
+ *port_ret = suri->port;
+ }
+ }
+
+ g_free (uri);
+}
+
+static gboolean
+mail_session_authenticate_sync (CamelSession *session,
+ CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceAuthType *authtype = NULL;
+ CamelAuthenticationResult result;
+ CamelProvider *provider;
+ CamelSettings *settings;
+ const gchar *password;
+ guint32 password_flags;
+ GError *local_error = NULL;
+
+ /* Do not chain up. Camel's default method is only an example for
+ * subclasses to follow. Instead we mimic most of its logic here. */
+
+ provider = camel_service_get_provider (service);
+ settings = camel_service_get_settings (service);
+
+ /* APOP is one case where a non-SASL mechanism name is passed, so
+ * don't bail if the CamelServiceAuthType struct comes back NULL. */
+ if (mechanism != NULL)
+ authtype = camel_sasl_authtype (mechanism);
+
+ /* If the SASL mechanism does not involve a user
+ * password, then it gets one shot to authenticate. */
+ if (authtype != NULL && !authtype->need_password) {
+ result = camel_service_authenticate_sync (
+ service, mechanism, cancellable, error);
+ if (result == CAMEL_AUTHENTICATION_REJECTED)
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("%s authentication failed"), mechanism);
+ return (result == CAMEL_AUTHENTICATION_ACCEPTED);
+ }
+
+ /* Some SASL mechanisms can attempt to authenticate without a
+ * user password being provided (e.g. single-sign-on credentials),
+ * but can fall back to a user password. Handle that case next. */
+ if (mechanism != NULL) {
+ CamelProvider *provider;
+ CamelSasl *sasl;
+ const gchar *service_name;
+ gboolean success = FALSE;
+
+ provider = camel_service_get_provider (service);
+ service_name = provider->protocol;
+
+ /* XXX Would be nice if camel_sasl_try_empty_password_sync()
+ * returned CamelAuthenticationResult so it's easier to
+ * detect errors. */
+ sasl = camel_sasl_new (service_name, mechanism, service);
+ if (sasl != NULL) {
+ success = camel_sasl_try_empty_password_sync (
+ sasl, cancellable, &local_error);
+ g_object_unref (sasl);
+ }
+
+ if (success)
+ return TRUE;
+ }
+
+ /* Abort authentication if we got cancelled.
+ * Otherwise clear any errors and press on. */
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return FALSE;
+
+ g_clear_error (&local_error);
+
+ password_flags = CAMEL_SESSION_PASSWORD_SECRET;
+
+retry:
+ password = camel_service_get_password (service);
+
+ if (password == NULL) {
+ CamelNetworkSettings *network_settings;
+ const gchar *host;
+ const gchar *user;
+ gchar *prompt;
+ gchar *new_passwd;
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_get_host (network_settings);
+ user = camel_network_settings_get_user (network_settings);
+
+ prompt = camel_session_build_password_prompt (
+ provider->name, user, host);
+
+ new_passwd = camel_session_get_password (
+ session, service, prompt, "password",
+ password_flags, &local_error);
+ camel_service_set_password (service, new_passwd);
+ password = camel_service_get_password (service);
+ g_free (new_passwd);
+
+ g_free (prompt);
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ if (password == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("No password was provided"));
+ return FALSE;
+ }
+ }
+
+ result = camel_service_authenticate_sync (
+ service, mechanism, cancellable, error);
+
+ if (result == CAMEL_AUTHENTICATION_REJECTED) {
+ password_flags |= CAMEL_SESSION_PASSWORD_REPROMPT;
+ camel_service_set_password (service, NULL);
+ goto retry;
+ }
+
+ return (result == CAMEL_AUTHENTICATION_ACCEPTED);
+}
+
+static void
+e_mail_session_class_init (EMailSessionClass *class)
+{
+ GObjectClass *object_class;
+ CamelSessionClass *session_class;
+
+ g_type_class_add_private (class, sizeof (EMailSessionPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mail_session_set_property;
+ object_class->get_property = mail_session_get_property;
+ object_class->dispose = mail_session_dispose;
+ object_class->finalize = mail_session_finalize;
+ object_class->notify = mail_session_notify;
+ object_class->constructed = mail_session_constructed;
+
+ session_class = CAMEL_SESSION_CLASS (class);
+ session_class->add_service = mail_session_add_service;
+ session_class->get_password = mail_session_get_password;
+ session_class->forget_password = mail_session_forget_password;
+ session_class->alert_user = mail_session_alert_user;
+ session_class->get_filter_driver = mail_session_get_filter_driver;
+ session_class->lookup_addressbook = mail_session_lookup_addressbook;
+ session_class->forward_to = mail_session_forward_to;
+ session_class->get_socks_proxy = mail_session_get_socks_proxy;
+ session_class->authenticate_sync = mail_session_authenticate_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FOLDER_CACHE,
+ g_param_spec_object (
+ "folder-cache",
+ NULL,
+ NULL,
+ MAIL_TYPE_FOLDER_CACHE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /* XXX This property can be removed once Evolution moves to
+ * GSettings and can use transform functions when binding
+ * properties to settings. */
+ g_object_class_install_property (
+ object_class,
+ PROP_JUNK_FILTER_NAME,
+ g_param_spec_string (
+ "junk-filter-name",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_LOCAL_STORE,
+ g_param_spec_object (
+ "local-store",
+ "Local Store",
+ "Built-in local store",
+ CAMEL_TYPE_STORE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EMailSession::flush-outbox
+ * @session: the email session
+ *
+ * Emitted if the send folder should be flushed.
+ **/
+ signals[FLUSH_OUTBOX] = g_signal_new (
+ "flush-outbox",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, /* struct offset */
+ NULL, NULL, /* accumulator */
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * EMailSession::store-added
+ * @session: the email session
+ * @store: the CamelStore
+ *
+ * Emitted when a store is added
+ **/
+ signals[STORE_ADDED] = g_signal_new (
+ "store-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, /* struct offset */
+ NULL, NULL, /* accumulator */
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ CAMEL_TYPE_STORE);
+
+
+ /**
+ * EMailSession::store-removed
+ * @session: the email session
+ * @store: the CamelStore
+ *
+ * Emitted when a store is removed
+ **/
+ signals[STORE_REMOVED] = g_signal_new (
+ "store-removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, /* struct offset */
+ NULL, NULL, /* accumulator */
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ CAMEL_TYPE_STORE);
+
+
+}
+
+static void
+e_mail_session_init (EMailSession *session)
+{
+ GSettings *settings;
+ GHashTable *junk_filters;
+
+ junk_filters = g_hash_table_new (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal);
+
+ session->priv = E_MAIL_SESSION_GET_PRIVATE (session);
+ session->priv->junk_filters = junk_filters;
+ session->priv->proxy = e_proxy_new ();
+
+ session->priv->local_folders =
+ g_ptr_array_new_with_free_func (
+ (GDestroyNotify) g_object_unref);
+ session->priv->local_folder_uris =
+ g_ptr_array_new_with_free_func (
+ (GDestroyNotify) g_free);
+
+ /* Initialize the EAccount setup. */
+ e_account_writable (NULL, E_ACCOUNT_SOURCE_SAVE_PASSWD);
+
+ settings = g_settings_new ("org.gnome.evolution.mail");
+
+ camel_session_set_check_junk (
+ CAMEL_SESSION (session), g_settings_get_boolean (
+ settings, "junk-check-incoming"));
+ g_signal_connect (
+ settings, "changed",
+ G_CALLBACK (mail_session_check_junk_notify), session);
+
+ mail_config_reload_junk_headers (session);
+
+ e_proxy_setup_proxy (session->priv->proxy);
+
+ g_object_unref (settings);
+}
+
+EMailSession *
+e_mail_session_new (void)
+{
+ const gchar *user_data_dir;
+ const gchar *user_cache_dir;
+
+ user_data_dir = mail_session_get_data_dir ();
+ user_cache_dir = mail_session_get_cache_dir ();
+
+ return g_object_new (
+ E_TYPE_MAIL_SESSION,
+ "user-data-dir", user_data_dir,
+ "user-cache-dir", user_cache_dir,
+ NULL);
+}
+
+MailFolderCache *
+e_mail_session_get_folder_cache (EMailSession *session)
+{
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+
+ return session->priv->folder_cache;
+}
+
+CamelStore *
+e_mail_session_get_local_store (EMailSession *session)
+{
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+
+ return session->priv->local_store;
+}
+
+CamelFolder *
+e_mail_session_get_local_folder (EMailSession *session,
+ EMailLocalFolder type)
+{
+ GPtrArray *local_folders;
+ CamelFolder *folder;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+
+ local_folders = session->priv->local_folders;
+ g_return_val_if_fail (type < local_folders->len, NULL);
+
+ folder = g_ptr_array_index (local_folders, type);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ return folder;
+}
+
+const gchar *
+e_mail_session_get_local_folder_uri (EMailSession *session,
+ EMailLocalFolder type)
+{
+ GPtrArray *local_folder_uris;
+ const gchar *folder_uri;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+
+ local_folder_uris = session->priv->local_folder_uris;
+ g_return_val_if_fail (type < local_folder_uris->len, NULL);
+
+ folder_uri = g_ptr_array_index (local_folder_uris, type);
+ g_return_val_if_fail (folder_uri != NULL, NULL);
+
+ return folder_uri;
+}
+
+GList *
+e_mail_session_get_available_junk_filters (EMailSession *session)
+{
+ GList *list, *link;
+ GQueue trash = G_QUEUE_INIT;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+
+ list = g_hash_table_get_values (session->priv->junk_filters);
+
+ /* Discard unavailable junk filters. (e.g. Junk filter
+ * requires Bogofilter but Bogofilter is not installed,
+ * hence the junk filter is unavailable.) */
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ EMailJunkFilter *junk_filter;
+
+ junk_filter = E_MAIL_JUNK_FILTER (link->data);
+ if (!e_mail_junk_filter_available (junk_filter))
+ g_queue_push_tail (&trash, link);
+ }
+
+ while ((link = g_queue_pop_head (&trash)) != NULL)
+ list = g_list_delete_link (list, link);
+
+ /* Sort the remaining junk filters by display name. */
+
+ return g_list_sort (list, (GCompareFunc) e_mail_junk_filter_compare);
+}
+
+static void
+mail_session_get_inbox_thread (GSimpleAsyncResult *simple,
+ EMailSession *session,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ context->folder = e_mail_session_get_inbox_sync (
+ session, context->uid, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+CamelFolder *
+e_mail_session_get_inbox_sync (EMailSession *session,
+ const gchar *service_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+ g_return_val_if_fail (service_uid != NULL, NULL);
+
+ service = camel_session_get_service (
+ CAMEL_SESSION (session), service_uid);
+
+ if (!CAMEL_IS_STORE (service))
+ return NULL;
+
+ if (!em_utils_connect_service_sync (service, cancellable, error))
+ return NULL;
+
+ return camel_store_get_inbox_folder_sync (
+ CAMEL_STORE (service), cancellable, error);
+}
+
+void
+e_mail_session_get_inbox (EMailSession *session,
+ const gchar *service_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+ g_return_if_fail (service_uid != NULL);
+
+ context = g_slice_new0 (AsyncContext);
+ context->uid = g_strdup (service_uid);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (session), callback,
+ user_data, e_mail_session_get_inbox);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_session_get_inbox_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+CamelFolder *
+e_mail_session_get_inbox_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (session),
+ e_mail_session_get_inbox), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (context->folder), NULL);
+
+ return g_object_ref (context->folder);
+}
+
+static void
+mail_session_get_trash_thread (GSimpleAsyncResult *simple,
+ EMailSession *session,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ context->folder = e_mail_session_get_trash_sync (
+ session, context->uid, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+CamelFolder *
+e_mail_session_get_trash_sync (EMailSession *session,
+ const gchar *service_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+ g_return_val_if_fail (service_uid != NULL, NULL);
+
+ service = camel_session_get_service (
+ CAMEL_SESSION (session), service_uid);
+
+ if (!CAMEL_IS_STORE (service))
+ return NULL;
+
+ if (!em_utils_connect_service_sync (service, cancellable, error))
+ return NULL;
+
+ return camel_store_get_trash_folder_sync (
+ CAMEL_STORE (service), cancellable, error);
+}
+
+void
+e_mail_session_get_trash (EMailSession *session,
+ const gchar *service_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+ g_return_if_fail (service_uid != NULL);
+
+ context = g_slice_new0 (AsyncContext);
+ context->uid = g_strdup (service_uid);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (session), callback,
+ user_data, e_mail_session_get_trash);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_session_get_trash_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+CamelFolder *
+e_mail_session_get_trash_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (session),
+ e_mail_session_get_trash), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (context->folder), NULL);
+
+ return g_object_ref (context->folder);
+}
+
+static void
+mail_session_uri_to_folder_thread (GSimpleAsyncResult *simple,
+ EMailSession *session,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ context->folder = e_mail_session_uri_to_folder_sync (
+ session, context->uri, context->flags,
+ cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+CamelFolder *
+e_mail_session_uri_to_folder_sync (EMailSession *session,
+ const gchar *folder_uri,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelFolder *folder;
+ gchar *folder_name;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+ g_return_val_if_fail (folder_uri != NULL, NULL);
+
+ success = e_mail_folder_uri_parse (
+ CAMEL_SESSION (session), folder_uri,
+ &store, &folder_name, error);
+
+ if (!success)
+ return NULL;
+
+ folder = camel_store_get_folder_sync (
+ store, folder_name, flags, cancellable, error);
+
+ if (folder != NULL) {
+ MailFolderCache *folder_cache;
+ folder_cache = e_mail_session_get_folder_cache (session);
+ mail_folder_cache_note_folder (folder_cache, folder);
+ }
+
+ g_free (folder_name);
+ g_object_unref (store);
+
+ return folder;
+}
+
+void
+e_mail_session_uri_to_folder (EMailSession *session,
+ const gchar *folder_uri,
+ CamelStoreGetFolderFlags flags,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+ g_return_if_fail (folder_uri != NULL);
+
+ context = g_slice_new0 (AsyncContext);
+ context->uri = g_strdup (folder_uri);
+ context->flags = flags;
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (session), callback,
+ user_data, e_mail_session_uri_to_folder);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_session_uri_to_folder_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+CamelFolder *
+e_mail_session_uri_to_folder_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (session),
+ e_mail_session_uri_to_folder), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (context->folder), NULL);
+
+ return g_object_ref (context->folder);
+}
+
+/******************************** Legacy API *********************************/
+
+void
+mail_session_flush_filter_log (EMailSession *session)
+{
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+
+ if (session->priv->filter_logfile)
+ fflush (session->priv->filter_logfile);
+}
+
+const gchar *
+mail_session_get_data_dir (void)
+{
+ if (G_UNLIKELY (mail_data_dir == NULL))
+ mail_data_dir = g_build_filename (
+ e_get_user_data_dir (), "mail", NULL);
+
+ return mail_data_dir;
+}
+
+const gchar *
+mail_session_get_cache_dir (void)
+{
+ if (G_UNLIKELY (mail_cache_dir == NULL))
+ mail_cache_dir = g_build_filename (
+ e_get_user_cache_dir (), "mail", NULL);
+
+ return mail_cache_dir;
+}
+
+const gchar *
+mail_session_get_config_dir (void)
+{
+ if (G_UNLIKELY (mail_config_dir == NULL))
+ mail_config_dir = g_build_filename (
+ e_get_user_config_dir (), "mail", NULL);
+
+ return mail_config_dir;
+}
+
diff --git a/libemail-engine/e-mail-session.h b/libemail-engine/e-mail-session.h
new file mode 100644
index 0000000000..af7892dc22
--- /dev/null
+++ b/libemail-engine/e-mail-session.h
@@ -0,0 +1,135 @@
+/*
+ * e-mail-session.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_MAIL_SESSION_H
+#define E_MAIL_SESSION_H
+
+#include <camel/camel.h>
+#include <libemail-engine/e-mail-enums.h>
+#include <libemail-engine/mail-folder-cache.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SESSION \
+ (e_mail_session_get_type ())
+#define E_MAIL_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_SESSION, EMailSession))
+#define E_MAIL_SESSION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_SESSION, EMailSessionClass))
+#define E_IS_MAIL_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_SESSION))
+#define E_IS_MAIL_SESSION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MAIL_SESSION))
+#define E_MAIL_SESSION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MAIL_SESSION, EMailSessionClass))
+
+/* Built-in CamelServices */
+#define E_MAIL_SESSION_LOCAL_UID "local" /* "On This Computer" */
+#define E_MAIL_SESSION_VFOLDER_UID "vfolder" /* "Search Folders" */
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSession EMailSession;
+typedef struct _EMailSessionClass EMailSessionClass;
+typedef struct _EMailSessionPrivate EMailSessionPrivate;
+
+struct _EMailSession {
+ CamelSession parent;
+ EMailSessionPrivate *priv;
+};
+
+struct _EMailSessionClass {
+ CamelSessionClass parent_class;
+};
+
+GType e_mail_session_get_type (void);
+EMailSession * e_mail_session_new (void);
+MailFolderCache *
+ e_mail_session_get_folder_cache (EMailSession *session);
+CamelStore * e_mail_session_get_local_store (EMailSession *session);
+CamelFolder * e_mail_session_get_local_folder (EMailSession *session,
+ EMailLocalFolder type);
+const gchar * e_mail_session_get_local_folder_uri
+ (EMailSession *session,
+ EMailLocalFolder type);
+GList * e_mail_session_get_available_junk_filters
+ (EMailSession *session);
+CamelFolder * e_mail_session_get_inbox_sync (EMailSession *session,
+ const gchar *service_uid,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_session_get_inbox (EMailSession *session,
+ const gchar *service_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolder * e_mail_session_get_inbox_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error);
+CamelFolder * e_mail_session_get_trash_sync (EMailSession *session,
+ const gchar *service_uid,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_session_get_trash (EMailSession *session,
+ const gchar *service_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolder * e_mail_session_get_trash_finish (EMailSession *session,
+ GAsyncResult *result,
+ GError **error);
+CamelFolder * e_mail_session_uri_to_folder_sync
+ (EMailSession *session,
+ const gchar *folder_uri,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_session_uri_to_folder (EMailSession *session,
+ const gchar *folder_uri,
+ CamelStoreGetFolderFlags flags,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolder * e_mail_session_uri_to_folder_finish
+ (EMailSession *session,
+ GAsyncResult *result,
+ GError **error);
+
+/*** Legacy API ***/
+
+void mail_session_flush_filter_log (EMailSession *session);
+const gchar * mail_session_get_data_dir (void);
+const gchar * mail_session_get_cache_dir (void);
+const gchar * mail_session_get_config_dir (void);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SESSION_H */
diff --git a/libemail-engine/e-mail-store-utils.c b/libemail-engine/e-mail-store-utils.c
new file mode 100644
index 0000000000..757f86dfd5
--- /dev/null
+++ b/libemail-engine/e-mail-store-utils.c
@@ -0,0 +1,385 @@
+/*
+ * e-mail-store-utils.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-mail-utils.h"
+
+#include "e-mail-store-utils.h"
+
+#include <glib/gi18n-lib.h>
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _AsyncContext {
+ gchar *full_name;
+};
+
+static void
+async_context_free (AsyncContext *context)
+{
+ g_free (context->full_name);
+
+ g_slice_free (AsyncContext, context);
+}
+
+static void
+mail_store_create_folder_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ e_mail_store_create_folder_sync (
+ CAMEL_STORE (object), context->full_name,
+ cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+gboolean
+e_mail_store_create_folder_sync (CamelStore *store,
+ const gchar *full_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderInfo *folder_info;
+ gchar *copied_full_name;
+ gchar *display_name;
+ const gchar *parent;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (full_name != NULL, FALSE);
+
+ copied_full_name = g_strdup (full_name);
+ display_name = strrchr (copied_full_name, '/');
+ if (display_name == NULL) {
+ display_name = copied_full_name;
+ parent = "";
+ } else {
+ *display_name++ = '\0';
+ parent = copied_full_name;
+ }
+
+ folder_info = camel_store_create_folder_sync (
+ store, parent, display_name, cancellable, error);
+
+ g_free (copied_full_name);
+
+ if (folder_info == NULL)
+ return FALSE;
+
+ if (CAMEL_IS_SUBSCRIBABLE (store))
+ success = camel_subscribable_subscribe_folder_sync (
+ CAMEL_SUBSCRIBABLE (store),
+ full_name, cancellable, error);
+
+ camel_store_free_folder_info (store, folder_info);
+
+ return success;
+}
+
+void
+e_mail_store_create_folder (CamelStore *store,
+ const gchar *full_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (full_name != NULL);
+
+ context = g_slice_new0 (AsyncContext);
+ context->full_name = g_strdup (full_name);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (store), callback, user_data,
+ e_mail_store_create_folder);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, mail_store_create_folder_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_store_create_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (store),
+ e_mail_store_create_folder), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_store_go_offline_thread (GSimpleAsyncResult *simple,
+ CamelStore *store,
+ GCancellable *cancellable)
+{
+ CamelService *service;
+ gchar *service_name;
+ GError *error = NULL;
+
+ service = CAMEL_SERVICE (store);
+
+ service_name = camel_service_get_name (service, TRUE);
+ camel_operation_push_message (
+ cancellable, _("Disconnecting from '%s'"), service_name);
+ g_free (service_name);
+
+ if (CAMEL_IS_DISCO_STORE (store)) {
+ CamelDiscoStore *disco_store;
+
+ disco_store = CAMEL_DISCO_STORE (store);
+
+ if (camel_disco_store_can_work_offline (disco_store))
+ camel_disco_store_set_status (
+ disco_store, CAMEL_DISCO_STORE_OFFLINE,
+ cancellable, &error);
+ else
+ em_utils_disconnect_service_sync (service, TRUE, cancellable, &error);
+
+ } else if (CAMEL_IS_OFFLINE_STORE (store)) {
+ CamelOfflineStore *offline_store;
+
+ offline_store = CAMEL_OFFLINE_STORE (store);
+
+ camel_offline_store_set_online_sync (
+ offline_store, FALSE, cancellable, &error);
+
+ } else
+ em_utils_disconnect_service_sync (service, TRUE, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+
+ camel_operation_pop_message (cancellable);
+}
+
+void
+e_mail_store_go_offline (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ /* Cancel any pending connect first so the set_offline_op
+ * thread won't get queued behind a hung connect op. */
+ camel_service_cancel_connect (CAMEL_SERVICE (store));
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (store), callback,
+ user_data, e_mail_store_go_offline);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_store_go_offline_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_store_go_offline_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (store), e_mail_store_go_offline), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_store_go_online_thread (GSimpleAsyncResult *simple,
+ CamelStore *store,
+ GCancellable *cancellable)
+{
+ CamelService *service;
+ gchar *service_name;
+ GError *error = NULL;
+
+ service = CAMEL_SERVICE (store);
+
+ service_name = camel_service_get_name (service, TRUE);
+ camel_operation_push_message (
+ cancellable, _("Reconnecting to '%s'"), service_name);
+ g_free (service_name);
+
+ if (CAMEL_IS_DISCO_STORE (store))
+ camel_disco_store_set_status (
+ CAMEL_DISCO_STORE (store),
+ CAMEL_DISCO_STORE_ONLINE,
+ cancellable, &error);
+
+ else if (CAMEL_IS_OFFLINE_STORE (store))
+ camel_offline_store_set_online_sync (
+ CAMEL_OFFLINE_STORE (store),
+ TRUE, cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+
+ camel_operation_pop_message (cancellable);
+}
+
+void
+e_mail_store_go_online (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (store), callback,
+ user_data, e_mail_store_go_online);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_store_go_online_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_store_go_online_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (store), e_mail_store_go_online), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+mail_store_prepare_for_offline_thread (GSimpleAsyncResult *simple,
+ CamelStore *store,
+ GCancellable *cancellable)
+{
+ CamelService *service;
+ gchar *service_name;
+ GError *error = NULL;
+
+ service = CAMEL_SERVICE (store);
+
+ service_name = camel_service_get_name (service, TRUE);
+ camel_operation_push_message (
+ cancellable, _("Preparing account '%s' for offline"),
+ service_name);
+ g_free (service_name);
+
+ if (CAMEL_IS_DISCO_STORE (store))
+ camel_disco_store_prepare_for_offline (
+ CAMEL_DISCO_STORE (store), cancellable, &error);
+
+ else if (CAMEL_IS_OFFLINE_STORE (store))
+ camel_offline_store_prepare_for_offline_sync (
+ CAMEL_OFFLINE_STORE (store), cancellable, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+
+ camel_operation_pop_message (cancellable);
+}
+
+void
+e_mail_store_prepare_for_offline (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (store), callback, user_data,
+ e_mail_store_prepare_for_offline);
+
+ g_simple_async_result_run_in_thread (
+ simple, (GSimpleAsyncThreadFunc)
+ mail_store_prepare_for_offline_thread,
+ io_priority, cancellable);
+
+ g_object_unref (simple);
+}
+
+gboolean
+e_mail_store_prepare_for_offline_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (store),
+ e_mail_store_prepare_for_offline), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
diff --git a/libemail-engine/e-mail-store-utils.h b/libemail-engine/e-mail-store-utils.h
new file mode 100644
index 0000000000..de4484c020
--- /dev/null
+++ b/libemail-engine/e-mail-store-utils.h
@@ -0,0 +1,74 @@
+/*
+ * e-mail-store-utils.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_MAIL_STORE_UTILS_H
+#define E_MAIL_STORE_UTILS_H
+
+/* CamelStore wrappers with Evolution-specific policies. */
+
+#include <camel/camel.h>
+
+G_BEGIN_DECLS
+
+gboolean e_mail_store_create_folder_sync (CamelStore *store,
+ const gchar *full_name,
+ GCancellable *cancellable,
+ GError **error);
+void e_mail_store_create_folder (CamelStore *store,
+ const gchar *full_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_store_create_folder_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+
+void e_mail_store_go_offline (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_store_go_offline_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+
+void e_mail_store_go_online (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_store_go_online_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+
+void e_mail_store_prepare_for_offline
+ (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_mail_store_prepare_for_offline_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_STORE_UTILS_H */
diff --git a/libemail-engine/e-mail-utils.c b/libemail-engine/e-mail-utils.c
new file mode 100644
index 0000000000..05f5381147
--- /dev/null
+++ b/libemail-engine/e-mail-utils.c
@@ -0,0 +1,1062 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Srinivasa Ragavan <sragavan@gnome.org>
+ *
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <time.h>
+
+#include <glib/gstdio.h>
+
+#ifdef G_OS_WIN32
+/* Work around namespace clobbage in <windows.h> */
+#define DATADIR windows_DATADIR
+#include <windows.h>
+#undef DATADIR
+#undef interface
+#endif
+
+#include <libebook/e-book-client.h>
+#include <libebook/e-book-query.h>
+
+
+#include <glib/gi18n.h>
+
+#include <gio/gio.h>
+
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-flag.h>
+#include <libedataserver/e-proxy.h>
+
+#include "libemail-utils/e-account-utils.h"
+#include "libemail-utils/mail-mt.h"
+
+#include "e-mail-folder-utils.h"
+#include "e-mail-session.h"
+#include "e-mail-utils.h"
+#include "mail-tools.h"
+
+#define d(x)
+
+/**
+ * em_utils_folder_is_templates:
+ * @folder: a #CamelFolder
+ *
+ * Decides if @folder is a Templates folder.
+ *
+ * Returns %TRUE if this is a Templates folder or %FALSE otherwise.
+ **/
+
+gboolean
+em_utils_folder_is_templates (CamelFolder *folder)
+{
+ CamelFolder *local_templates_folder;
+ CamelSession *session;
+ CamelStore *store;
+ EAccountList *account_list;
+ EIterator *iterator;
+ gchar *folder_uri;
+ gboolean is_templates = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ store = camel_folder_get_parent_store (folder);
+ session = camel_service_get_session (CAMEL_SERVICE (store));
+
+ local_templates_folder =
+ e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_TEMPLATES);
+
+ if (folder == local_templates_folder)
+ return TRUE;
+
+ folder_uri = e_mail_folder_uri_from_folder (folder);
+
+ account_list = e_get_account_list ();
+ iterator = e_list_get_iterator (E_LIST (account_list));
+
+ while (!is_templates && e_iterator_is_valid (iterator)) {
+ EAccount *account;
+
+ /* XXX EIterator misuses const. */
+ account = (EAccount *) e_iterator_get (iterator);
+
+ if (account->templates_folder_uri != NULL)
+ is_templates = e_mail_folder_uri_equal (
+ session, folder_uri,
+ account->templates_folder_uri);
+
+ e_iterator_next (iterator);
+ }
+
+ g_object_unref (iterator);
+ g_free (folder_uri);
+
+ return is_templates;
+}
+
+/**
+ * em_utils_folder_is_drafts:
+ * @folder: a #CamelFolder
+ *
+ * Decides if @folder is a Drafts folder.
+ *
+ * Returns %TRUE if this is a Drafts folder or %FALSE otherwise.
+ **/
+gboolean
+em_utils_folder_is_drafts (CamelFolder *folder)
+{
+ CamelFolder *local_drafts_folder;
+ CamelSession *session;
+ CamelStore *store;
+ EAccountList *account_list;
+ EIterator *iterator;
+ gchar *folder_uri;
+ gboolean is_drafts = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ store = camel_folder_get_parent_store (folder);
+ session = camel_service_get_session (CAMEL_SERVICE (store));
+
+ local_drafts_folder =
+ e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_DRAFTS);
+
+ if (folder == local_drafts_folder)
+ return TRUE;
+
+ folder_uri = e_mail_folder_uri_from_folder (folder);
+
+ account_list = e_get_account_list ();
+ iterator = e_list_get_iterator (E_LIST (account_list));
+
+ while (!is_drafts && e_iterator_is_valid (iterator)) {
+ EAccount *account;
+
+ /* XXX EIterator misuses const. */
+ account = (EAccount *) e_iterator_get (iterator);
+
+ if (account->drafts_folder_uri != NULL)
+ is_drafts = e_mail_folder_uri_equal (
+ session, folder_uri,
+ account->drafts_folder_uri);
+
+ e_iterator_next (iterator);
+ }
+
+ g_object_unref (iterator);
+ g_free (folder_uri);
+
+ return is_drafts;
+}
+
+/**
+ * em_utils_folder_is_sent:
+ * @folder: a #CamelFolder
+ *
+ * Decides if @folder is a Sent folder.
+ *
+ * Returns %TRUE if this is a Sent folder or %FALSE otherwise.
+ **/
+gboolean
+em_utils_folder_is_sent (CamelFolder *folder)
+{
+ CamelFolder *local_sent_folder;
+ CamelSession *session;
+ CamelStore *store;
+ EAccountList *account_list;
+ EIterator *iterator;
+ gchar *folder_uri;
+ gboolean is_sent = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ store = camel_folder_get_parent_store (folder);
+ session = camel_service_get_session (CAMEL_SERVICE (store));
+
+ local_sent_folder =
+ e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_SENT);
+
+ if (folder == local_sent_folder)
+ return TRUE;
+
+ folder_uri = e_mail_folder_uri_from_folder (folder);
+
+ account_list = e_get_account_list ();
+ iterator = e_list_get_iterator (E_LIST (account_list));
+
+ while (!is_sent && e_iterator_is_valid (iterator)) {
+ EAccount *account;
+
+ /* XXX EIterator misuses const. */
+ account = (EAccount *) e_iterator_get (iterator);
+
+ if (account->sent_folder_uri != NULL)
+ is_sent = e_mail_folder_uri_equal (
+ session, folder_uri,
+ account->sent_folder_uri);
+
+ e_iterator_next (iterator);
+ }
+
+ g_object_unref (iterator);
+ g_free (folder_uri);
+
+ return is_sent;
+}
+
+/**
+ * em_utils_folder_is_outbox:
+ * @folder: a #CamelFolder
+ *
+ * Decides if @folder is an Outbox folder.
+ *
+ * Returns %TRUE if this is an Outbox folder or %FALSE otherwise.
+ **/
+gboolean
+em_utils_folder_is_outbox (CamelFolder *folder)
+{
+ CamelStore *store;
+ CamelSession *session;
+ CamelFolder *local_outbox_folder;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ store = camel_folder_get_parent_store (folder);
+ session = camel_service_get_session (CAMEL_SERVICE (store));
+
+ local_outbox_folder =
+ e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_OUTBOX);
+
+ return (folder == local_outbox_folder);
+}
+
+/* ********************************************************************** */
+
+
+/* runs sync, in main thread */
+static gpointer
+emu_addr_setup (gpointer user_data)
+{
+ GError *err = NULL;
+ ESourceList **psource_list = user_data;
+
+ if (!e_book_client_get_sources (psource_list, &err))
+ g_error_free (err);
+
+ return NULL;
+}
+
+static void
+emu_addr_cancel_stop (gpointer data)
+{
+ gboolean *stop = data;
+
+ g_return_if_fail (stop != NULL);
+
+ *stop = TRUE;
+}
+
+static void
+emu_addr_cancel_cancellable (gpointer data)
+{
+ GCancellable *cancellable = data;
+
+ g_return_if_fail (cancellable != NULL);
+
+ g_cancellable_cancel (cancellable);
+}
+
+struct TryOpenEBookStruct {
+ GError **error;
+ EFlag *flag;
+ gboolean result;
+};
+
+static void
+try_open_book_client_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer closure)
+{
+ EBookClient *book_client = E_BOOK_CLIENT (source_object);
+ struct TryOpenEBookStruct *data = (struct TryOpenEBookStruct *) closure;
+ GError *error = NULL;
+
+ if (!data)
+ return;
+
+ e_client_open_finish (E_CLIENT (book_client), result, &error);
+
+ data->result = error == NULL;
+
+ if (!data->result) {
+ g_clear_error (data->error);
+ g_propagate_error (data->error, error);
+ }
+
+ e_flag_set (data->flag);
+}
+
+/*
+ * try_open_book_client:
+ * Tries to open address book asynchronously, but acts as synchronous.
+ * The advantage is it checks periodically whether the camel_operation
+ * has been canceled or not, and if so, then stops immediately, with
+ * result FALSE. Otherwise returns same as e_client_open()
+ */
+static gboolean
+try_open_book_client (EBookClient *book_client,
+ gboolean only_if_exists,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct TryOpenEBookStruct data;
+ gboolean canceled = FALSE;
+ EFlag *flag = e_flag_new ();
+
+ data.error = error;
+ data.flag = flag;
+ data.result = FALSE;
+
+ e_client_open (
+ E_CLIENT (book_client), only_if_exists,
+ cancellable, try_open_book_client_cb, &data);
+
+ while (canceled = g_cancellable_is_cancelled (cancellable),
+ !canceled && !e_flag_is_set (flag)) {
+ GTimeVal wait;
+
+ g_get_current_time (&wait);
+ g_time_val_add (&wait, 250000); /* waits 250ms */
+
+ e_flag_timed_wait (flag, &wait);
+ }
+
+ if (canceled) {
+ g_cancellable_cancel (cancellable);
+
+ g_clear_error (error);
+ g_propagate_error (
+ error, e_client_error_create (
+ E_CLIENT_ERROR_CANCELLED, NULL));
+ }
+
+ e_flag_wait (flag);
+ e_flag_free (flag);
+
+ return data.result && (!error || !*error);
+}
+
+
+#define NOT_FOUND_BOOK (GINT_TO_POINTER (1))
+
+G_LOCK_DEFINE_STATIC (contact_cache);
+
+/* key is lowercased contact email; value is EBook pointer
+ * (just for comparison) where it comes from */
+static GHashTable *contact_cache = NULL;
+
+/* key is source ID; value is pointer to EBook */
+static GHashTable *emu_books_hash = NULL;
+
+/* key is source ID; value is same pointer as key; this is hash of
+ * broken books, which failed to open for some reason */
+static GHashTable *emu_broken_books_hash = NULL;
+
+static ESourceList *emu_books_source_list = NULL;
+
+static gboolean
+search_address_in_addressbooks (const gchar *address,
+ gboolean local_only,
+ gboolean (*check_contact) (EContact *contact,
+ gpointer user_data),
+ gpointer user_data)
+{
+ gboolean found = FALSE, stop = FALSE, found_any = FALSE;
+ gchar *lowercase_addr;
+ gpointer ptr;
+ EBookQuery *book_query;
+ gchar *query;
+ GSList *s, *g, *addr_sources = NULL;
+ GHook *hook_cancellable;
+ GCancellable *cancellable;
+
+ if (!address || !*address)
+ return FALSE;
+
+ G_LOCK (contact_cache);
+
+ if (!emu_books_source_list) {
+ mail_call_main (
+ MAIL_CALL_p_p, (MailMainFunc)
+ emu_addr_setup, &emu_books_source_list);
+ emu_books_hash = g_hash_table_new_full (
+ g_str_hash, g_str_equal, g_free, g_object_unref);
+ emu_broken_books_hash = g_hash_table_new_full (
+ g_str_hash, g_str_equal, g_free, NULL);
+ contact_cache = g_hash_table_new_full (
+ g_str_hash, g_str_equal, g_free, NULL);
+ }
+
+ if (!emu_books_source_list) {
+ G_UNLOCK (contact_cache);
+ return FALSE;
+ }
+
+ lowercase_addr = g_utf8_strdown (address, -1);
+ ptr = g_hash_table_lookup (contact_cache, lowercase_addr);
+ if (ptr != NULL && (check_contact == NULL || ptr == NOT_FOUND_BOOK)) {
+ g_free (lowercase_addr);
+ G_UNLOCK (contact_cache);
+ return ptr != NOT_FOUND_BOOK;
+ }
+
+ book_query = e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_IS, address);
+ query = e_book_query_to_string (book_query);
+ e_book_query_unref (book_query);
+
+ for (g = e_source_list_peek_groups (emu_books_source_list);
+ g; g = g_slist_next (g)) {
+ ESourceGroup *group = g->data;
+
+ if (!group)
+ continue;
+
+ if (local_only && !(e_source_group_peek_base_uri (group) &&
+ g_str_has_prefix (
+ e_source_group_peek_base_uri (group), "local:")))
+ continue;
+
+ for (s = e_source_group_peek_sources (group); s; s = g_slist_next (s)) {
+ ESource *source = s->data;
+ const gchar *completion = e_source_get_property (source, "completion");
+
+ if (completion && g_ascii_strcasecmp (completion, "true") == 0) {
+ addr_sources = g_slist_prepend (addr_sources, g_object_ref (source));
+ }
+ }
+ }
+
+ cancellable = g_cancellable_new ();
+ hook_cancellable = mail_cancel_hook_add (emu_addr_cancel_cancellable, cancellable);
+
+ for (s = addr_sources; !stop && !found && s; s = g_slist_next (s)) {
+ ESource *source = s->data;
+ GSList *contacts;
+ EBookClient *book_client = NULL;
+ GHook *hook_stop;
+ gboolean cached_book = FALSE;
+ const gchar *display_name;
+ const gchar *uid;
+ GError *err = NULL;
+
+ uid = e_source_peek_uid (source);
+ display_name = e_source_peek_name (source);
+
+ /* failed to load this book last time, skip it now */
+ if (g_hash_table_lookup (emu_broken_books_hash, uid) != NULL) {
+ d(printf ("%s: skipping broken book '%s'\n",
+ G_STRFUNC, display_name));
+ continue;
+ }
+
+ d(printf(" checking '%s'\n", e_source_get_uri(source)));
+
+ hook_stop = mail_cancel_hook_add (emu_addr_cancel_stop, &stop);
+
+ book_client = g_hash_table_lookup (emu_books_hash, uid);
+ if (!book_client) {
+ book_client = e_book_client_new (source, &err);
+
+ if (book_client == NULL) {
+ if (err && (g_error_matches (err, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
+ g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED))) {
+ stop = TRUE;
+ } else if (err) {
+ gchar *source_uid;
+
+ source_uid = g_strdup (uid);
+
+ g_hash_table_insert (
+ emu_broken_books_hash,
+ source_uid, source_uid);
+
+ g_warning (
+ "%s: Unable to create addressbook '%s': %s",
+ G_STRFUNC,
+ display_name,
+ err->message);
+ }
+ g_clear_error (&err);
+ } else if (!stop && !try_open_book_client (book_client, TRUE, cancellable, &err)) {
+ g_object_unref (book_client);
+ book_client = NULL;
+
+ if (err && (g_error_matches (err, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
+ g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED))) {
+ stop = TRUE;
+ } else if (err) {
+ gchar *source_uid;
+
+ source_uid = g_strdup (uid);
+
+ g_hash_table_insert (
+ emu_broken_books_hash,
+ source_uid, source_uid);
+
+ g_warning (
+ "%s: Unable to open addressbook '%s': %s",
+ G_STRFUNC,
+ display_name,
+ err->message);
+ }
+ g_clear_error (&err);
+ }
+ } else {
+ cached_book = TRUE;
+ }
+
+ if (book_client && !stop && e_book_client_get_contacts_sync (book_client, query, &contacts, cancellable, &err)) {
+ if (contacts != NULL) {
+ if (!found_any) {
+ g_hash_table_insert (contact_cache, g_strdup (lowercase_addr), book_client);
+ }
+ found_any = TRUE;
+
+ if (check_contact) {
+ GSList *l;
+
+ for (l = contacts; l && !found; l = l->next) {
+ EContact *contact = l->data;
+
+ found = check_contact (contact, user_data);
+ }
+ } else {
+ found = TRUE;
+ }
+
+ g_slist_foreach (contacts, (GFunc) g_object_unref, NULL);
+ g_slist_free (contacts);
+ }
+ } else if (book_client) {
+ stop = stop || (err &&
+ (g_error_matches (err, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
+ g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)));
+ if (err && !stop) {
+ gchar *source_uid = g_strdup (uid);
+
+ g_hash_table_insert (emu_broken_books_hash, source_uid, source_uid);
+
+ g_warning (
+ "%s: Can't get contacts from '%s': %s",
+ G_STRFUNC,
+ display_name,
+ err->message);
+ }
+ g_clear_error (&err);
+ }
+
+ mail_cancel_hook_remove (hook_stop);
+
+ if (stop && !cached_book && book_client) {
+ g_object_unref (book_client);
+ } else if (!stop && book_client && !cached_book) {
+ g_hash_table_insert (
+ emu_books_hash, g_strdup (uid), book_client);
+ }
+ }
+
+ mail_cancel_hook_remove (hook_cancellable);
+ g_object_unref (cancellable);
+
+ g_slist_free_full (addr_sources, (GDestroyNotify) g_object_unref);
+
+ g_free (query);
+
+ if (!found_any) {
+ g_hash_table_insert (contact_cache, lowercase_addr, NOT_FOUND_BOOK);
+ lowercase_addr = NULL;
+ }
+
+ G_UNLOCK (contact_cache);
+
+ g_free (lowercase_addr);
+
+ return found_any;
+}
+
+gboolean
+em_utils_in_addressbook (CamelInternetAddress *iaddr,
+ gboolean local_only)
+{
+ const gchar *addr;
+
+ /* TODO: check all addresses? */
+ if (iaddr == NULL || !camel_internet_address_get (iaddr, 0, NULL, &addr))
+ return FALSE;
+
+ return search_address_in_addressbooks (addr, local_only, NULL, NULL);
+}
+
+static gboolean
+extract_photo_data (EContact *contact,
+ gpointer user_data)
+{
+ EContactPhoto **photo = user_data;
+
+ g_return_val_if_fail (contact != NULL, FALSE);
+ g_return_val_if_fail (user_data != NULL, FALSE);
+
+ *photo = e_contact_get (contact, E_CONTACT_PHOTO);
+ if (!*photo)
+ *photo = e_contact_get (contact, E_CONTACT_LOGO);
+
+ return *photo != NULL;
+}
+
+typedef struct _PhotoInfo {
+ gchar *address;
+ EContactPhoto *photo;
+} PhotoInfo;
+
+static void
+emu_free_photo_info (PhotoInfo *pi)
+{
+ if (!pi)
+ return;
+
+ if (pi->address)
+ g_free (pi->address);
+ if (pi->photo)
+ e_contact_photo_free (pi->photo);
+ g_free (pi);
+}
+
+G_LOCK_DEFINE_STATIC (photos_cache);
+static GSList *photos_cache = NULL; /* list of PhotoInfo-s */
+
+CamelMimePart *
+em_utils_contact_photo (CamelInternetAddress *cia,
+ gboolean local_only)
+{
+ const gchar *addr = NULL;
+ CamelMimePart *part = NULL;
+ EContactPhoto *photo = NULL;
+ GSList *p, *first_not_null = NULL;
+ gint count_not_null = 0;
+
+ if (cia == NULL || !camel_internet_address_get (cia, 0, NULL, &addr) || !addr) {
+ return NULL;
+ }
+
+ G_LOCK (photos_cache);
+
+ /* search a cache first */
+ for (p = photos_cache; p; p = p->next) {
+ PhotoInfo *pi = p->data;
+
+ if (!pi)
+ continue;
+
+ if (pi->photo) {
+ if (!first_not_null)
+ first_not_null = p;
+ count_not_null++;
+ }
+
+ if (g_ascii_strcasecmp (addr, pi->address) == 0) {
+ photo = pi->photo;
+ break;
+ }
+ }
+
+ /* !p means the address had not been found in the cache */
+ if (!p && search_address_in_addressbooks (
+ addr, local_only, extract_photo_data, &photo)) {
+ PhotoInfo *pi;
+
+ if (photo && photo->type != E_CONTACT_PHOTO_TYPE_INLINED) {
+ e_contact_photo_free (photo);
+ photo = NULL;
+ }
+
+ /* keep only up to 10 photos in memory */
+ if (photo && count_not_null >= 10 && first_not_null) {
+ pi = first_not_null->data;
+
+ photos_cache = g_slist_remove (photos_cache, pi);
+
+ emu_free_photo_info (pi);
+ }
+
+ pi = g_new0 (PhotoInfo, 1);
+ pi->address = g_strdup (addr);
+ pi->photo = photo;
+
+ photos_cache = g_slist_append (photos_cache, pi);
+ }
+
+ /* some photo found, use it */
+ if (photo) {
+ /* Form a mime part out of the photo */
+ part = camel_mime_part_new ();
+ camel_mime_part_set_content (part,
+ (const gchar *) photo->data.inlined.data,
+ photo->data.inlined.length, "image/jpeg");
+ }
+
+ G_UNLOCK (photos_cache);
+
+ return part;
+}
+
+/* list of email addresses (strings) to remove from local cache of photos and
+ * contacts, but only if the photo doesn't exist or is an not-found contact */
+void
+emu_remove_from_mail_cache (const GSList *addresses)
+{
+ const GSList *a;
+ GSList *p;
+ CamelInternetAddress *cia;
+
+ cia = camel_internet_address_new ();
+
+ for (a = addresses; a; a = a->next) {
+ const gchar *addr = NULL;
+
+ if (!a->data)
+ continue;
+
+ if (camel_address_decode ((CamelAddress *) cia, a->data) != -1 &&
+ camel_internet_address_get (cia, 0, NULL, &addr) && addr) {
+ gchar *lowercase_addr = g_utf8_strdown (addr, -1);
+
+ G_LOCK (contact_cache);
+ if (g_hash_table_lookup (contact_cache, lowercase_addr) == NOT_FOUND_BOOK)
+ g_hash_table_remove (contact_cache, lowercase_addr);
+ G_UNLOCK (contact_cache);
+
+ g_free (lowercase_addr);
+
+ G_LOCK (photos_cache);
+ for (p = photos_cache; p; p = p->next) {
+ PhotoInfo *pi = p->data;
+
+ if (pi && !pi->photo && g_ascii_strcasecmp (pi->address, addr) == 0) {
+ photos_cache = g_slist_remove (photos_cache, pi);
+ emu_free_photo_info (pi);
+ break;
+ }
+ }
+ G_UNLOCK (photos_cache);
+ }
+ }
+
+ g_object_unref (cia);
+}
+
+
+void
+emu_remove_from_mail_cache_1 (const gchar *address)
+{
+ GSList *l;
+
+ g_return_if_fail (address != NULL);
+
+ l = g_slist_append (NULL, (gpointer) address);
+
+ emu_remove_from_mail_cache (l);
+
+ g_slist_free (l);
+}
+
+/* frees all data created by call of em_utils_in_addressbook() or
+ * em_utils_contact_photo() */
+void
+emu_free_mail_cache (void)
+{
+ G_LOCK (contact_cache);
+
+ if (emu_books_hash) {
+ g_hash_table_destroy (emu_books_hash);
+ emu_books_hash = NULL;
+ }
+
+ if (emu_broken_books_hash) {
+ g_hash_table_destroy (emu_broken_books_hash);
+ emu_broken_books_hash = NULL;
+ }
+
+ if (emu_books_source_list) {
+ g_object_unref (emu_books_source_list);
+ emu_books_source_list = NULL;
+ }
+
+ if (contact_cache) {
+ g_hash_table_destroy (contact_cache);
+ contact_cache = NULL;
+ }
+
+ G_UNLOCK (contact_cache);
+
+ G_LOCK (photos_cache);
+
+ g_slist_foreach (photos_cache, (GFunc) emu_free_photo_info, NULL);
+ g_slist_free (photos_cache);
+ photos_cache = NULL;
+
+ G_UNLOCK (photos_cache);
+}
+
+static EAccount *
+guess_account_from_folder (CamelFolder *folder)
+{
+ CamelStore *store;
+ const gchar *uid;
+
+ store = camel_folder_get_parent_store (folder);
+ uid = camel_service_get_uid (CAMEL_SERVICE (store));
+
+ return e_get_account_by_uid (uid);
+}
+
+static EAccount *
+guess_account_from_message (CamelMimeMessage *message)
+{
+ const gchar *uid;
+
+ uid = camel_mime_message_get_source (message);
+
+ return (uid != NULL) ? e_get_account_by_uid (uid) : NULL;
+}
+
+EAccount *
+em_utils_guess_account (CamelMimeMessage *message,
+ CamelFolder *folder)
+{
+ EAccount *account = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
+
+ if (folder != NULL)
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ /* check for newsgroup header */
+ if (folder != NULL
+ && camel_medium_get_header (CAMEL_MEDIUM (message), "Newsgroups"))
+ account = guess_account_from_folder (folder);
+
+ /* check for source folder */
+ if (account == NULL && folder != NULL)
+ account = guess_account_from_folder (folder);
+
+ /* then message source */
+ if (account == NULL)
+ account = guess_account_from_message (message);
+
+ return account;
+}
+
+EAccount *
+em_utils_guess_account_with_recipients (CamelMimeMessage *message,
+ CamelFolder *folder)
+{
+ EAccount *account = NULL;
+ EAccountList *account_list;
+ GHashTable *recipients;
+ EIterator *iterator;
+ CamelInternetAddress *addr;
+ const gchar *type;
+ const gchar *key;
+
+ /* This policy is subject to debate and tweaking,
+ * but please also document the rational here. */
+
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
+
+ /* Build a set of email addresses in which to test for membership.
+ * Only the keys matter here; the values just need to be non-NULL. */
+ recipients = g_hash_table_new (g_str_hash, g_str_equal);
+
+ type = CAMEL_RECIPIENT_TYPE_TO;
+ addr = camel_mime_message_get_recipients (message, type);
+ if (addr != NULL) {
+ gint index = 0;
+
+ while (camel_internet_address_get (addr, index++, NULL, &key))
+ g_hash_table_insert (
+ recipients, (gpointer) key,
+ GINT_TO_POINTER (1));
+ }
+
+ type = CAMEL_RECIPIENT_TYPE_CC;
+ addr = camel_mime_message_get_recipients (message, type);
+ if (addr != NULL) {
+ gint index = 0;
+
+ while (camel_internet_address_get (addr, index++, NULL, &key))
+ g_hash_table_insert (
+ recipients, (gpointer) key,
+ GINT_TO_POINTER (1));
+ }
+
+ /* First Preference: We were given a folder that maps to an
+ * enabled account, and that account's email address appears
+ * in the list of To: or Cc: recipients. */
+
+ if (folder != NULL)
+ account = guess_account_from_folder (folder);
+
+ if (account == NULL || !account->enabled)
+ goto second_preference;
+
+ if ((key = account->id->address) == NULL)
+ goto second_preference;
+
+ if (g_hash_table_lookup (recipients, key) != NULL)
+ goto exit;
+
+second_preference:
+
+ /* Second Preference: Choose any enabled account whose email
+ * address appears in the list to To: or Cc: recipients. */
+
+ account_list = e_get_account_list ();
+ iterator = e_list_get_iterator (E_LIST (account_list));
+
+ while (e_iterator_is_valid (iterator)) {
+ account = (EAccount *) e_iterator_get (iterator);
+ e_iterator_next (iterator);
+
+ if (account == NULL || !account->enabled)
+ continue;
+
+ if ((key = account->id->address) == NULL)
+ continue;
+
+ if (g_hash_table_lookup (recipients, key) != NULL) {
+ g_object_unref (iterator);
+ goto exit;
+ }
+ }
+ g_object_unref (iterator);
+
+ /* Last Preference: Defer to em_utils_guess_account(). */
+ account = em_utils_guess_account (message, folder);
+
+exit:
+ g_hash_table_destroy (recipients);
+
+ return account;
+}
+
+static void
+cancel_service_connect_cb (GCancellable *cancellable,
+ CamelService *service)
+{
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ camel_service_cancel_connect (service);
+}
+
+gboolean
+em_utils_connect_service_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean res;
+ gulong handler_id = 0;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+
+ if (cancellable != NULL)
+ handler_id = g_cancellable_connect (
+ cancellable,
+ G_CALLBACK (cancel_service_connect_cb),
+ service, NULL);
+
+ res = camel_service_connect_sync (service, error);
+
+ if (handler_id)
+ g_cancellable_disconnect (cancellable, handler_id);
+
+ return res;
+}
+
+gboolean
+em_utils_disconnect_service_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean res;
+ gulong handler_id = 0;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+
+ if (cancellable != NULL)
+ handler_id = g_cancellable_connect (
+ cancellable,
+ G_CALLBACK (cancel_service_connect_cb),
+ service, NULL);
+
+ res = camel_service_disconnect_sync (service, clean, error);
+
+ if (handler_id)
+ g_cancellable_disconnect (cancellable, handler_id);
+
+ return res;
+}
+
+/**
+ * em_utils_uids_free:
+ * @uids: array of uids
+ *
+ * Frees the array of uids pointed to by @uids back to the system.
+ **/
+void
+em_utils_uids_free (GPtrArray *uids)
+{
+ gint i;
+
+ for (i = 0; i < uids->len; i++)
+ g_free (uids->pdata[i]);
+
+ g_ptr_array_free (uids, TRUE);
+}
+
+/* Returns TRUE if CamelURL points to a local mbox file. */
+gboolean
+em_utils_is_local_delivery_mbox_file (CamelURL *url)
+{
+ g_return_val_if_fail (url != NULL, FALSE);
+
+ return g_str_equal (url->protocol, "mbox") &&
+ (url->path != NULL) &&
+ g_file_test (url->path, G_FILE_TEST_EXISTS) &&
+ !g_file_test (url->path, G_FILE_TEST_IS_DIR);
+}
+
diff --git a/libemail-engine/e-mail-utils.h b/libemail-engine/e-mail-utils.h
new file mode 100644
index 0000000000..144f13dfdd
--- /dev/null
+++ b/libemail-engine/e-mail-utils.h
@@ -0,0 +1,56 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Srinivasa Ragavan <sragavan@gnome.org>
+ *
+ *
+ */
+
+#ifndef E_MAIL_UTILS_H
+#define E_MAIL_UTILS_H
+
+#include <camel/camel.h>
+#include <libedataserver/e-account.h>
+
+gboolean em_utils_folder_is_drafts (CamelFolder *folder);
+gboolean em_utils_folder_is_templates (CamelFolder *folder);
+gboolean em_utils_folder_is_sent (CamelFolder *folder);
+gboolean em_utils_folder_is_outbox (CamelFolder *folder);
+gboolean em_utils_in_addressbook (CamelInternetAddress *addr,
+ gboolean local_only);
+CamelMimePart * em_utils_contact_photo (CamelInternetAddress *addr,
+ gboolean local);
+EAccount * em_utils_guess_account (CamelMimeMessage *message,
+ CamelFolder *folder);
+EAccount * em_utils_guess_account_with_recipients
+ (CamelMimeMessage *message,
+ CamelFolder *folder);
+void emu_remove_from_mail_cache (const GSList *addresses);
+void emu_remove_from_mail_cache_1 (const gchar *address);
+void emu_free_mail_cache (void);
+gboolean em_utils_connect_service_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error);
+gboolean em_utils_disconnect_service_sync
+ (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error);
+void em_utils_uids_free (GPtrArray *uids);
+gboolean em_utils_is_local_delivery_mbox_file
+ (CamelURL *url);
+
+#endif /* E_MAIL_UTILS_H */
diff --git a/libemail-engine/libemail-engine.pc.in b/libemail-engine/libemail-engine.pc.in
new file mode 100644
index 0000000000..1d915bcf33
--- /dev/null
+++ b/libemail-engine/libemail-engine.pc.in
@@ -0,0 +1,15 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+datarootdir=@datarootdir@
+datadir=@datadir@
+
+privincludedir=@privincludedir@
+
+Name: libemail-engine
+Description: Client library for evolution mail
+Version: @VERSION@
+Requires: libemail-utils
+Libs: -L${libdir} -lemail-engine
+Cflags: -I${privincludedir}
diff --git a/libemail-engine/mail-config.c b/libemail-engine/mail-config.c
new file mode 100644
index 0000000000..51d34a0714
--- /dev/null
+++ b/libemail-engine/mail-config.c
@@ -0,0 +1,294 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Radek Doulik <rodo@ximian.com>
+ * Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ * Copyright (C) 2009 Intel Corporation
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include <libedataserver/e-data-server-util.h>
+
+#include <libemail-utils/e-account-utils.h>
+#include <libemail-utils/e-signature-utils.h>
+
+#include "e-mail-folder-utils.h"
+#include "mail-config.h"
+#include "mail-tools.h"
+
+typedef struct {
+ GSList *labels;
+
+ gboolean address_compress;
+ gint address_count;
+
+ GSList *jh_header;
+ gboolean jh_check;
+ gboolean book_lookup;
+ gboolean book_lookup_local_only;
+} MailConfig;
+
+extern gint camel_header_param_encode_filenames_in_rfc_2047;
+
+static MailConfig *config = NULL;
+static GSettings *mail_settings = NULL;
+
+static void
+settings_outlook_filenames_changed (GSettings *settings,
+ const gchar *key,
+ gpointer user_data)
+{
+ /* pass option to the camel */
+ if (g_settings_get_boolean (settings, key))
+ camel_header_param_encode_filenames_in_rfc_2047 = 1;
+ else
+ camel_header_param_encode_filenames_in_rfc_2047 = 0;
+}
+
+static void
+settings_jh_headers_changed (GSettings *settings,
+ const gchar *key,
+ EMailSession *session)
+{
+ GSList *node;
+ GPtrArray *name, *value;
+ gchar **strv;
+ gint i;
+
+ g_slist_foreach (config->jh_header, (GFunc) g_free, NULL);
+ g_slist_free (config->jh_header);
+ config->jh_header = NULL;
+
+ strv = g_settings_get_strv (settings, "junk-custom-header");
+ for (i = 0; strv[i] != NULL; i++)
+ config->jh_header = g_slist_append (config->jh_header, g_strdup (strv[i]));
+ g_strfreev (strv);
+
+ node = config->jh_header;
+ name = g_ptr_array_new ();
+ value = g_ptr_array_new ();
+ while (node && node->data) {
+ gchar **tok = g_strsplit (node->data, "=", 2);
+ g_ptr_array_add (name, g_strdup (tok[0]));
+ g_ptr_array_add (value, g_strdup (tok[1]));
+ node = node->next;
+ g_strfreev (tok);
+ }
+ camel_session_set_junk_headers (
+ CAMEL_SESSION (session),
+ (const gchar **) name->pdata,
+ (const gchar **) value->pdata, name->len);
+
+ g_ptr_array_foreach (name, (GFunc) g_free, NULL);
+ g_ptr_array_foreach (value, (GFunc) g_free, NULL);
+ g_ptr_array_free (name, TRUE);
+ g_ptr_array_free (value, TRUE);
+}
+
+static void
+settings_jh_check_changed (GSettings *settings,
+ const gchar *key,
+ EMailSession *session)
+{
+ config->jh_check = g_settings_get_boolean (settings, "junk-check-custom-header");
+ if (!config->jh_check) {
+ camel_session_set_junk_headers (
+ CAMEL_SESSION (session), NULL, NULL, 0);
+ } else {
+ settings_jh_headers_changed (settings, NULL, session);
+ }
+}
+
+static void
+settings_bool_value_changed (GSettings *settings,
+ const gchar *key,
+ gboolean *save_location)
+{
+ *save_location = g_settings_get_boolean (settings, key);
+}
+
+static void
+settings_int_value_changed (GSettings *settings,
+ const gchar *key,
+ gint *save_location)
+{
+ *save_location = g_settings_get_int (settings, key);
+}
+
+void
+mail_config_write (void)
+{
+ EAccountList *account_list;
+ ESignatureList *signature_list;
+
+ if (!config)
+ return;
+
+ account_list = e_get_account_list ();
+ signature_list = e_get_signature_list ();
+
+ e_account_list_save (account_list);
+ e_signature_list_save (signature_list);
+
+ g_settings_sync ();
+}
+
+gint
+mail_config_get_address_count (void)
+{
+ if (!config->address_compress)
+ return -1;
+
+ return config->address_count;
+}
+
+/* timeout interval, in seconds, when to call server update */
+gint
+mail_config_get_sync_timeout (void)
+{
+ gint res = 60;
+
+ res = g_settings_get_int (mail_settings, "sync-interval");
+
+ /* do not allow recheck sooner than every 30 seconds */
+ if (res == 0)
+ res = 60;
+ else if (res < 30)
+ res = 30;
+
+ return res;
+}
+
+gchar *
+mail_config_folder_to_cachename (CamelFolder *folder,
+ const gchar *prefix)
+{
+ gchar *folder_uri, *basename, *filename;
+ const gchar *config_dir;
+
+ config_dir = mail_session_get_config_dir ();
+
+ basename = g_build_filename (config_dir, "folders", NULL);
+ if (!g_file_test (basename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
+ /* create the folder if does not exist */
+ g_mkdir_with_parents (basename, 0700);
+ }
+ g_free (basename);
+
+ folder_uri = e_mail_folder_uri_from_folder (folder);
+ e_filename_make_safe (folder_uri);
+ basename = g_strdup_printf ("%s%s", prefix, folder_uri);
+ filename = g_build_filename (config_dir, "folders", basename, NULL);
+ g_free (basename);
+ g_free (folder_uri);
+
+ return filename;
+}
+
+void
+mail_config_reload_junk_headers (EMailSession *session)
+{
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+
+ /* It automatically sets in the session */
+ if (config == NULL)
+ mail_config_init (session);
+ else {
+ settings_jh_check_changed (mail_settings, NULL, session);
+ }
+}
+
+gboolean
+mail_config_get_lookup_book (void)
+{
+ g_return_val_if_fail (config != NULL, FALSE);
+
+ return config->book_lookup;
+}
+
+gboolean
+mail_config_get_lookup_book_local_only (void)
+{
+ g_return_val_if_fail (config != NULL, FALSE);
+
+ return config->book_lookup_local_only;
+}
+
+/* Config struct routines */
+void
+mail_config_init (EMailSession *session)
+{
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+
+ if (config)
+ return;
+
+ config = g_new0 (MailConfig, 1);
+
+ mail_settings = g_settings_new ("org.gnome.evolution.mail");
+
+ /* Composer Configuration */
+
+ settings_outlook_filenames_changed (
+ mail_settings, "composer-outlook-filenames", NULL);
+ g_signal_connect (
+ mail_settings, "changed::composer-outlook-filenames",
+ G_CALLBACK (settings_outlook_filenames_changed), NULL);
+
+ /* Display Configuration */
+
+ g_signal_connect (
+ mail_settings, "changed::address-compress",
+ G_CALLBACK (settings_bool_value_changed), &config->address_compress);
+ config->address_compress = g_settings_get_boolean (mail_settings, "address-compress");
+
+ g_signal_connect (
+ mail_settings, "changed::address-count",
+ G_CALLBACK (settings_int_value_changed), &config->address_count);
+ config->address_count = g_settings_get_int (mail_settings, "address-count");
+
+ /* Junk Configuration */
+
+ g_signal_connect (
+ mail_settings, "changed::junk-check-custom-header",
+ G_CALLBACK (settings_jh_check_changed), session);
+ config->jh_check = g_settings_get_boolean (mail_settings, "junk-check-custom-header");
+
+ g_signal_connect (
+ mail_settings, "changed::junk-custom-header",
+ G_CALLBACK (settings_jh_headers_changed), session);
+
+ g_signal_connect (
+ mail_settings, "changed::junk-lookup-addressbook",
+ G_CALLBACK (settings_bool_value_changed), &config->book_lookup);
+ config->book_lookup = g_settings_get_boolean (mail_settings, "junk-lookup-addressbook");
+
+ g_signal_connect (
+ mail_settings, "changed::junk-lookup-addressbook-local-only",
+ G_CALLBACK (settings_bool_value_changed), &config->book_lookup_local_only);
+ config->book_lookup_local_only = g_settings_get_boolean (mail_settings, "junk-lookup-addressbook-local-only");
+
+ settings_jh_check_changed (mail_settings, NULL, session);
+}
diff --git a/libemail-engine/mail-config.h b/libemail-engine/mail-config.h
new file mode 100644
index 0000000000..0a1c618f35
--- /dev/null
+++ b/libemail-engine/mail-config.h
@@ -0,0 +1,49 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef MAIL_CONFIG_H
+#define MAIL_CONFIG_H
+
+#include <libemail-engine/e-mail-session.h>
+
+G_BEGIN_DECLS
+
+/* Configuration */
+void mail_config_init (EMailSession *session);
+void mail_config_write (void);
+
+/* General Accessor functions */
+
+gint mail_config_get_address_count (void);
+
+/* static utility functions */
+gchar * mail_config_folder_to_cachename (CamelFolder *folder,
+ const gchar *prefix);
+gint mail_config_get_sync_timeout (void);
+
+void mail_config_reload_junk_headers (EMailSession *session);
+gboolean mail_config_get_lookup_book (void);
+gboolean mail_config_get_lookup_book_local_only (void);
+
+G_END_DECLS
+
+#endif /* MAIL_CONFIG_H */
diff --git a/libemail-engine/mail-folder-cache.c b/libemail-engine/mail-folder-cache.c
new file mode 100644
index 0000000000..954c14d379
--- /dev/null
+++ b/libemail-engine/mail-folder-cache.c
@@ -0,0 +1,1841 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Peter Williams <peterw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ * Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ * Copyright (C) 2009 Intel Corporation
+ *
+ */
+
+/**
+ * SECTION: mail-folder-cache
+ * @short_description: Stores information about open folders
+ **/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <time.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <libedataserver/e-data-server-util.h>
+
+#include <libemail-utils/mail-mt.h>
+
+#include "mail-folder-cache.h"
+#include "mail-ops.h"
+#include "mail-tools.h"
+#include "e-mail-utils.h"
+#include "e-mail-folder-utils.h"
+#include "e-mail-session.h"
+#include "e-mail-store-utils.h"
+#include "mail-config.h"
+
+#define w(x)
+#define d(x)
+
+#define MAIL_FOLDER_CACHE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), MAIL_TYPE_FOLDER_CACHE, MailFolderCachePrivate))
+
+/* This code is a mess, there is no reason it should be so complicated. */
+
+typedef struct _StoreInfo StoreInfo;
+
+struct _MailFolderCachePrivate {
+ gpointer session; /* weak pointer */
+
+ /* source id for the ping timeout callback */
+ guint ping_id;
+ /* Store to storeinfo table, active stores */
+ GHashTable *stores;
+ /* mutex to protect access to the stores hash */
+ GMutex *stores_mutex;
+ /* List of folder changes to be executed in gui thread */
+ GQueue updates;
+ /* idle source id for flushing all pending updates */
+ guint update_id;
+ /* hack for people who LIKE to have unsent count */
+ gint count_sent;
+ gint count_trash;
+
+ GQueue local_folder_uris;
+ GQueue remote_folder_uris;
+};
+
+enum {
+ PROP_0,
+ PROP_SESSION
+};
+
+enum {
+ FOLDER_AVAILABLE,
+ FOLDER_UNAVAILABLE,
+ FOLDER_DELETED,
+ FOLDER_RENAMED,
+ FOLDER_UNREAD_UPDATED,
+ FOLDER_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+struct _folder_info {
+ StoreInfo *store_info; /* 'parent' link */
+
+ gchar *full_name; /* full name of folder/folderinfo */
+
+ guint32 flags;
+ gboolean has_children;
+
+ gpointer folder; /* if known (weak pointer) */
+};
+
+/* pending list of updates */
+struct _folder_update {
+ guint remove:1; /* removing from vfolders */
+ guint delete:1; /* deleting as well? */
+ guint add:1; /* add to vfolder */
+ guint unsub:1; /* unsubcribing? */
+ guint new; /* new mail arrived? */
+
+ gchar *full_name;
+ gchar *oldfull;
+
+ gint unread;
+ CamelStore *store;
+
+ /* for only one new message... */
+ gchar *msg_uid; /* ... its uid ... */
+ gchar *msg_sender; /* ... its sender ... */
+ gchar *msg_subject; /* ... and its subject. */
+};
+
+struct _StoreInfo {
+ GHashTable *folders; /* by full_name */
+ CamelStore *store; /* the store for these folders */
+ gboolean first_update; /* TRUE initially, then FALSE forever */
+
+ /* Hold a reference to keep them alive. */
+ CamelFolder *vjunk;
+ CamelFolder *vtrash;
+
+ /* Outstanding folderinfo requests */
+ GQueue folderinfo_updates;
+};
+
+struct _update_data {
+ NoteDoneFunc done;
+ gpointer data;
+ MailFolderCache *cache;
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (MailFolderCache, mail_folder_cache, G_TYPE_OBJECT)
+
+static void
+free_update (struct _folder_update *up)
+{
+ g_free (up->full_name);
+ if (up->store)
+ g_object_unref (up->store);
+ g_free (up->oldfull);
+ g_free (up->msg_uid);
+ g_free (up->msg_sender);
+ g_free (up->msg_subject);
+ g_free (up);
+}
+
+static void
+free_folder_info (struct _folder_info *mfi)
+{
+ g_free (mfi->full_name);
+ g_free (mfi);
+}
+
+static StoreInfo *
+store_info_new (CamelStore *store)
+{
+ StoreInfo *info;
+ GHashTable *folders;
+
+ folders = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) free_folder_info);
+
+ info = g_slice_new0 (StoreInfo);
+ info->folders = folders;
+ info->store = g_object_ref (store);
+ info->first_update = TRUE;
+
+ /* If these are vfolders then they need to be opened
+ * now, otherwise they won't keep track of all folders. */
+ if (store->flags & CAMEL_STORE_VJUNK)
+ info->vjunk = camel_store_get_junk_folder_sync (
+ store, NULL, NULL);
+ if (store->flags & CAMEL_STORE_VTRASH)
+ info->vtrash = camel_store_get_trash_folder_sync (
+ store, NULL, NULL);
+
+ g_queue_init (&info->folderinfo_updates);
+
+ return info;
+}
+
+static void
+store_info_free (StoreInfo *info)
+{
+ struct _update_data *ud;
+
+ while (!g_queue_is_empty (&info->folderinfo_updates)) {
+ ud = g_queue_pop_head (&info->folderinfo_updates);
+ g_cancellable_cancel (ud->cancellable);
+ }
+
+ g_hash_table_destroy (info->folders);
+ g_object_unref (info->store);
+
+ if (info->vjunk != NULL)
+ g_object_unref (info->vjunk);
+
+ if (info->vtrash != NULL)
+ g_object_unref (info->vtrash);
+
+ g_slice_free (StoreInfo, info);
+}
+
+static gboolean
+flush_updates_idle_cb (MailFolderCache *cache)
+{
+ struct _folder_update *up;
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ while ((up = g_queue_pop_head (&cache->priv->updates)) != NULL) {
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ if (up->remove) {
+ if (up->delete) {
+ g_signal_emit (
+ cache, signals[FOLDER_DELETED], 0,
+ up->store, up->full_name);
+ } else
+ g_signal_emit (
+ cache, signals[FOLDER_UNAVAILABLE], 0,
+ up->store, up->full_name);
+ } else {
+ if (up->oldfull && up->add) {
+ g_signal_emit (
+ cache, signals[FOLDER_RENAMED], 0,
+ up->store, up->oldfull, up->full_name);
+ }
+
+ if (!up->oldfull && up->add)
+ g_signal_emit (
+ cache, signals[FOLDER_AVAILABLE], 0,
+ up->store, up->full_name);
+ }
+
+ /* update unread counts */
+ g_signal_emit (cache, signals[FOLDER_UNREAD_UPDATED], 0,
+ up->store, up->full_name, up->unread);
+
+ /* indicate that the folder has changed (new mail received, etc) */
+ if (up->store != NULL && up->full_name != NULL) {
+ g_signal_emit (
+ cache, signals[FOLDER_CHANGED], 0, up->store,
+ up->full_name, up->new, up->msg_uid,
+ up->msg_sender, up->msg_subject);
+ }
+
+ if (CAMEL_IS_VEE_STORE (up->store) && !up->remove) {
+ /* Normally the vfolder store takes care of the
+ * folder_opened event itself, but we add folder to
+ * the noting system later, thus we do not know about
+ * search folders to update them in a tree, thus
+ * ensure their changes will be tracked correctly. */
+ CamelFolder *folder;
+
+ /* FIXME camel_store_get_folder_sync() may block. */
+ folder = camel_store_get_folder_sync (
+ up->store, up->full_name, 0, NULL, NULL);
+
+ if (folder) {
+ mail_folder_cache_note_folder (cache, folder);
+ g_object_unref (folder);
+ }
+ }
+
+ free_update (up);
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ }
+ cache->priv->update_id = 0;
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ return FALSE;
+}
+
+static void
+flush_updates (MailFolderCache *cache)
+{
+ if (cache->priv->update_id > 0)
+ return;
+
+ if (g_queue_is_empty (&cache->priv->updates))
+ return;
+
+ cache->priv->update_id = g_idle_add (
+ (GSourceFunc) flush_updates_idle_cb, cache);
+}
+
+/* This is how unread counts work (and don't work):
+ *
+ * camel_folder_unread_message_count() only gives a correct answer if
+ * the store is paying attention to the folder. (Some stores always
+ * pay attention to all folders, but IMAP can only pay attention to
+ * one folder at a time.) But it doesn't have any way to know when
+ * it's lying, so it's only safe to call it when you know for sure
+ * that the store is paying attention to the folder, such as when it's
+ * just been created, or you get a folder_changed signal on it.
+ *
+ * camel_store_get_folder_info() always gives correct answers for the
+ * folders it checks, but it can also return -1 for a folder, meaning
+ * it didn't check, and so you should stick with your previous answer.
+ *
+ * update_1folder is called from three places: with info != NULL when
+ * the folder is created (or get_folder_info), with info == NULL when
+ * a folder changed event is emitted.
+ *
+ * So if info is NULL, camel_folder_unread_message_count is correct,
+ * and if it's not NULL and its unread_message_count isn't -1, then
+ * it's correct. */
+
+static void
+update_1folder (MailFolderCache *cache,
+ struct _folder_info *mfi,
+ gint new,
+ const gchar *msg_uid,
+ const gchar *msg_sender,
+ const gchar *msg_subject,
+ CamelFolderInfo *info)
+{
+ struct _folder_update *up;
+ CamelFolder *folder;
+ gint unread = -1;
+ gint deleted;
+
+ folder = mfi->folder;
+ if (folder) {
+ gboolean folder_is_sent;
+ gboolean folder_is_drafts;
+ gboolean folder_is_outbox;
+ gboolean folder_is_vtrash;
+ gboolean special_case;
+
+ folder_is_sent = em_utils_folder_is_sent (folder);
+ folder_is_drafts = em_utils_folder_is_drafts (folder);
+ folder_is_outbox = em_utils_folder_is_outbox (folder);
+ folder_is_vtrash = CAMEL_IS_VTRASH_FOLDER (folder);
+
+ special_case =
+ (cache->priv->count_trash && folder_is_vtrash) ||
+ (cache->priv->count_sent && folder_is_sent) ||
+ folder_is_drafts || folder_is_outbox;
+
+ if (special_case) {
+ d(printf(" total count\n"));
+ unread = camel_folder_get_message_count (folder);
+ if (folder_is_drafts || folder_is_outbox) {
+ guint32 junked = 0;
+
+ if ((deleted = camel_folder_get_deleted_message_count (folder)) > 0)
+ unread -= deleted;
+
+ junked = camel_folder_summary_get_junk_count (folder->summary);
+ if (junked > 0)
+ unread -= junked;
+ }
+ } else {
+ d(printf(" unread count\n"));
+ if (info)
+ unread = info->unread;
+ else
+ unread = camel_folder_get_unread_message_count (folder);
+ }
+ }
+
+ d(printf("folder updated: unread %d: '%s'\n", unread, mfi->full_name));
+
+ if (unread == -1)
+ return;
+
+ up = g_malloc0 (sizeof (*up));
+ up->full_name = g_strdup (mfi->full_name);
+ up->unread = unread;
+ up->new = new;
+ up->store = g_object_ref (mfi->store_info->store);
+ up->msg_uid = g_strdup (msg_uid);
+ up->msg_sender = g_strdup (msg_sender);
+ up->msg_subject = g_strdup (msg_subject);
+ g_queue_push_tail (&cache->priv->updates, up);
+ flush_updates (cache);
+}
+
+static void
+folder_changed_cb (CamelFolder *folder,
+ CamelFolderChangeInfo *changes,
+ MailFolderCache *cache)
+{
+ static GHashTable *last_newmail_per_folder = NULL;
+ time_t latest_received, new_latest_received;
+ CamelFolder *local_drafts;
+ CamelFolder *local_outbox;
+ CamelFolder *local_sent;
+ CamelSession *session;
+ CamelStore *parent_store;
+ CamelMessageInfo *info;
+ StoreInfo *si;
+ struct _folder_info *mfi;
+ const gchar *full_name;
+ gint new = 0;
+ gint i;
+ guint32 flags;
+ gchar *uid = NULL, *sender = NULL, *subject = NULL;
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+ session = camel_service_get_session (CAMEL_SERVICE (parent_store));
+
+ if (!last_newmail_per_folder)
+ last_newmail_per_folder = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ /* it's fine to hash them by folder pointer here */
+ latest_received = GPOINTER_TO_INT (
+ g_hash_table_lookup (last_newmail_per_folder, folder));
+ new_latest_received = latest_received;
+
+ local_drafts = e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_DRAFTS);
+ local_outbox = e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_OUTBOX);
+ local_sent = e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_SENT);
+
+ if (!CAMEL_IS_VEE_FOLDER (folder)
+ && folder != local_drafts
+ && folder != local_outbox
+ && folder != local_sent
+ && changes && (changes->uid_added->len > 0)) {
+ /* for each added message, check to see that it is
+ * brand new, not junk and not already deleted */
+ for (i = 0; i < changes->uid_added->len; i++) {
+ info = camel_folder_get_message_info (
+ folder, changes->uid_added->pdata[i]);
+ if (info) {
+ flags = camel_message_info_flags (info);
+ if (((flags & CAMEL_MESSAGE_SEEN) == 0) &&
+ ((flags & CAMEL_MESSAGE_JUNK) == 0) &&
+ ((flags & CAMEL_MESSAGE_DELETED) == 0) &&
+ (camel_message_info_date_received (info) > latest_received)) {
+ if (camel_message_info_date_received (info) > new_latest_received)
+ new_latest_received = camel_message_info_date_received (info);
+ new++;
+ if (new == 1) {
+ uid = g_strdup (camel_message_info_uid (info));
+ sender = g_strdup (camel_message_info_from (info));
+ subject = g_strdup (camel_message_info_subject (info));
+ } else {
+ g_free (uid);
+ g_free (sender);
+ g_free (subject);
+
+ uid = NULL;
+ sender = NULL;
+ subject = NULL;
+ }
+ }
+ camel_folder_free_message_info (folder, info);
+ }
+ }
+ }
+
+ if (new > 0)
+ g_hash_table_insert (
+ last_newmail_per_folder, folder,
+ GINT_TO_POINTER (new_latest_received));
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ if (cache->priv->stores != NULL
+ && (si = g_hash_table_lookup (cache->priv->stores, parent_store)) != NULL
+ && (mfi = g_hash_table_lookup (si->folders, full_name)) != NULL
+ && mfi->folder == folder) {
+ update_1folder (cache, mfi, new, uid, sender, subject, NULL);
+ }
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ g_free (uid);
+ g_free (sender);
+ g_free (subject);
+}
+
+static void
+unset_folder_info (MailFolderCache *cache,
+ struct _folder_info *mfi,
+ gint delete,
+ gint unsub)
+{
+ struct _folder_update *up;
+
+ d(printf("unset folderinfo '%s'\n", mfi->uri));
+
+ if (mfi->folder) {
+ CamelFolder *folder = mfi->folder;
+
+ g_signal_handlers_disconnect_by_func (
+ folder, folder_changed_cb, cache);
+
+ g_object_remove_weak_pointer (
+ G_OBJECT (mfi->folder), &mfi->folder);
+ }
+
+ if ((mfi->flags & CAMEL_FOLDER_NOSELECT) == 0) {
+ up = g_malloc0 (sizeof (*up));
+
+ up->remove = TRUE;
+ up->delete = delete;
+ up->unsub = unsub;
+ up->store = g_object_ref (mfi->store_info->store);
+ up->full_name = g_strdup (mfi->full_name);
+
+ g_queue_push_tail (&cache->priv->updates, up);
+ flush_updates (cache);
+ }
+}
+
+static void
+setup_folder (MailFolderCache *cache,
+ CamelFolderInfo *fi,
+ StoreInfo *si)
+{
+ struct _folder_info *mfi;
+ struct _folder_update *up;
+
+ mfi = g_hash_table_lookup (si->folders, fi->full_name);
+ if (mfi) {
+ update_1folder (cache, mfi, 0, NULL, NULL, NULL, fi);
+ } else {
+ mfi = g_malloc0 (sizeof (*mfi));
+ mfi->full_name = g_strdup (fi->full_name);
+ mfi->store_info = si;
+ mfi->flags = fi->flags;
+ mfi->has_children = fi->child != NULL;
+
+ g_hash_table_insert (si->folders, mfi->full_name, mfi);
+
+ up = g_malloc0 (sizeof (*up));
+ up->full_name = g_strdup (mfi->full_name);
+ up->unread = fi->unread;
+ up->store = g_object_ref (si->store);
+
+ if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0)
+ up->add = TRUE;
+
+ g_queue_push_tail (&cache->priv->updates, up);
+ flush_updates (cache);
+ }
+}
+
+static void
+create_folders (MailFolderCache *cache,
+ CamelFolderInfo *fi,
+ StoreInfo *si)
+{
+ while (fi) {
+ setup_folder (cache, fi, si);
+
+ if (fi->child)
+ create_folders (cache, fi->child, si);
+
+ fi = fi->next;
+ }
+}
+
+static void
+store_folder_subscribed_cb (CamelStore *store,
+ CamelFolderInfo *info,
+ MailFolderCache *cache)
+{
+ StoreInfo *si;
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ si = g_hash_table_lookup (cache->priv->stores, store);
+ if (si)
+ setup_folder (cache, info, si);
+ g_mutex_unlock (cache->priv->stores_mutex);
+}
+
+static void
+store_folder_created_cb (CamelStore *store,
+ CamelFolderInfo *info,
+ MailFolderCache *cache)
+{
+ /* We only want created events to do more work
+ * if we dont support subscriptions. */
+ if (!CAMEL_IS_SUBSCRIBABLE (store))
+ store_folder_subscribed_cb (store, info, cache);
+}
+
+static void
+store_folder_opened_cb (CamelStore *store,
+ CamelFolder *folder,
+ MailFolderCache *cache)
+{
+ mail_folder_cache_note_folder (cache, folder);
+}
+
+static void
+store_folder_unsubscribed_cb (CamelStore *store,
+ CamelFolderInfo *info,
+ MailFolderCache *cache)
+{
+ StoreInfo *si;
+ struct _folder_info *mfi;
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ si = g_hash_table_lookup (cache->priv->stores, store);
+ if (si) {
+ mfi = g_hash_table_lookup (si->folders, info->full_name);
+ if (mfi) {
+ unset_folder_info (cache, mfi, TRUE, TRUE);
+ g_hash_table_remove (si->folders, mfi->full_name);
+ }
+ }
+ g_mutex_unlock (cache->priv->stores_mutex);
+}
+
+static void
+store_folder_deleted_cb (CamelStore *store,
+ CamelFolderInfo *info,
+ MailFolderCache *cache)
+{
+ /* We only want deleted events to do more work
+ * if we dont support subscriptions. */
+ if (!CAMEL_IS_SUBSCRIBABLE (store))
+ store_folder_unsubscribed_cb (store, info, cache);
+}
+
+static void
+rename_folders (MailFolderCache *cache,
+ StoreInfo *si,
+ const gchar *oldbase,
+ const gchar *newbase,
+ CamelFolderInfo *fi)
+{
+ gchar *old, *olduri, *oldfile, *newuri, *newfile;
+ struct _folder_info *mfi;
+ struct _folder_update *up;
+ const gchar *config_dir;
+
+ up = g_malloc0 (sizeof (*up));
+
+ d(printf("oldbase '%s' newbase '%s' new '%s'\n", oldbase, newbase, fi->full_name));
+
+ /* Form what was the old name, and try and look it up */
+ old = g_strdup_printf("%s%s", oldbase, fi->full_name + strlen(newbase));
+ mfi = g_hash_table_lookup (si->folders, old);
+ if (mfi) {
+ up->oldfull = mfi->full_name;
+
+ /* Be careful not to invoke the destroy function. */
+ g_hash_table_steal (si->folders, mfi->full_name);
+
+ /* Its a rename op */
+ mfi->full_name = g_strdup (fi->full_name);
+ mfi->flags = fi->flags;
+ mfi->has_children = fi->child != NULL;
+
+ g_hash_table_insert (si->folders, mfi->full_name, mfi);
+ } else {
+ /* Its a new op */
+ mfi = g_malloc0 (sizeof (*mfi));
+ mfi->full_name = g_strdup (fi->full_name);
+ mfi->store_info = si;
+ mfi->flags = fi->flags;
+ mfi->has_children = fi->child != NULL;
+
+ g_hash_table_insert (si->folders, mfi->full_name, mfi);
+ }
+
+ up->full_name = g_strdup (mfi->full_name);
+ up->unread = fi->unread==-1 ? 0 : fi->unread;
+ up->store = g_object_ref (si->store);
+
+ if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0)
+ up->add = TRUE;
+
+ g_queue_push_tail (&cache->priv->updates, up);
+ flush_updates (cache);
+#if 0
+ if (fi->sibling)
+ rename_folders (cache, si, oldbase, newbase, fi->sibling, folders);
+ if (fi->child)
+ rename_folders (cache, si, oldbase, newbase, fi->child, folders);
+#endif
+
+ /* rename the meta-data we maintain ourselves */
+ config_dir = mail_session_get_config_dir ();
+ olduri = e_mail_folder_uri_build (si->store, old);
+ e_filename_make_safe (olduri);
+ newuri = e_mail_folder_uri_build (si->store, fi->full_name);
+ e_filename_make_safe (newuri);
+ oldfile = g_strdup_printf("%s/custom_view-%s.xml", config_dir, olduri);
+ newfile = g_strdup_printf("%s/custom_view-%s.xml", config_dir, newuri);
+ g_rename (oldfile, newfile);
+ g_free (oldfile);
+ g_free (newfile);
+ oldfile = g_strdup_printf("%s/current_view-%s.xml", config_dir, olduri);
+ newfile = g_strdup_printf("%s/current_view-%s.xml", config_dir, newuri);
+ g_rename (oldfile, newfile);
+ g_free (oldfile);
+ g_free (newfile);
+ g_free (olduri);
+ g_free (newuri);
+
+ g_free (old);
+}
+
+static void
+get_folders (CamelFolderInfo *fi,
+ GPtrArray *folders)
+{
+ while (fi) {
+ g_ptr_array_add (folders, fi);
+
+ if (fi->child)
+ get_folders (fi->child, folders);
+
+ fi = fi->next;
+ }
+}
+
+static gint
+folder_cmp (gconstpointer ap,
+ gconstpointer bp)
+{
+ const CamelFolderInfo *a = ((CamelFolderInfo **) ap)[0];
+ const CamelFolderInfo *b = ((CamelFolderInfo **) bp)[0];
+
+ return strcmp (a->full_name, b->full_name);
+}
+
+static void
+store_folder_renamed_cb (CamelStore *store,
+ const gchar *old_name,
+ CamelFolderInfo *info,
+ MailFolderCache *cache)
+{
+ StoreInfo *si;
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ si = g_hash_table_lookup (cache->priv->stores, store);
+ if (si) {
+ GPtrArray *folders = g_ptr_array_new ();
+ CamelFolderInfo *top;
+ gint i;
+
+ /* Ok, so for some reason the folderinfo we have comes in all messed up from
+ * imap, should find out why ... this makes it workable */
+ get_folders (info, folders);
+ qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), folder_cmp);
+
+ top = folders->pdata[0];
+ for (i = 0; i < folders->len; i++) {
+ rename_folders (cache, si, old_name, top->full_name, folders->pdata[i]);
+ }
+
+ g_ptr_array_free (folders, TRUE);
+
+ }
+ g_mutex_unlock (cache->priv->stores_mutex);
+}
+
+static void
+unset_folder_info_hash (gchar *path,
+ struct _folder_info *mfi,
+ gpointer data)
+{
+ MailFolderCache *cache = (MailFolderCache *) data;
+ unset_folder_info (cache, mfi, FALSE, FALSE);
+}
+
+static void
+mail_folder_cache_first_update (MailFolderCache *cache,
+ StoreInfo *info)
+{
+ EMailSession *session;
+ const gchar *uid;
+
+ session = mail_folder_cache_get_session (cache);
+ uid = camel_service_get_uid (CAMEL_SERVICE (info->store));
+
+ if (info->vjunk != NULL)
+ mail_folder_cache_note_folder (cache, info->vjunk);
+
+ if (info->vtrash != NULL)
+ mail_folder_cache_note_folder (cache, info->vtrash);
+
+ /* Some extra work for the "On This Computer" store. */
+ if (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0) {
+ CamelFolder *folder;
+ gint ii;
+
+ for (ii = 0; ii < E_MAIL_NUM_LOCAL_FOLDERS; ii++) {
+ folder = e_mail_session_get_local_folder (session, ii);
+ mail_folder_cache_note_folder (cache, folder);
+ }
+ }
+}
+
+static void
+update_folders (CamelStore *store,
+ GAsyncResult *result,
+ struct _update_data *ud)
+{
+ CamelFolderInfo *fi;
+ StoreInfo *si;
+ GError *error = NULL;
+
+ fi = camel_store_get_folder_info_finish (store, result, &error);
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_mutex_lock (ud->cache->priv->stores_mutex);
+ si = g_hash_table_lookup (ud->cache->priv->stores, store);
+ if (si && !g_cancellable_is_cancelled (ud->cancellable)) {
+ /* The 'si' is still there, so we can remove ourselves from
+ * its list. Or else its not, and we're on our own and free
+ * anyway. */
+ g_queue_remove (&si->folderinfo_updates, ud);
+
+ if (fi != NULL)
+ create_folders (ud->cache, fi, si);
+ }
+ g_mutex_unlock (ud->cache->priv->stores_mutex);
+
+ /* Do some extra work for the first update. */
+ if (si != NULL && si->first_update) {
+ mail_folder_cache_first_update (ud->cache, si);
+ si->first_update = FALSE;
+ }
+
+ if (fi != NULL) {
+ gboolean free_fi = TRUE;
+
+ if (ud->done != NULL)
+ free_fi = ud->done (ud->cache, store, fi, ud->data);
+ if (free_fi)
+ camel_store_free_folder_info (store, fi);
+ }
+
+ if (ud->cancellable != NULL)
+ g_object_unref (ud->cancellable);
+
+ g_free (ud);
+}
+
+struct _ping_store_msg {
+ MailMsg base;
+ CamelStore *store;
+};
+
+static gchar *
+ping_store_desc (struct _ping_store_msg *m)
+{
+ gchar *service_name;
+ gchar *msg;
+
+ service_name = camel_service_get_name (CAMEL_SERVICE (m->store), TRUE);
+ msg = g_strdup_printf (_("Pinging %s"), service_name);
+ g_free (service_name);
+
+ return msg;
+}
+
+static void
+ping_store_exec (struct _ping_store_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceConnectionStatus status;
+ CamelService *service;
+ gboolean online = FALSE;
+
+ service = CAMEL_SERVICE (m->store);
+ status = camel_service_get_connection_status (service);
+
+ if (status == CAMEL_SERVICE_CONNECTED) {
+ if (CAMEL_IS_DISCO_STORE (m->store) &&
+ camel_disco_store_status (
+ CAMEL_DISCO_STORE (m->store)) !=CAMEL_DISCO_STORE_OFFLINE)
+ online = TRUE;
+ else if (CAMEL_IS_OFFLINE_STORE (m->store) &&
+ camel_offline_store_get_online (
+ CAMEL_OFFLINE_STORE (m->store)))
+ online = TRUE;
+ }
+ if (online)
+ camel_store_noop_sync (m->store, cancellable, error);
+}
+
+static void
+ping_store_free (struct _ping_store_msg *m)
+{
+ g_object_unref (m->store);
+}
+
+static MailMsgInfo ping_store_info = {
+ sizeof (struct _ping_store_msg),
+ (MailMsgDescFunc) ping_store_desc,
+ (MailMsgExecFunc) ping_store_exec,
+ (MailMsgDoneFunc) NULL,
+ (MailMsgFreeFunc) ping_store_free
+};
+
+static void
+ping_store (CamelStore *store)
+{
+ CamelServiceConnectionStatus status;
+ CamelService *service;
+ struct _ping_store_msg *m;
+
+ service = CAMEL_SERVICE (store);
+ status = camel_service_get_connection_status (service);
+
+ if (status != CAMEL_SERVICE_CONNECTED)
+ return;
+
+ m = mail_msg_new (&ping_store_info);
+ m->store = g_object_ref (store);
+
+ mail_msg_slow_ordered_push (m);
+}
+
+static gboolean
+ping_cb (MailFolderCache *cache)
+{
+ g_mutex_lock (cache->priv->stores_mutex);
+
+ g_hash_table_foreach (cache->priv->stores, (GHFunc) ping_store, NULL);
+
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ return TRUE;
+}
+
+static gboolean
+store_has_folder_hierarchy (CamelStore *store)
+{
+ CamelProvider *provider;
+
+ g_return_val_if_fail (store != NULL, FALSE);
+
+ provider = camel_service_get_provider (CAMEL_SERVICE (store));
+ g_return_val_if_fail (provider != NULL, FALSE);
+
+ return (provider->flags & (CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_IS_EXTERNAL)) != 0;
+}
+
+static void
+store_go_online_cb (CamelStore *store,
+ GAsyncResult *result,
+ struct _update_data *ud)
+{
+ /* FIXME Not checking result for error. */
+
+ g_mutex_lock (ud->cache->priv->stores_mutex);
+
+ if (g_hash_table_lookup (ud->cache->priv->stores, store) != NULL &&
+ !g_cancellable_is_cancelled (ud->cancellable)) {
+ /* We're already in the store update list. */
+ if (store_has_folder_hierarchy (store))
+ camel_store_get_folder_info (
+ store, NULL,
+ CAMEL_STORE_FOLDER_INFO_FAST |
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE |
+ CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
+ G_PRIORITY_DEFAULT, ud->cancellable,
+ (GAsyncReadyCallback) update_folders, ud);
+ } else {
+ /* The store vanished, that means we were probably cancelled,
+ * or at any rate, need to clean ourselves up. */
+ if (ud->cancellable != NULL)
+ g_object_unref (ud->cancellable);
+ g_free (ud);
+ }
+
+ g_mutex_unlock (ud->cache->priv->stores_mutex);
+}
+
+static GList *
+find_folder_uri (GQueue *queue,
+ CamelSession *session,
+ const gchar *folder_uri)
+{
+ GList *head, *link;
+
+ head = g_queue_peek_head_link (queue);
+
+ for (link = head; link != NULL; link = g_list_next (link))
+ if (e_mail_folder_uri_equal (session, link->data, folder_uri))
+ break;
+
+ return link;
+}
+
+struct _find_info {
+ const gchar *folder_uri;
+ struct _folder_info *fi;
+};
+
+static void
+storeinfo_find_folder_info (CamelStore *store,
+ StoreInfo *si,
+ struct _find_info *fi)
+{
+ gchar *folder_name;
+ gboolean success;
+
+ if (fi->fi != NULL)
+ return;
+
+ success = e_mail_folder_uri_parse (
+ camel_service_get_session (CAMEL_SERVICE (store)),
+ fi->folder_uri, NULL, &folder_name, NULL);
+
+ if (success) {
+ fi->fi = g_hash_table_lookup (si->folders, folder_name);
+ g_free (folder_name);
+ }
+}
+
+static void
+mail_folder_cache_set_session (MailFolderCache *cache,
+ EMailSession *session)
+{
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+ g_return_if_fail (cache->priv->session == NULL);
+
+ cache->priv->session = session;
+
+ g_object_add_weak_pointer (
+ G_OBJECT (cache->priv->session),
+ &cache->priv->session);
+}
+
+static void
+mail_folder_cache_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SESSION:
+ mail_folder_cache_set_session (
+ MAIL_FOLDER_CACHE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_folder_cache_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SESSION:
+ g_value_set_object (
+ value,
+ mail_folder_cache_get_session (
+ MAIL_FOLDER_CACHE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_folder_cache_dispose (GObject *object)
+{
+ MailFolderCachePrivate *priv;
+
+ priv = MAIL_FOLDER_CACHE_GET_PRIVATE (object);
+
+ if (priv->session != NULL) {
+ g_object_remove_weak_pointer (
+ G_OBJECT (priv->session), &priv->session);
+ priv->session = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (mail_folder_cache_parent_class)->dispose (object);
+}
+
+static void
+mail_folder_cache_finalize (GObject *object)
+{
+ MailFolderCachePrivate *priv;
+
+ priv = MAIL_FOLDER_CACHE_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->stores);
+ g_mutex_free (priv->stores_mutex);
+
+ if (priv->ping_id > 0) {
+ g_source_remove (priv->ping_id);
+ priv->ping_id = 0;
+ }
+
+ if (priv->update_id > 0) {
+ g_source_remove (priv->update_id);
+ priv->update_id = 0;
+ }
+
+ while (!g_queue_is_empty (&priv->local_folder_uris))
+ g_free (g_queue_pop_head (&priv->local_folder_uris));
+
+ while (!g_queue_is_empty (&priv->remote_folder_uris))
+ g_free (g_queue_pop_head (&priv->remote_folder_uris));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (mail_folder_cache_parent_class)->finalize (object);
+}
+
+static void
+mail_folder_cache_folder_available (MailFolderCache *cache,
+ CamelStore *store,
+ const gchar *folder_name)
+{
+ CamelService *service;
+ CamelSession *session;
+ CamelProvider *provider;
+ GQueue *queue;
+ gchar *folder_uri;
+
+ /* Disregard virtual stores. */
+ if (CAMEL_IS_VEE_STORE (store))
+ return;
+
+ /* Disregard virtual Junk folders. */
+ if (store->flags & CAMEL_STORE_VJUNK)
+ if (g_strcmp0 (folder_name, CAMEL_VJUNK_NAME) == 0)
+ return;
+
+ /* Disregard virtual Trash folders. */
+ if (store->flags & CAMEL_STORE_VTRASH)
+ if (g_strcmp0 (folder_name, CAMEL_VTRASH_NAME) == 0)
+ return;
+
+ service = CAMEL_SERVICE (store);
+ session = camel_service_get_session (service);
+ provider = camel_service_get_provider (service);
+
+ /* Reuse the stores mutex just because it's handy. */
+ g_mutex_lock (cache->priv->stores_mutex);
+
+ folder_uri = e_mail_folder_uri_build (store, folder_name);
+
+ if (provider->flags & CAMEL_PROVIDER_IS_REMOTE)
+ queue = &cache->priv->remote_folder_uris;
+ else
+ queue = &cache->priv->local_folder_uris;
+
+ if (find_folder_uri (queue, session, folder_uri) == NULL)
+ g_queue_push_tail (queue, folder_uri);
+ else
+ g_free (folder_uri);
+
+ g_mutex_unlock (cache->priv->stores_mutex);
+}
+
+static void
+mail_folder_cache_folder_unavailable (MailFolderCache *cache,
+ CamelStore *store,
+ const gchar *folder_name)
+{
+ CamelService *service;
+ CamelSession *session;
+ CamelProvider *provider;
+ GQueue *queue;
+ GList *link;
+ gchar *folder_uri;
+
+ /* Disregard virtual stores. */
+ if (CAMEL_IS_VEE_STORE (store))
+ return;
+
+ /* Disregard virtual Junk folders. */
+ if (store->flags & CAMEL_STORE_VJUNK)
+ if (g_strcmp0 (folder_name, CAMEL_VJUNK_NAME) == 0)
+ return;
+
+ /* Disregard virtual Trash folders. */
+ if (store->flags & CAMEL_STORE_VTRASH)
+ if (g_strcmp0 (folder_name, CAMEL_VTRASH_NAME) == 0)
+ return;
+
+ service = CAMEL_SERVICE (store);
+ session = camel_service_get_session (service);
+ provider = camel_service_get_provider (service);
+
+ /* Reuse the stores mutex just because it's handy. */
+ g_mutex_lock (cache->priv->stores_mutex);
+
+ folder_uri = e_mail_folder_uri_build (store, folder_name);
+
+ if (provider->flags & CAMEL_PROVIDER_IS_REMOTE)
+ queue = &cache->priv->remote_folder_uris;
+ else
+ queue = &cache->priv->local_folder_uris;
+
+ link = find_folder_uri (queue, session, folder_uri);
+ if (link != NULL) {
+ g_free (link->data);
+ g_queue_delete_link (queue, link);
+ }
+
+ g_free (folder_uri);
+
+ g_mutex_unlock (cache->priv->stores_mutex);
+}
+
+static void
+mail_folder_cache_folder_deleted (MailFolderCache *cache,
+ CamelStore *store,
+ const gchar *folder_name)
+{
+ CamelService *service;
+ CamelSession *session;
+ GQueue *queue;
+ GList *link;
+ gchar *folder_uri;
+
+ /* Disregard virtual stores. */
+ if (CAMEL_IS_VEE_STORE (store))
+ return;
+
+ /* Disregard virtual Junk folders. */
+ if (store->flags & CAMEL_STORE_VJUNK)
+ if (g_strcmp0 (folder_name, CAMEL_VJUNK_NAME) == 0)
+ return;
+
+ /* Disregard virtual Trash folders. */
+ if (store->flags & CAMEL_STORE_VTRASH)
+ if (g_strcmp0 (folder_name, CAMEL_VTRASH_NAME) == 0)
+ return;
+
+ service = CAMEL_SERVICE (store);
+ session = camel_service_get_session (service);
+
+ /* Reuse the stores mutex just because it's handy. */
+ g_mutex_lock (cache->priv->stores_mutex);
+
+ folder_uri = e_mail_folder_uri_build (store, folder_name);
+
+ queue = &cache->priv->local_folder_uris;
+ link = find_folder_uri (queue, session, folder_uri);
+ if (link != NULL) {
+ g_free (link->data);
+ g_queue_delete_link (queue, link);
+ }
+
+ queue = &cache->priv->remote_folder_uris;
+ link = find_folder_uri (queue, session, folder_uri);
+ if (link != NULL) {
+ g_free (link->data);
+ g_queue_delete_link (queue, link);
+ }
+
+ g_free (folder_uri);
+
+ g_mutex_unlock (cache->priv->stores_mutex);
+}
+
+static void
+mail_folder_cache_class_init (MailFolderCacheClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (MailFolderCachePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mail_folder_cache_set_property;
+ object_class->get_property = mail_folder_cache_get_property;
+ object_class->dispose = mail_folder_cache_dispose;
+ object_class->finalize = mail_folder_cache_finalize;
+
+ class->folder_available = mail_folder_cache_folder_available;
+ class->folder_unavailable = mail_folder_cache_folder_unavailable;
+ class->folder_deleted = mail_folder_cache_folder_deleted;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SESSION,
+ g_param_spec_object (
+ "session",
+ "Session",
+ "Mail session",
+ E_TYPE_MAIL_SESSION,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * MailFolderCache::folder-available
+ * @store: the #CamelStore containing the folder
+ * @folder_name: the name of the folder
+ *
+ * Emitted when a folder becomes available
+ **/
+ signals[FOLDER_AVAILABLE] = g_signal_new (
+ "folder-available",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MailFolderCacheClass, folder_available),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ CAMEL_TYPE_STORE,
+ G_TYPE_STRING);
+
+ /**
+ * MailFolderCache::folder-unavailable
+ * @store: the #CamelStore containing the folder
+ * @folder_name: the name of the folder
+ *
+ * Emitted when a folder becomes unavailable. This represents a
+ * transient condition. See MailFolderCache::folder-deleted to be
+ * notified when a folder is permanently removed.
+ **/
+ signals[FOLDER_UNAVAILABLE] = g_signal_new (
+ "folder-unavailable",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MailFolderCacheClass, folder_unavailable),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ CAMEL_TYPE_STORE,
+ G_TYPE_STRING);
+
+ /**
+ * MailFolderCache::folder-deleted
+ * @store: the #CamelStore containing the folder
+ * @folder_name: the name of the folder
+ *
+ * Emitted when a folder is deleted
+ **/
+ signals[FOLDER_DELETED] = g_signal_new (
+ "folder-deleted",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MailFolderCacheClass, folder_deleted),
+ NULL, NULL, /* accumulator */
+ NULL,
+ G_TYPE_NONE, 2,
+ CAMEL_TYPE_STORE,
+ G_TYPE_STRING);
+
+ /**
+ * MailFolderCache::folder-renamed
+ * @store: the #CamelStore containing the folder
+ * @old_folder_name: the old name of the folder
+ * @new_folder_name: the new name of the folder
+ *
+ * Emitted when a folder is renamed
+ **/
+ signals[FOLDER_RENAMED] = g_signal_new (
+ "folder-renamed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MailFolderCacheClass, folder_renamed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 3,
+ CAMEL_TYPE_STORE,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ /**
+ * MailFolderCache::folder-unread-updated
+ * @store: the #CamelStore containing the folder
+ * @folder_name: the name of the folder
+ * @unread: the number of unread mails in the folder
+ *
+ * Emitted when a we receive an update to the unread count for a folder
+ **/
+ signals[FOLDER_UNREAD_UPDATED] = g_signal_new (
+ "folder-unread-updated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MailFolderCacheClass, folder_unread_updated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 3,
+ CAMEL_TYPE_STORE,
+ G_TYPE_STRING,
+ G_TYPE_INT);
+
+ /**
+ * MailFolderCache::folder-changed
+ * @store: the #CamelStore containing the folder
+ * @folder_name: the name of the folder
+ * @new_messages: the number of new messages for the folder
+ * @msg_uid: uid of the new message, or NULL
+ * @msg_sender: sender of the new message, or NULL
+ * @msg_subject: subject of the new message, or NULL
+ *
+ * Emitted when a folder has changed. If @new_messages is not
+ * exactly 1, @msg_uid, @msg_sender, and @msg_subject will be NULL.
+ **/
+ signals[FOLDER_CHANGED] = g_signal_new (
+ "folder-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MailFolderCacheClass, folder_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 6,
+ CAMEL_TYPE_STORE,
+ G_TYPE_STRING,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+}
+
+static void
+mail_folder_cache_init (MailFolderCache *cache)
+{
+ const gchar *buf;
+ guint timeout;
+
+ cache->priv = MAIL_FOLDER_CACHE_GET_PRIVATE (cache);
+
+ /* initialize values */
+ cache->priv->stores = g_hash_table_new (NULL, NULL);
+ cache->priv->stores_mutex = g_mutex_new ();
+
+ g_queue_init (&cache->priv->updates);
+ cache->priv->count_sent = getenv("EVOLUTION_COUNT_SENT") != NULL;
+ cache->priv->count_trash = getenv("EVOLUTION_COUNT_TRASH") != NULL;
+
+ buf = getenv ("EVOLUTION_PING_TIMEOUT");
+ timeout = buf ? strtoul (buf, NULL, 10) : 600;
+ cache->priv->ping_id = g_timeout_add_seconds (
+ timeout, (GSourceFunc) ping_cb, cache);
+
+ g_queue_init (&cache->priv->local_folder_uris);
+ g_queue_init (&cache->priv->remote_folder_uris);
+}
+
+MailFolderCache *
+mail_folder_cache_new (EMailSession *session)
+{
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
+
+ return g_object_new (
+ MAIL_TYPE_FOLDER_CACHE,
+ "session", session, NULL);
+}
+
+EMailSession *
+mail_folder_cache_get_session (MailFolderCache *cache)
+{
+ g_return_val_if_fail (MAIL_IS_FOLDER_CACHE (cache), NULL);
+
+ return E_MAIL_SESSION (cache->priv->session);
+}
+
+/**
+ * mail_folder_cache_note_store:
+ *
+ * Add a store whose folders should appear in the shell The folders are scanned
+ * from the store, and/or added at runtime via the folder_created event. The
+ * @done function returns if we can free folder info.
+ */
+void
+mail_folder_cache_note_store (MailFolderCache *cache,
+ CamelStore *store,
+ GCancellable *cancellable,
+ NoteDoneFunc done,
+ gpointer data)
+{
+ CamelSession *session;
+ StoreInfo *si;
+ struct _update_data *ud;
+ gint hook = 0;
+
+ g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache));
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ session = camel_service_get_session (CAMEL_SERVICE (store));
+
+ g_mutex_lock (cache->priv->stores_mutex);
+
+ si = g_hash_table_lookup (cache->priv->stores, store);
+ if (si == NULL) {
+ si = store_info_new (store);
+ g_hash_table_insert (cache->priv->stores, store, si);
+ hook = TRUE;
+ }
+
+ ud = g_malloc0 (sizeof (*ud));
+ ud->done = done;
+ ud->data = data;
+ ud->cache = cache;
+
+ if (G_IS_CANCELLABLE (cancellable))
+ ud->cancellable = g_object_ref (cancellable);
+
+ /* We might get a race when setting up a store, such that it is
+ * still left in offline mode, after we've gone online. This
+ * catches and fixes it up when the shell opens us. */
+ if (CAMEL_IS_DISCO_STORE (store)) {
+ if (camel_session_get_online (session) &&
+ camel_disco_store_status (CAMEL_DISCO_STORE (store)) ==
+ CAMEL_DISCO_STORE_OFFLINE) {
+ e_mail_store_go_online (
+ store, G_PRIORITY_DEFAULT, cancellable,
+ (GAsyncReadyCallback) store_go_online_cb, ud);
+ } else {
+ goto normal_setup;
+ }
+ } else if (CAMEL_IS_OFFLINE_STORE (store)) {
+ if (camel_session_get_online (session) &&
+ !camel_offline_store_get_online (
+ CAMEL_OFFLINE_STORE (store))) {
+ e_mail_store_go_online (
+ store, G_PRIORITY_DEFAULT, cancellable,
+ (GAsyncReadyCallback) store_go_online_cb, ud);
+ } else {
+ goto normal_setup;
+ }
+ } else {
+ normal_setup:
+ if (store_has_folder_hierarchy (store))
+ camel_store_get_folder_info (
+ store, NULL,
+ CAMEL_STORE_FOLDER_INFO_FAST |
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE |
+ CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
+ G_PRIORITY_DEFAULT, cancellable,
+ (GAsyncReadyCallback) update_folders, ud);
+ }
+
+ g_queue_push_tail (&si->folderinfo_updates, ud);
+
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ /* there is potential for race here, but it is safe as we check
+ * for the store anyway */
+ if (hook) {
+ g_signal_connect (
+ store, "folder-opened",
+ G_CALLBACK (store_folder_opened_cb), cache);
+ g_signal_connect (
+ store, "folder-created",
+ G_CALLBACK (store_folder_created_cb), cache);
+ g_signal_connect (
+ store, "folder-deleted",
+ G_CALLBACK (store_folder_deleted_cb), cache);
+ g_signal_connect (
+ store, "folder-renamed",
+ G_CALLBACK (store_folder_renamed_cb), cache);
+ }
+
+ if (hook && CAMEL_IS_SUBSCRIBABLE (store)) {
+ g_signal_connect (
+ store, "folder-subscribed",
+ G_CALLBACK (store_folder_subscribed_cb), cache);
+ g_signal_connect (
+ store, "folder-unsubscribed",
+ G_CALLBACK (store_folder_unsubscribed_cb), cache);
+ }
+}
+
+/**
+ * mail_folder_cache_note_folder:
+ *
+ * When a folder has been opened, notify it for watching. The folder must have
+ * already been created on the store (which has already been noted) before the
+ * folder can be opened
+ */
+void
+mail_folder_cache_note_folder (MailFolderCache *cache,
+ CamelFolder *folder)
+{
+ CamelStore *parent_store;
+ StoreInfo *si;
+ struct _folder_info *mfi;
+ const gchar *full_name;
+
+ g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache));
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ if (cache->priv->stores == NULL
+ || (si = g_hash_table_lookup (cache->priv->stores, parent_store)) == NULL
+ || (mfi = g_hash_table_lookup (si->folders, full_name)) == NULL) {
+ w(g_warning("Noting folder before store initialised"));
+ g_mutex_unlock (cache->priv->stores_mutex);
+ return;
+ }
+
+ /* dont do anything if we already have this */
+ if (mfi->folder == folder) {
+ g_mutex_unlock (cache->priv->stores_mutex);
+ return;
+ }
+
+ mfi->folder = folder;
+
+ g_object_add_weak_pointer (G_OBJECT (folder), &mfi->folder);
+
+ update_1folder (cache, mfi, 0, NULL, NULL, NULL, NULL);
+
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ g_signal_connect (
+ folder, "changed",
+ G_CALLBACK (folder_changed_cb), cache);
+}
+
+/**
+ * mail_folder_cache_get_folder_from_uri:
+ *
+ * Gets the #CamelFolder for the supplied @uri.
+ *
+ * Returns: %TRUE if the URI is available, folderp is set to a reffed
+ * folder if the folder has also already been opened
+ */
+gboolean
+mail_folder_cache_get_folder_from_uri (MailFolderCache *cache,
+ const gchar *uri,
+ CamelFolder **folderp)
+{
+ struct _find_info fi = { uri, NULL };
+
+ if (cache->priv->stores == NULL)
+ return FALSE;
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ g_hash_table_foreach (
+ cache->priv->stores, (GHFunc)
+ storeinfo_find_folder_info, &fi);
+ if (folderp) {
+ if (fi.fi && fi.fi->folder)
+ *folderp = g_object_ref (fi.fi->folder);
+ else
+ *folderp = NULL;
+ }
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ return fi.fi != NULL;
+}
+
+gboolean
+mail_folder_cache_get_folder_info_flags (MailFolderCache *cache,
+ CamelFolder *folder,
+ CamelFolderInfoFlags *flags)
+{
+ struct _find_info fi = { NULL, NULL };
+ gchar *folder_uri;
+
+ if (cache->priv->stores == NULL)
+ return FALSE;
+
+ folder_uri = e_mail_folder_uri_from_folder (folder);
+ fi.folder_uri = folder_uri;
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ g_hash_table_foreach (
+ cache->priv->stores, (GHFunc)
+ storeinfo_find_folder_info, &fi);
+ if (flags) {
+ if (fi.fi)
+ *flags = fi.fi->flags;
+ else
+ *flags = 0;
+ }
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ g_free (folder_uri);
+
+ return fi.fi != NULL;
+}
+
+/* Returns whether folder 'folder' has children based on folder_info->child property.
+ * If not found returns FALSE and sets 'found' to FALSE, if not NULL. */
+gboolean
+mail_folder_cache_get_folder_has_children (MailFolderCache *cache,
+ CamelFolder *folder,
+ gboolean *found)
+{
+ struct _find_info fi = { NULL, NULL };
+ gchar *folder_uri;
+
+ g_return_val_if_fail (MAIL_IS_FOLDER_CACHE (cache), FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ if (cache->priv->stores == NULL)
+ return FALSE;
+
+ folder_uri = e_mail_folder_uri_from_folder (folder);
+ fi.folder_uri = folder_uri;
+
+ g_mutex_lock (cache->priv->stores_mutex);
+ g_hash_table_foreach (
+ cache->priv->stores, (GHFunc)
+ storeinfo_find_folder_info, &fi);
+ if (found != NULL)
+ *found = fi.fi != NULL;
+ g_mutex_unlock (cache->priv->stores_mutex);
+
+ g_free (folder_uri);
+
+ return fi.fi != NULL && fi.fi->has_children;
+}
+
+void
+mail_folder_cache_get_local_folder_uris (MailFolderCache *self,
+ GQueue *out_queue)
+{
+ GList *head, *link;
+
+ g_return_if_fail (MAIL_IS_FOLDER_CACHE (self));
+ g_return_if_fail (out_queue != NULL);
+
+ /* Reuse the stores mutex just because it's handy. */
+ g_mutex_lock (self->priv->stores_mutex);
+
+ head = g_queue_peek_head_link (&self->priv->local_folder_uris);
+
+ for (link = head; link != NULL; link = g_list_next (link))
+ g_queue_push_tail (out_queue, g_strdup (link->data));
+
+ g_mutex_unlock (self->priv->stores_mutex);
+}
+
+void
+mail_folder_cache_get_remote_folder_uris (MailFolderCache *self,
+ GQueue *out_queue)
+{
+ GList *head, *link;
+
+ g_return_if_fail (MAIL_IS_FOLDER_CACHE (self));
+ g_return_if_fail (out_queue != NULL);
+
+ /* Reuse the stores mutex just because it's handy. */
+ g_mutex_lock (self->priv->stores_mutex);
+
+ head = g_queue_peek_head_link (&self->priv->remote_folder_uris);
+
+ for (link = head; link != NULL; link = g_list_next (link))
+ g_queue_push_tail (out_queue, g_strdup (link->data));
+
+ g_mutex_unlock (self->priv->stores_mutex);
+}
+
+void
+mail_folder_cache_service_added (MailFolderCache *cache,
+ CamelService *service)
+{
+ g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache));
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ mail_folder_cache_note_store (
+ cache, CAMEL_STORE (service), NULL, NULL, NULL);
+}
+
+void
+mail_folder_cache_service_removed (MailFolderCache *cache,
+ CamelService *service)
+{
+ StoreInfo *si;
+
+ g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache));
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ if (cache->priv->stores == NULL)
+ return;
+
+ g_mutex_lock (cache->priv->stores_mutex);
+
+ si = g_hash_table_lookup (cache->priv->stores, service);
+ if (si != NULL) {
+ g_hash_table_remove (cache->priv->stores, service);
+
+ g_signal_handlers_disconnect_matched (
+ service, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, cache);
+
+ g_hash_table_foreach (
+ si->folders, (GHFunc)
+ unset_folder_info_hash, cache);
+
+ store_info_free (si);
+ }
+
+ g_mutex_unlock (cache->priv->stores_mutex);
+}
+
+void
+mail_folder_cache_service_enabled (MailFolderCache *cache,
+ CamelService *service)
+{
+ g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache));
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ mail_folder_cache_note_store (
+ cache, CAMEL_STORE (service), NULL, NULL, NULL);
+}
+
+void
+mail_folder_cache_service_disabled (MailFolderCache *cache,
+ CamelService *service)
+{
+ g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache));
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ /* To the folder cache, disabling a service is the same as
+ * removing it. We keep a separate callback function only
+ * to use as a breakpoint target in a debugger. */
+ mail_folder_cache_service_removed (cache, service);
+}
diff --git a/libemail-engine/mail-folder-cache.h b/libemail-engine/mail-folder-cache.h
new file mode 100644
index 0000000000..b0d1d1bc49
--- /dev/null
+++ b/libemail-engine/mail-folder-cache.h
@@ -0,0 +1,156 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Peter Williams <peterw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ * Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ * Copyright (C) 2009 Intel Corporation
+ *
+ */
+
+#ifndef MAIL_FOLDER_CACHE_H
+#define MAIL_FOLDER_CACHE_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define MAIL_TYPE_FOLDER_CACHE \
+ (mail_folder_cache_get_type ())
+#define MAIL_FOLDER_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), MAIL_TYPE_FOLDER_CACHE, MailFolderCache))
+#define MAIL_FOLDER_CACHE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), MAIL_TYPE_FOLDER_CACHE, MailFolderCacheClass))
+#define MAIL_IS_FOLDER_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), MAIL_TYPE_FOLDER_CACHE))
+#define MAIL_IS_FOLDER_CACHE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), MAIL_TYPE_FOLDER_CACHE))
+#define MAIL_FOLDER_CACHE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), MAIL_TYPE_FOLDER_CACHE, MailFolderCacheClass))
+
+G_BEGIN_DECLS
+
+/* Avoid a circular dependency. */
+struct _EMailSession;
+
+typedef struct _MailFolderCache MailFolderCache;
+typedef struct _MailFolderCacheClass MailFolderCacheClass;
+typedef struct _MailFolderCachePrivate MailFolderCachePrivate;
+
+/**
+ * NoteDoneFunc:
+ *
+ * The signature of a function to be registered as a callback for
+ * mail_folder_cache_note_store()
+ */
+typedef gboolean (*NoteDoneFunc) (MailFolderCache *cache,
+ CamelStore *store,
+ CamelFolderInfo *info,
+ gpointer data);
+
+/**
+ * MailFolderCache:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ */
+struct _MailFolderCache {
+ GObject parent;
+ MailFolderCachePrivate *priv;
+};
+
+struct _MailFolderCacheClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*folder_available) (MailFolderCache *cache,
+ CamelStore *store,
+ const gchar *folder_name);
+ void (*folder_unavailable) (MailFolderCache *cache,
+ CamelStore *store,
+ const gchar *folder_name);
+ void (*folder_deleted) (MailFolderCache *cache,
+ CamelStore *store,
+ const gchar *folder_name);
+ void (*folder_renamed) (MailFolderCache *cache,
+ CamelStore *store,
+ const gchar *old_folder_name,
+ const gchar *new_folder_name);
+ void (*folder_unread_updated)
+ (MailFolderCache *cache,
+ CamelStore *store,
+ const gchar *folder_name,
+ gint unread);
+ void (*folder_changed) (MailFolderCache *cache,
+ CamelStore *store,
+ gint new_messages,
+ const gchar *msg_uid,
+ const gchar *msg_sender,
+ const gchar *msg_subject);
+};
+
+GType mail_folder_cache_get_type (void) G_GNUC_CONST;
+MailFolderCache *
+ mail_folder_cache_new (struct _EMailSession *session);
+struct _EMailSession *
+ mail_folder_cache_get_session (MailFolderCache *cache);
+void mail_folder_cache_note_store (MailFolderCache *cache,
+ CamelStore *store,
+ GCancellable *cancellable,
+ NoteDoneFunc done,
+ gpointer data);
+void mail_folder_cache_note_folder (MailFolderCache *cache,
+ CamelFolder *folder);
+gboolean mail_folder_cache_get_folder_from_uri
+ (MailFolderCache *cache,
+ const gchar *uri,
+ CamelFolder **folderp);
+gboolean mail_folder_cache_get_folder_info_flags
+ (MailFolderCache *cache,
+ CamelFolder *folder,
+ CamelFolderInfoFlags *flags);
+gboolean mail_folder_cache_get_folder_has_children
+ (MailFolderCache *cache,
+ CamelFolder *folder,
+ gboolean *found);
+void mail_folder_cache_get_local_folder_uris
+ (MailFolderCache *cache,
+ GQueue *out_queue);
+void mail_folder_cache_get_remote_folder_uris
+ (MailFolderCache *cache,
+ GQueue *out_queue);
+void mail_folder_cache_service_added (MailFolderCache *cache,
+ CamelService *service);
+void mail_folder_cache_service_removed
+ (MailFolderCache *cache,
+ CamelService *service);
+void mail_folder_cache_service_enabled
+ (MailFolderCache *cache,
+ CamelService *service);
+void mail_folder_cache_service_disabled
+ (MailFolderCache *cache,
+ CamelService *service);
+
+
+G_END_DECLS
+
+#endif /* MAIL_FOLDER_CACHE_H */
diff --git a/libemail-engine/mail-ops.c b/libemail-engine/mail-ops.c
new file mode 100644
index 0000000000..7063179e55
--- /dev/null
+++ b/libemail-engine/mail-ops.c
@@ -0,0 +1,1691 @@
+/*
+ * mail-ops.c: callbacks for the mail toolbar/menus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Peter Williams <peterw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+
+#include <libedataserver/e-data-server-util.h>
+
+#include <libemail-utils/e-account-utils.h>
+#include <libemail-utils/mail-mt.h>
+
+#include "e-mail-utils.h"
+#include "mail-ops.h"
+#include "mail-tools.h"
+
+#include "e-mail-session.h"
+#include "e-mail-session-utils.h"
+
+#define w(x)
+#define d(x)
+
+/* XXX Make this a preprocessor definition. */
+const gchar *x_mailer = "Evolution " VERSION SUB_VERSION " " VERSION_COMMENT;
+
+/* used for both just filtering a folder + uid's, and for filtering a whole folder */
+/* used both for fetching mail, and for filtering mail */
+struct _filter_mail_msg {
+ MailMsg base;
+
+ EMailSession *session;
+ CamelFolder *source_folder; /* where they come from */
+ GPtrArray *source_uids; /* uids to copy, or NULL == copy all */
+ CamelUIDCache *cache; /* UID cache if we are to cache the uids, NULL otherwise */
+ CamelFilterDriver *driver;
+ gint delete; /* delete messages after filtering them? */
+ CamelFolder *destination; /* default destination for any messages, NULL for none */
+};
+
+/* since fetching also filters, we subclass the data here */
+struct _fetch_mail_msg {
+ struct _filter_mail_msg fmsg;
+
+ CamelStore *store;
+ GCancellable *cancellable; /* we have our own cancellation
+ * struct, the other should be empty */
+ gint keep; /* keep on server? */
+
+ void (*done)(gpointer data);
+ gpointer data;
+};
+
+static gchar *
+em_filter_folder_element_desc (struct _filter_mail_msg *m)
+{
+ return g_strdup (_("Filtering Selected Messages"));
+}
+
+/* filter a folder, or a subset thereof, uses source_folder/source_uids */
+/* this is shared with fetch_mail */
+static void
+em_filter_folder_element_exec (struct _filter_mail_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ GPtrArray *uids, *folder_uids = NULL;
+
+ folder = m->source_folder;
+
+ if (folder == NULL || camel_folder_get_message_count (folder) == 0)
+ return;
+
+ if (m->destination) {
+ camel_folder_freeze (m->destination);
+ camel_filter_driver_set_default_folder (m->driver, m->destination);
+ }
+
+ camel_folder_freeze (folder);
+
+ if (m->source_uids)
+ uids = m->source_uids;
+ else
+ folder_uids = uids = camel_folder_get_uids (folder);
+
+ camel_filter_driver_filter_folder (
+ m->driver, folder, m->cache, uids, m->delete,
+ cancellable, error);
+ camel_filter_driver_flush (m->driver, error);
+
+ if (folder_uids)
+ camel_folder_free_uids (folder, folder_uids);
+
+ /* sync our source folder */
+ if (!m->cache)
+ camel_folder_synchronize_sync (
+ folder, FALSE, cancellable, error);
+ camel_folder_thaw (folder);
+
+ if (m->destination)
+ camel_folder_thaw (m->destination);
+
+ /* this may thaw/unref source folders, do it here so we dont do
+ * it in the main thread see also fetch_mail_fetch () below */
+ g_object_unref (m->driver);
+ m->driver = NULL;
+}
+
+static void
+em_filter_folder_element_done (struct _filter_mail_msg *m)
+{
+}
+
+static void
+em_filter_folder_element_free (struct _filter_mail_msg *m)
+{
+ mail_session_flush_filter_log (m->session);
+
+ if (m->session)
+ g_object_unref (m->session);
+
+ if (m->source_folder)
+ g_object_unref (m->source_folder);
+
+ if (m->source_uids)
+ em_utils_uids_free (m->source_uids);
+
+ if (m->destination)
+ g_object_unref (m->destination);
+
+ if (m->driver)
+ g_object_unref (m->driver);
+}
+
+static MailMsgInfo em_filter_folder_element_info = {
+ sizeof (struct _filter_mail_msg),
+ (MailMsgDescFunc) em_filter_folder_element_desc,
+ (MailMsgExecFunc) em_filter_folder_element_exec,
+ (MailMsgDoneFunc) em_filter_folder_element_done,
+ (MailMsgFreeFunc) em_filter_folder_element_free
+};
+
+void
+mail_filter_folder (EMailSession *session,
+ CamelFolder *source_folder,
+ GPtrArray *uids,
+ const gchar *type,
+ gboolean notify)
+{
+ struct _filter_mail_msg *m;
+
+ m = mail_msg_new (&em_filter_folder_element_info);
+ m->session = g_object_ref (session);
+ m->source_folder = g_object_ref (source_folder);
+ m->source_uids = uids;
+ m->cache = NULL;
+ m->delete = FALSE;
+
+ m->driver = camel_session_get_filter_driver (
+ CAMEL_SESSION (session), type, NULL);
+
+ if (!notify) {
+ /* FIXME: have a #define NOTIFY_FILTER_NAME macro? */
+ /* the filter name has to stay in sync with mail-session::get_filter_driver */
+ camel_filter_driver_remove_rule_by_name (m->driver, "new-mail-notification");
+ }
+
+ mail_msg_unordered_push (m);
+}
+
+/* ********************************************************************** */
+
+static gchar *
+fetch_mail_desc (struct _fetch_mail_msg *m)
+{
+ return g_strdup (_("Fetching Mail"));
+}
+
+static void
+fetch_mail_exec (struct _fetch_mail_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _filter_mail_msg *fm = (struct _filter_mail_msg *) m;
+ CamelFolder *folder = NULL;
+ CamelService *service;
+ CamelSession *session;
+ CamelURL *url;
+ gboolean is_local_delivery;
+ const gchar *uid;
+ gint i;
+
+ service = CAMEL_SERVICE (m->store);
+ session = camel_service_get_session (service);
+
+ fm->destination = e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_LOCAL_INBOX);
+ if (fm->destination == NULL)
+ goto exit;
+ g_object_ref (fm->destination);
+
+ url = camel_service_new_camel_url (service);
+ is_local_delivery = em_utils_is_local_delivery_mbox_file (url);
+
+ if (is_local_delivery) {
+ gchar *path;
+ gchar *url_string;
+
+ path = mail_tool_do_movemail (m->store, error);
+ url_string = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+
+ if (path && (!error || !*error)) {
+ camel_folder_freeze (fm->destination);
+ camel_filter_driver_set_default_folder (
+ fm->driver, fm->destination);
+ camel_filter_driver_filter_mbox (
+ fm->driver, path, url_string,
+ cancellable, error);
+ camel_folder_thaw (fm->destination);
+
+ if (!error || !*error)
+ g_unlink (path);
+ }
+
+ g_free (path);
+ g_free (url_string);
+ } else {
+ uid = camel_service_get_uid (service);
+
+ folder = fm->source_folder =
+ e_mail_session_get_inbox_sync (
+ fm->session, uid, cancellable, error);
+ }
+
+ camel_url_free (url);
+
+ if (folder != NULL) {
+ /* This handles 'keep on server' stuff, if we have any new
+ * uid's to copy across, we need to copy them to a new array
+ * 'cause of the way fetch_mail_free works. */
+ CamelUIDCache *cache = NULL;
+ CamelStore *parent_store;
+ CamelService *service;
+ const gchar *data_dir;
+ gchar *cachename;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ service = CAMEL_SERVICE (parent_store);
+ data_dir = camel_service_get_user_data_dir (service);
+
+ cachename = g_build_filename (data_dir, "uid-cache", NULL);
+ cache = camel_uid_cache_new (cachename);
+ g_free (cachename);
+
+ if (cache) {
+ GPtrArray *folder_uids, *cache_uids, *uids;
+
+ folder_uids = camel_folder_get_uids (folder);
+ cache_uids = camel_uid_cache_get_new_uids (cache, folder_uids);
+ if (cache_uids) {
+ /* need to copy this, sigh */
+ fm->source_uids = uids = g_ptr_array_new ();
+ g_ptr_array_set_size (uids, cache_uids->len);
+ for (i = 0; i < cache_uids->len; i++)
+ uids->pdata[i] = g_strdup (cache_uids->pdata[i]);
+ camel_uid_cache_free_uids (cache_uids);
+
+ fm->cache = cache;
+ em_filter_folder_element_exec (fm, cancellable, error);
+
+ /* need to uncancel so writes/etc. don't fail */
+ if (g_cancellable_is_cancelled (m->cancellable))
+ g_cancellable_reset (m->cancellable);
+
+ /* save the cache of uids that we've just downloaded */
+ camel_uid_cache_save (cache);
+ }
+
+ if (fm->delete && (!error || !*error)) {
+ /* not keep on server - just delete all
+ * the actual messages on the server */
+ for (i = 0; i < folder_uids->len; i++) {
+ camel_folder_delete_message (
+ folder, folder_uids->pdata[i]);
+ }
+ }
+
+ if ((fm->delete || cache_uids) && (!error || !*error)) {
+ /* expunge messages (downloaded so far) */
+ /* FIXME Not passing a GCancellable or GError here. */
+ camel_folder_synchronize_sync (
+ folder, fm->delete, NULL, NULL);
+ }
+
+ camel_uid_cache_destroy (cache);
+ camel_folder_free_uids (folder, folder_uids);
+ } else {
+ em_filter_folder_element_exec (fm, cancellable, error);
+ }
+
+ /* we unref the source folder here since we
+ * may now block in finalize (we try to
+ * disconnect cleanly) */
+ g_object_unref (fm->source_folder);
+ fm->source_folder = NULL;
+ }
+
+exit:
+ /* we unref this here as it may have more work to do (syncing
+ * folders and whatnot) before we are really done */
+ /* should this be cancellable too? (i.e. above unregister above) */
+ if (fm->driver) {
+ g_object_unref (fm->driver);
+ fm->driver = NULL;
+ }
+
+ /* also disconnect if not a local delivery mbox;
+ * there is no need to keep the connection alive forever */
+ if (!is_local_delivery)
+ em_utils_disconnect_service_sync (
+ service, TRUE, cancellable, NULL);
+}
+
+static void
+fetch_mail_done (struct _fetch_mail_msg *m)
+{
+ if (m->done)
+ m->done (m->data);
+}
+
+static void
+fetch_mail_free (struct _fetch_mail_msg *m)
+{
+ if (m->store != NULL)
+ g_object_unref (m->store);
+
+ if (m->cancellable != NULL)
+ g_object_unref (m->cancellable);
+
+ em_filter_folder_element_free ((struct _filter_mail_msg *) m);
+}
+
+static MailMsgInfo fetch_mail_info = {
+ sizeof (struct _fetch_mail_msg),
+ (MailMsgDescFunc) fetch_mail_desc,
+ (MailMsgExecFunc) fetch_mail_exec,
+ (MailMsgDoneFunc) fetch_mail_done,
+ (MailMsgFreeFunc) fetch_mail_free
+};
+
+/* ouch, a 'do everything' interface ... */
+void
+mail_fetch_mail (CamelStore *store,
+ gint keep,
+ const gchar *type,
+ GCancellable *cancellable,
+ CamelFilterGetFolderFunc get_folder,
+ gpointer get_data,
+ CamelFilterStatusFunc *status,
+ gpointer status_data,
+ void (*done)(gpointer data),
+ gpointer data)
+{
+ struct _fetch_mail_msg *m;
+ struct _filter_mail_msg *fm;
+ CamelSession *session;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ session = camel_service_get_session (CAMEL_SERVICE (store));
+
+ m = mail_msg_new (&fetch_mail_info);
+ fm = (struct _filter_mail_msg *) m;
+ fm->session = g_object_ref (session);
+ m->store = g_object_ref (store);
+ fm->delete = !keep;
+ fm->cache = NULL;
+ if (cancellable)
+ m->cancellable = g_object_ref (cancellable);
+ m->done = done;
+ m->data = data;
+
+ fm->driver = camel_session_get_filter_driver (session, type, NULL);
+ camel_filter_driver_set_folder_func (fm->driver, get_folder, get_data);
+ if (status)
+ camel_filter_driver_set_status_func (fm->driver, status, status_data);
+
+ mail_msg_unordered_push (m);
+}
+
+/* ********************************************************************** */
+/* sending stuff */
+/* ** SEND MAIL *********************************************************** */
+
+static const gchar *normal_recipients[] = {
+ CAMEL_RECIPIENT_TYPE_TO,
+ CAMEL_RECIPIENT_TYPE_CC,
+ CAMEL_RECIPIENT_TYPE_BCC
+};
+
+static const gchar *resent_recipients[] = {
+ CAMEL_RECIPIENT_TYPE_RESENT_TO,
+ CAMEL_RECIPIENT_TYPE_RESENT_CC,
+ CAMEL_RECIPIENT_TYPE_RESENT_BCC
+};
+
+struct _send_queue_msg {
+ MailMsg base;
+
+ EMailSession *session;
+ CamelFolder *queue;
+ CamelTransport *transport;
+
+ CamelFilterDriver *driver;
+
+ /* we use camelfilterstatusfunc, even though its not the filter doing it */
+ CamelFilterStatusFunc *status;
+ gpointer status_data;
+
+ void (*done)(gpointer data);
+ gpointer data;
+};
+
+static void report_status (struct _send_queue_msg *m,
+ enum camel_filter_status_t status,
+ gint pc,
+ const gchar *desc,
+ ...);
+
+/* send 1 message to a specific transport */
+static void
+mail_send_message (struct _send_queue_msg *m,
+ CamelFolder *queue,
+ const gchar *uid,
+ CamelTransport *transport,
+ CamelFilterDriver *driver,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EAccount *account = NULL;
+ const CamelInternetAddress *iaddr;
+ CamelAddress *from, *recipients;
+ CamelMessageInfo *info = NULL;
+ CamelProvider *provider;
+ gchar *transport_uid = NULL;
+ gchar *sent_folder_uri = NULL;
+ const gchar *resent_from, *tmp;
+ CamelFolder *folder = NULL;
+ GString *err = NULL;
+ struct _camel_header_raw *xev, *header;
+ CamelMimeMessage *message;
+ gint i;
+ GError *local_error = NULL;
+
+ message = camel_folder_get_message_sync (
+ queue, uid, cancellable, error);
+ if (!message)
+ return;
+
+ camel_medium_set_header (CAMEL_MEDIUM (message), "X-Mailer", x_mailer);
+
+ err = g_string_new ("");
+ xev = mail_tool_remove_xevolution_headers (message);
+
+ tmp = camel_header_raw_find (&xev, "X-Evolution-Account", NULL);
+ if (tmp != NULL) {
+ gchar *name;
+
+ name = g_strstrip (g_strdup (tmp));
+ if ((account = e_get_account_by_uid (name))
+ /* 'old' x-evolution-account stored the name, how silly */
+ || (account = e_get_account_by_name (name))) {
+ if (account->transport) {
+ CamelService *service;
+ gchar *transport_uid;
+
+ transport_uid = g_strconcat (
+ account->uid, "-transport", NULL);
+ service = camel_session_get_service (
+ CAMEL_SESSION (m->session),
+ transport_uid);
+ g_free (transport_uid);
+
+ if (CAMEL_IS_TRANSPORT (service))
+ transport = CAMEL_TRANSPORT (service);
+ }
+
+ sent_folder_uri = g_strdup (account->sent_folder_uri);
+ }
+ g_free (name);
+ }
+
+ if (!account) {
+ /* default back to these headers */
+ tmp = camel_header_raw_find(&xev, "X-Evolution-Transport", NULL);
+ if (tmp)
+ transport_uid = g_strstrip (g_strdup (tmp));
+
+ tmp = camel_header_raw_find(&xev, "X-Evolution-Fcc", NULL);
+ if (tmp)
+ sent_folder_uri = g_strstrip (g_strdup (tmp));
+ }
+
+ if (transport != NULL) {
+ const gchar *uid;
+
+ /* Let the dialog know the right account it is using. */
+ uid = camel_service_get_uid (CAMEL_SERVICE (transport));
+ report_status (m, CAMEL_FILTER_STATUS_ACTION, 0, uid);
+ }
+
+ /* Check for email sending */
+ from = (CamelAddress *) camel_internet_address_new ();
+ resent_from = camel_medium_get_header (CAMEL_MEDIUM (message), "Resent-From");
+ if (resent_from) {
+ camel_address_decode (from, resent_from);
+ } else {
+ iaddr = camel_mime_message_get_from (message);
+ camel_address_copy (from, CAMEL_ADDRESS (iaddr));
+ }
+
+ recipients = (CamelAddress *) camel_internet_address_new ();
+ for (i = 0; i < 3; i++) {
+ const gchar *type;
+
+ type = resent_from ? resent_recipients[i] : normal_recipients[i];
+ iaddr = camel_mime_message_get_recipients (message, type);
+ camel_address_cat (recipients, CAMEL_ADDRESS (iaddr));
+ }
+
+ if (camel_address_length (recipients) > 0) {
+ if (!em_utils_connect_service_sync (
+ CAMEL_SERVICE (transport), cancellable, error))
+ goto exit;
+
+ if (!camel_transport_send_to_sync (
+ transport, message, from,
+ recipients, cancellable, error))
+ goto exit;
+ }
+
+ /* Now check for posting, failures are ignored */
+ info = camel_message_info_new (NULL);
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0);
+
+ for (header = xev; header; header = header->next) {
+ gchar *uri;
+
+ if (strcmp(header->name, "X-Evolution-PostTo") != 0)
+ continue;
+
+ /* TODO: don't lose errors */
+
+ uri = g_strstrip (g_strdup (header->value));
+ /* FIXME Not passing a GCancellable or GError here. */
+ folder = e_mail_session_uri_to_folder_sync (
+ m->session, uri, 0, NULL, NULL);
+ if (folder) {
+ /* FIXME Not passing a GCancellable or GError here. */
+ camel_folder_append_message_sync (
+ folder, message, info, NULL, NULL, NULL);
+ g_object_unref (folder);
+ folder = NULL;
+ }
+ g_free (uri);
+ }
+
+ /* post process */
+ mail_tool_restore_xevolution_headers (message, xev);
+
+ if (driver) {
+ camel_filter_driver_filter_message (
+ driver, message, info, NULL, NULL,
+ NULL, "", cancellable, &local_error);
+
+ if (local_error != NULL) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto exit;
+
+ /* sending mail, filtering failed */
+ g_string_append_printf (
+ err, _("Failed to apply outgoing filters: %s"),
+ local_error->message);
+
+ g_clear_error (&local_error);
+ }
+ }
+
+ provider = camel_service_get_provider (CAMEL_SERVICE (transport));
+
+ if (provider == NULL
+ || !(provider->flags & CAMEL_PROVIDER_DISABLE_SENT_FOLDER)) {
+ GError *local_error = NULL;
+
+ if (sent_folder_uri) {
+ folder = e_mail_session_uri_to_folder_sync (
+ m->session, sent_folder_uri, 0,
+ cancellable, &local_error);
+ if (folder == NULL) {
+ g_string_append_printf (
+ err, _("Failed to append to %s: %s\n"
+ "Appending to local 'Sent' folder instead."),
+ sent_folder_uri,
+ local_error ?
+ local_error->message :
+ _("Unknown error"));
+ if (local_error)
+ g_clear_error (&local_error);
+ }
+ }
+
+ if (!folder) {
+ folder = e_mail_session_get_local_folder (
+ m->session, E_MAIL_LOCAL_FOLDER_SENT);
+ g_object_ref (folder);
+ }
+
+ if (!camel_folder_append_message_sync (
+ folder, message, info,
+ NULL, cancellable, &local_error)) {
+
+ CamelFolder *sent_folder;
+
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto exit;
+
+ sent_folder = e_mail_session_get_local_folder (
+ m->session, E_MAIL_LOCAL_FOLDER_SENT);
+
+ if (folder != sent_folder) {
+ const gchar *description;
+
+ description = camel_folder_get_description (folder);
+ if (err->len)
+ g_string_append(err, "\n\n");
+ g_string_append_printf (
+ err, _("Failed to append to %s: %s\n"
+ "Appending to local 'Sent' folder instead."),
+ description, local_error->message);
+ g_object_ref (sent_folder);
+ g_object_unref (folder);
+ folder = sent_folder;
+
+ g_clear_error (&local_error);
+ camel_folder_append_message_sync (
+ folder, message, info,
+ NULL, cancellable, &local_error);
+ }
+
+ if (local_error != NULL) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto exit;
+
+ if (err->len)
+ g_string_append(err, "\n\n");
+ g_string_append_printf (
+ err, _("Failed to append to local 'Sent' folder: %s"),
+ local_error->message);
+ }
+ }
+ }
+
+ if (local_error == NULL) {
+ /* Mark the draft message for deletion, if present. */
+ e_mail_session_handle_draft_headers_sync (
+ m->session, message, cancellable, &local_error);
+ if (local_error != NULL) {
+ g_warning ("%s: Failed to handle draft headers: %s", G_STRFUNC, local_error->message);
+ g_clear_error (&local_error);
+ }
+
+ /* Set flags on the original source message, if present.
+ * Source message refers to the message being forwarded
+ * or replied to. */
+ e_mail_session_handle_source_headers_sync (
+ m->session, message, cancellable, &local_error);
+ if (local_error != NULL) {
+ g_warning ("%s: Failed to handle source headers: %s", G_STRFUNC, local_error->message);
+ g_clear_error (&local_error);
+ }
+ }
+
+ if (local_error == NULL) {
+ camel_folder_set_message_flags (
+ queue, uid, CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_SEEN, ~0);
+ /* Sync it to disk, since if it crashes in between,
+ * we keep sending it again on next start. */
+ /* FIXME Not passing a GCancellable or GError here. */
+ camel_folder_synchronize_sync (queue, FALSE, NULL, NULL);
+ }
+
+ if (err->len) {
+ /* set the culmulative exception report */
+ g_set_error (
+ &local_error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC, "%s", err->str);
+ }
+
+exit:
+ if (local_error != NULL)
+ g_propagate_error (error, local_error);
+
+ /* FIXME Not passing a GCancellable or GError here. */
+ if (folder) {
+ camel_folder_synchronize_sync (folder, FALSE, NULL, NULL);
+ g_object_unref (folder);
+ }
+ if (info)
+ camel_message_info_free (info);
+ g_object_unref (recipients);
+ g_object_unref (from);
+ g_free (sent_folder_uri);
+ g_free (transport_uid);
+ camel_header_raw_clear (&xev);
+ g_string_free (err, TRUE);
+ g_object_unref (message);
+}
+
+/* ** SEND MAIL QUEUE ***************************************************** */
+
+static void
+report_status (struct _send_queue_msg *m,
+ enum camel_filter_status_t status,
+ gint pc,
+ const gchar *desc,
+ ...)
+{
+ va_list ap;
+ gchar *str;
+
+ if (m->status) {
+ va_start (ap, desc);
+ str = g_strdup_vprintf (desc, ap);
+ va_end (ap);
+ m->status (m->driver, status, pc, str, m->status_data);
+ g_free (str);
+ }
+}
+
+static void
+send_queue_exec (struct _send_queue_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *sent_folder;
+ GPtrArray *uids, *send_uids = NULL;
+ gint i, j;
+ GError *local_error = NULL;
+
+ d(printf("sending queue\n"));
+
+ sent_folder =
+ e_mail_session_get_local_folder (
+ m->session, E_MAIL_LOCAL_FOLDER_SENT);
+
+ if (!(uids = camel_folder_get_uids (m->queue)))
+ return;
+
+ send_uids = g_ptr_array_sized_new (uids->len);
+ for (i = 0, j = 0; i < uids->len; i++) {
+ CamelMessageInfo *info;
+
+ info = camel_folder_get_message_info (m->queue, uids->pdata[i]);
+ if (info) {
+ if ((camel_message_info_flags (info) & CAMEL_MESSAGE_DELETED) == 0)
+ send_uids->pdata[j++] = uids->pdata[i];
+ camel_folder_free_message_info (m->queue, info);
+ }
+ }
+
+ send_uids->len = j;
+ if (send_uids->len == 0) {
+ /* nothing to send */
+ camel_folder_free_uids (m->queue, uids);
+ g_ptr_array_free (send_uids, TRUE);
+ return;
+ }
+
+ camel_operation_push_message (cancellable, _("Sending message"));
+
+ /* NB: This code somewhat abuses the 'exception' stuff. Apart from
+ * fatal problems, it is also used as a mechanism to accumualte
+ * warning messages and present them back to the user. */
+
+ for (i = 0, j = 0; i < send_uids->len; i++) {
+ gint pc = (100 * i) / send_uids->len;
+
+ report_status (
+ m, CAMEL_FILTER_STATUS_START, pc,
+ _("Sending message %d of %d"), i+1,
+ send_uids->len);
+
+ camel_operation_progress (
+ cancellable, (i + 1) * 100 / send_uids->len);
+
+ mail_send_message (
+ m, m->queue, send_uids->pdata[i], m->transport,
+ m->driver, cancellable, &local_error);
+ if (local_error != NULL) {
+ if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ /* merge exceptions into one */
+ if (m->base.error != NULL) {
+ gchar *old_message;
+
+ old_message = g_strdup (
+ m->base.error->message);
+ g_clear_error (&m->base.error);
+ g_set_error (
+ &m->base.error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ "%s\n\n%s", old_message,
+ local_error->message);
+ g_free (old_message);
+
+ g_clear_error (&local_error);
+ } else {
+ g_propagate_error (&m->base.error, local_error);
+ local_error = NULL;
+ }
+
+ /* keep track of the number of failures */
+ j++;
+ } else {
+ /* transfer the USER_CANCEL error to the
+ * async op exception and then break */
+ g_propagate_error (&m->base.error, local_error);
+ local_error = NULL;
+ break;
+ }
+ }
+ }
+
+ j += (send_uids->len - i);
+
+ if (j > 0)
+ report_status (
+ m, CAMEL_FILTER_STATUS_END, 100,
+ _("Failed to send %d of %d messages"),
+ j, send_uids->len);
+ else if (g_error_matches (
+ m->base.error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ report_status (m, CAMEL_FILTER_STATUS_END, 100, _("Canceled."));
+ else
+ report_status (m, CAMEL_FILTER_STATUS_END, 100, _("Complete."));
+
+ if (m->driver) {
+ g_object_unref (m->driver);
+ m->driver = NULL;
+ }
+
+ camel_folder_free_uids (m->queue, uids);
+ g_ptr_array_free (send_uids, TRUE);
+
+ /* FIXME Not passing a GCancellable or GError here. */
+ if (j <= 0 && m->base.error == NULL)
+ camel_folder_synchronize_sync (m->queue, TRUE, NULL, NULL);
+
+ /* FIXME Not passing a GCancellable or GError here. */
+ if (sent_folder)
+ camel_folder_synchronize_sync (sent_folder, FALSE, NULL, NULL);
+
+ camel_operation_pop_message (cancellable);
+}
+
+static void
+send_queue_done (struct _send_queue_msg *m)
+{
+ if (m->done)
+ m->done (m->data);
+}
+
+static gchar *
+send_queue_desc (struct _send_queue_msg *m)
+{
+ return g_strdup (_("Sending message"));
+}
+
+static void
+send_queue_free (struct _send_queue_msg *m)
+{
+ if (m->session != NULL)
+ g_object_unref (m->session);
+ if (m->driver != NULL)
+ g_object_unref (m->driver);
+ if (m->transport != NULL)
+ g_object_unref (m->transport);
+ g_object_unref (m->queue);
+}
+
+static MailMsgInfo send_queue_info = {
+ sizeof (struct _send_queue_msg),
+ (MailMsgDescFunc) send_queue_desc,
+ (MailMsgExecFunc) send_queue_exec,
+ (MailMsgDoneFunc) send_queue_done,
+ (MailMsgFreeFunc) send_queue_free
+};
+
+/* same interface as fetch_mail, just 'cause i'm lazy today
+ * (and we need to run it from the same spot?) */
+void
+mail_send_queue (EMailSession *session,
+ CamelFolder *queue,
+ CamelTransport *transport,
+ const gchar *type,
+ GCancellable *cancellable,
+ CamelFilterGetFolderFunc get_folder,
+ gpointer get_data,
+ CamelFilterStatusFunc *status,
+ gpointer status_data,
+ void (*done)(gpointer data),
+ gpointer data)
+{
+ struct _send_queue_msg *m;
+
+ g_return_if_fail (E_IS_MAIL_SESSION (session));
+
+ m = mail_msg_new (&send_queue_info);
+ m->session = g_object_ref (session);
+ m->queue = g_object_ref (queue);
+ m->transport = g_object_ref (transport);
+ if (G_IS_CANCELLABLE (cancellable))
+ m->base.cancellable = cancellable;
+ m->status = status;
+ m->status_data = status_data;
+ m->done = done;
+ m->data = data;
+
+ m->driver = camel_session_get_filter_driver (
+ CAMEL_SESSION (session), type, NULL);
+ camel_filter_driver_set_folder_func (m->driver, get_folder, get_data);
+
+ mail_msg_unordered_push (m);
+}
+
+/* ** TRANSFER MESSAGES **************************************************** */
+
+struct _transfer_msg {
+ MailMsg base;
+
+ EMailSession *session;
+ CamelFolder *source;
+ GPtrArray *uids;
+ gboolean delete;
+ gchar *dest_uri;
+ guint32 dest_flags;
+
+ void (*done)(gboolean ok, gpointer data);
+ gpointer data;
+};
+
+static gchar *
+transfer_messages_desc (struct _transfer_msg *m)
+{
+ return g_strdup_printf (
+ m->delete ?
+ _("Moving messages to '%s'") :
+ _("Copying messages to '%s'"),
+ m->dest_uri);
+
+}
+
+static void
+transfer_messages_exec (struct _transfer_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *dest;
+
+ dest = e_mail_session_uri_to_folder_sync (
+ m->session, m->dest_uri, m->dest_flags,
+ cancellable, error);
+ if (dest == NULL)
+ return;
+
+ if (dest == m->source) {
+ g_object_unref (dest);
+ /* no-op */
+ return;
+ }
+
+ camel_folder_freeze (m->source);
+ camel_folder_freeze (dest);
+
+ camel_folder_transfer_messages_to_sync (
+ m->source, m->uids, dest, m->delete, NULL,
+ cancellable, error);
+
+ /* make sure all deleted messages are marked as seen */
+
+ if (m->delete) {
+ gint i;
+
+ for (i = 0; i < m->uids->len; i++)
+ camel_folder_set_message_flags (
+ m->source, m->uids->pdata[i],
+ CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN);
+ }
+
+ camel_folder_thaw (m->source);
+ camel_folder_thaw (dest);
+
+ /* FIXME Not passing a GCancellable or GError here. */
+ camel_folder_synchronize_sync (dest, FALSE, NULL, NULL);
+ g_object_unref (dest);
+}
+
+static void
+transfer_messages_done (struct _transfer_msg *m)
+{
+ if (m->done)
+ m->done (m->base.error == NULL, m->data);
+}
+
+static void
+transfer_messages_free (struct _transfer_msg *m)
+{
+ g_object_unref (m->session);
+ g_object_unref (m->source);
+ g_free (m->dest_uri);
+ em_utils_uids_free (m->uids);
+}
+
+static MailMsgInfo transfer_messages_info = {
+ sizeof (struct _transfer_msg),
+ (MailMsgDescFunc) transfer_messages_desc,
+ (MailMsgExecFunc) transfer_messages_exec,
+ (MailMsgDoneFunc) transfer_messages_done,
+ (MailMsgFreeFunc) transfer_messages_free
+};
+
+void
+mail_transfer_messages (EMailSession *session,
+ CamelFolder *source,
+ GPtrArray *uids,
+ gboolean delete_from_source,
+ const gchar *dest_uri,
+ guint32 dest_flags,
+ void (*done) (gboolean ok,
+ gpointer data),
+ gpointer data)
+{
+ struct _transfer_msg *m;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (source));
+ g_return_if_fail (uids != NULL);
+ g_return_if_fail (dest_uri != NULL);
+
+ m = mail_msg_new (&transfer_messages_info);
+ m->session = g_object_ref (session);
+ m->source = g_object_ref (source);
+ m->uids = uids;
+ m->delete = delete_from_source;
+ m->dest_uri = g_strdup (dest_uri);
+ m->dest_flags = dest_flags;
+ m->done = done;
+ m->data = data;
+
+ mail_msg_slow_ordered_push (m);
+}
+
+/* ** SYNC FOLDER ********************************************************* */
+
+struct _sync_folder_msg {
+ MailMsg base;
+
+ CamelFolder *folder;
+ void (*done) (CamelFolder *folder, gpointer data);
+ gpointer data;
+};
+
+static gchar *
+sync_folder_desc (struct _sync_folder_msg *m)
+{
+ return g_strdup_printf (_("Storing folder '%s'"),
+ camel_folder_get_full_name (m->folder));
+}
+
+static void
+sync_folder_exec (struct _sync_folder_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_folder_synchronize_sync (
+ m->folder, FALSE, cancellable, error);
+}
+
+static void
+sync_folder_done (struct _sync_folder_msg *m)
+{
+ if (m->done)
+ m->done (m->folder, m->data);
+}
+
+static void
+sync_folder_free (struct _sync_folder_msg *m)
+{
+ if (m->folder)
+ g_object_unref (m->folder);
+}
+
+static MailMsgInfo sync_folder_info = {
+ sizeof (struct _sync_folder_msg),
+ (MailMsgDescFunc) sync_folder_desc,
+ (MailMsgExecFunc) sync_folder_exec,
+ (MailMsgDoneFunc) sync_folder_done,
+ (MailMsgFreeFunc) sync_folder_free
+};
+
+void
+mail_sync_folder (CamelFolder *folder,
+ void (*done) (CamelFolder *folder,
+ gpointer data),
+ gpointer data)
+{
+ struct _sync_folder_msg *m;
+
+ m = mail_msg_new (&sync_folder_info);
+ m->folder = g_object_ref (folder);
+ m->data = data;
+ m->done = done;
+
+ mail_msg_slow_ordered_push (m);
+}
+
+/* ** SYNC STORE ********************************************************* */
+
+struct _sync_store_msg {
+ MailMsg base;
+
+ CamelStore *store;
+ gint expunge;
+ void (*done) (CamelStore *store, gpointer data);
+ gpointer data;
+};
+
+static gchar *
+sync_store_desc (struct _sync_store_msg *m)
+{
+ CamelURL *url;
+ gchar *uri, *res;
+
+ url = camel_service_new_camel_url (CAMEL_SERVICE (m->store));
+ uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+
+ res = g_strdup_printf (m->expunge
+ ?_("Expunging and storing account '%s'")
+ :_("Storing account '%s'"),
+ uri);
+ g_free (uri);
+
+ return res;
+}
+
+static void
+sync_store_exec (struct _sync_store_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_store_synchronize_sync (
+ m->store, m->expunge,
+ cancellable, error);
+}
+
+static void
+sync_store_done (struct _sync_store_msg *m)
+{
+ if (m->done)
+ m->done (m->store, m->data);
+}
+
+static void
+sync_store_free (struct _sync_store_msg *m)
+{
+ g_object_unref (m->store);
+}
+
+static MailMsgInfo sync_store_info = {
+ sizeof (struct _sync_store_msg),
+ (MailMsgDescFunc) sync_store_desc,
+ (MailMsgExecFunc) sync_store_exec,
+ (MailMsgDoneFunc) sync_store_done,
+ (MailMsgFreeFunc) sync_store_free
+};
+
+void
+mail_sync_store (CamelStore *store,
+ gint expunge,
+ void (*done) (CamelStore *store,
+ gpointer data),
+ gpointer data)
+{
+ struct _sync_store_msg *m;
+
+ m = mail_msg_new (&sync_store_info);
+ m->store = g_object_ref (store);
+ m->expunge = expunge;
+ m->data = data;
+ m->done = done;
+
+ mail_msg_slow_ordered_push (m);
+}
+
+/* ******************************************************************************** */
+
+static gchar *
+refresh_folder_desc (struct _sync_folder_msg *m)
+{
+ return g_strdup_printf (
+ _("Refreshing folder '%s'"),
+ camel_folder_get_full_name (m->folder));
+}
+
+static void
+refresh_folder_exec (struct _sync_folder_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_folder_refresh_info_sync (
+ m->folder, cancellable, error);
+}
+
+/* we just use the sync stuff where we can, since it would be the same */
+static MailMsgInfo refresh_folder_info = {
+ sizeof (struct _sync_folder_msg),
+ (MailMsgDescFunc) refresh_folder_desc,
+ (MailMsgExecFunc) refresh_folder_exec,
+ (MailMsgDoneFunc) sync_folder_done,
+ (MailMsgFreeFunc) sync_folder_free
+};
+
+void
+mail_refresh_folder (CamelFolder *folder,
+ void (*done) (CamelFolder *folder,
+ gpointer data),
+ gpointer data)
+{
+ struct _sync_folder_msg *m;
+
+ m = mail_msg_new (&refresh_folder_info);
+ m->folder = g_object_ref (folder);
+ m->data = data;
+ m->done = done;
+
+ mail_msg_slow_ordered_push (m);
+}
+
+/* ******************************************************************************** */
+
+static gboolean
+folder_is_from_source_uid (CamelFolder *folder,
+ const gchar *source_uid)
+{
+ CamelStore *store;
+ const gchar *uid;
+
+ store = camel_folder_get_parent_store (folder);
+ uid = camel_service_get_uid (CAMEL_SERVICE (store));
+
+ return (g_strcmp0 (uid, source_uid) == 0);
+}
+
+/* This is because pop3 accounts are hidden under local Inbox,
+ * thus whenever an expunge is done on a local trash or Inbox,
+ * then also all active pop3 accounts should be expunged. */
+static gboolean
+expunge_pop3_stores (CamelFolder *expunging,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GHashTable *expunging_uids;
+ CamelStore *parent_store;
+ CamelService *service;
+ CamelSession *session;
+ GPtrArray *uids;
+ EAccount *account;
+ EIterator *iter;
+ gboolean success = TRUE;
+ guint ii;
+
+ parent_store = camel_folder_get_parent_store (expunging);
+
+ service = CAMEL_SERVICE (parent_store);
+ session = camel_service_get_session (service);
+
+ uids = camel_folder_get_uids (expunging);
+
+ if (uids == NULL)
+ return TRUE;
+
+ expunging_uids = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ CamelMessageInfo *info;
+ CamelMessageFlags flags = 0;
+ CamelMimeMessage *message;
+ const gchar *pop3_uid;
+ const gchar *source_uid;
+
+ info = camel_folder_get_message_info (
+ expunging, uids->pdata[ii]);
+
+ if (info != NULL) {
+ flags = camel_message_info_flags (info);
+ camel_folder_free_message_info (expunging, info);
+ }
+
+ /* Only interested in deleted messages. */
+ if ((flags & CAMEL_MESSAGE_DELETED) == 0)
+ continue;
+
+ /* because the UID in the local store doesn't
+ * match with the UID in the pop3 store */
+ message = camel_folder_get_message_sync (
+ expunging, uids->pdata[ii], cancellable, NULL);
+
+ if (message == NULL)
+ continue;
+
+ pop3_uid = camel_medium_get_header (
+ CAMEL_MEDIUM (message), "X-Evolution-POP3-UID");
+ source_uid = camel_mime_message_get_source (message);
+
+ if (pop3_uid != NULL)
+ g_hash_table_insert (
+ expunging_uids,
+ g_strstrip (g_strdup (pop3_uid)),
+ g_strstrip (g_strdup (source_uid)));
+
+ g_object_unref (message);
+ }
+
+ camel_folder_free_uids (expunging, uids);
+ uids = NULL;
+
+ if (g_hash_table_size (expunging_uids) == 0) {
+ g_hash_table_destroy (expunging_uids);
+ return TRUE;
+ }
+
+ for (iter = e_list_get_iterator ((EList *) e_get_account_list ());
+ e_iterator_is_valid (iter); e_iterator_next (iter)) {
+ account = (EAccount *) e_iterator_get (iter);
+
+ if (account->enabled &&
+ account->source && account->source->url &&
+ g_str_has_prefix (account->source->url, "pop://")) {
+ CamelFolder *folder;
+ CamelService *service;
+ CamelSettings *settings;
+ gboolean any_found = FALSE;
+ gboolean delete_expunged = FALSE;
+ gboolean keep_on_server = FALSE;
+
+ service = camel_session_get_service (session, account->uid);
+
+ if (!CAMEL_IS_STORE (service))
+ continue;
+
+ settings = camel_service_get_settings (service);
+ if (!settings)
+ continue;
+
+ g_object_get (
+ settings,
+ "delete-expunged", &delete_expunged,
+ "keep-on-server", &keep_on_server,
+ NULL);
+
+ if (!keep_on_server || !delete_expunged)
+ continue;
+
+ folder = e_mail_session_get_inbox_sync (
+ E_MAIL_SESSION (session),
+ account->uid, cancellable, error);
+
+ /* Abort the loop on error. */
+ if (folder == NULL) {
+ success = FALSE;
+ break;
+ }
+
+ uids = camel_folder_get_uids (folder);
+ if (uids) {
+ for (ii = 0; ii < uids->len; ii++) {
+ /* ensure the ID is from this account,
+ * as it's generated by evolution */
+ const gchar *source_uid;
+
+ source_uid = g_hash_table_lookup (
+ expunging_uids, uids->pdata[ii]);
+ if (folder_is_from_source_uid (folder, source_uid)) {
+ any_found = TRUE;
+ camel_folder_delete_message (folder, uids->pdata[ii]);
+ }
+ }
+ camel_folder_free_uids (folder, uids);
+ }
+
+ if (any_found)
+ success = camel_folder_synchronize_sync (folder, TRUE, cancellable, error);
+
+ g_object_unref (folder);
+
+ /* Abort the loop on error. */
+ if (!success)
+ break;
+ }
+ }
+
+ if (iter)
+ g_object_unref (iter);
+
+ g_hash_table_destroy (expunging_uids);
+
+ return success;
+}
+
+static gchar *
+expunge_folder_desc (struct _sync_folder_msg *m)
+{
+ return g_strdup_printf (
+ _("Expunging folder '%s'"),
+ camel_folder_get_full_name (m->folder));
+}
+
+static void
+expunge_folder_exec (struct _sync_folder_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *local_inbox;
+ CamelStore *parent_store;
+ CamelService *service;
+ CamelSession *session;
+ gboolean is_local_inbox_or_trash;
+ gboolean store_is_local;
+ gboolean success = TRUE;
+ const gchar *uid;
+
+ parent_store = camel_folder_get_parent_store (m->folder);
+
+ service = CAMEL_SERVICE (parent_store);
+ session = camel_service_get_session (service);
+
+ uid = camel_service_get_uid (service);
+ store_is_local = (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0);
+
+ local_inbox =
+ e_mail_session_get_local_folder (
+ E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_INBOX);
+ is_local_inbox_or_trash = (m->folder == local_inbox);
+
+ if (store_is_local && !is_local_inbox_or_trash) {
+ CamelFolder *trash;
+
+ trash = camel_store_get_trash_folder_sync (
+ parent_store, cancellable, error);
+
+ if (trash == NULL)
+ return;
+
+ is_local_inbox_or_trash = (m->folder == trash);
+
+ g_object_unref (trash);
+ }
+
+ /* do this before expunge, to know which messages will be expunged */
+ if (is_local_inbox_or_trash)
+ success = expunge_pop3_stores (m->folder, cancellable, error);
+
+ if (success)
+ camel_folder_expunge_sync (m->folder, cancellable, error);
+}
+
+/* we just use the sync stuff where we can, since it would be the same */
+static MailMsgInfo expunge_folder_info = {
+ sizeof (struct _sync_folder_msg),
+ (MailMsgDescFunc) expunge_folder_desc,
+ (MailMsgExecFunc) expunge_folder_exec,
+ (MailMsgDoneFunc) sync_folder_done,
+ (MailMsgFreeFunc) sync_folder_free
+};
+
+void
+mail_expunge_folder (CamelFolder *folder)
+{
+ struct _sync_folder_msg *m;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ m = mail_msg_new (&expunge_folder_info);
+ m->folder = g_object_ref (folder);
+
+ mail_msg_slow_ordered_push (m);
+}
+
+/* ******************************************************************************** */
+
+struct _empty_trash_msg {
+ MailMsg base;
+
+ CamelStore *store;
+};
+
+static gchar *
+empty_trash_desc (struct _empty_trash_msg *m)
+{
+ CamelService *service;
+ const gchar *display_name;
+
+ service = CAMEL_SERVICE (m->store);
+ display_name = camel_service_get_display_name (service);
+
+ return g_strdup_printf (
+ _("Emptying trash in '%s'"), display_name);
+}
+
+static void
+empty_trash_exec (struct _empty_trash_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+ CamelFolder *trash;
+ const gchar *uid;
+ gboolean success = TRUE;
+
+ service = CAMEL_SERVICE (m->store);
+ uid = camel_service_get_uid (service);
+
+ if (!em_utils_connect_service_sync (service, cancellable, error))
+ return;
+
+ trash = camel_store_get_trash_folder_sync (
+ m->store, cancellable, error);
+
+ if (trash == NULL)
+ return;
+
+ /* do this before expunge, to know which messages will be expunged */
+ if (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0)
+ success = expunge_pop3_stores (trash, cancellable, error);
+
+ if (success)
+ camel_folder_expunge_sync (trash, cancellable, error);
+
+ g_object_unref (trash);
+}
+
+static void
+empty_trash_done (struct _empty_trash_msg *m)
+{
+}
+
+static void
+empty_trash_free (struct _empty_trash_msg *m)
+{
+ if (m->store)
+ g_object_unref (m->store);
+}
+
+static MailMsgInfo empty_trash_info = {
+ sizeof (struct _empty_trash_msg),
+ (MailMsgDescFunc) empty_trash_desc,
+ (MailMsgExecFunc) empty_trash_exec,
+ (MailMsgDoneFunc) empty_trash_done,
+ (MailMsgFreeFunc) empty_trash_free
+};
+
+void
+mail_empty_trash (CamelStore *store)
+{
+ struct _empty_trash_msg *m;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ m = mail_msg_new (&empty_trash_info);
+ m->store = g_object_ref (store);
+
+ mail_msg_slow_ordered_push (m);
+}
+
+/* ** Execute Shell Command ************************************************ */
+
+void
+mail_execute_shell_command (CamelFilterDriver *driver,
+ gint argc,
+ gchar **argv,
+ gpointer data)
+{
+ if (argc <= 0)
+ return;
+
+ g_spawn_async (NULL, argv, NULL, 0, NULL, data, NULL, NULL);
+}
+
+/* ------------------------------------------------------------------------- */
+
+struct _disconnect_msg {
+ MailMsg base;
+
+ CamelStore *store;
+};
+
+static gchar *
+disconnect_service_desc (struct _disconnect_msg *m)
+{
+ gchar *name, *res;
+
+ name = camel_service_get_name (CAMEL_SERVICE (m->store), TRUE);
+ res = g_strdup_printf (_("Disconnecting %s"), name ? name : "");
+ g_free (name);
+
+ return res;
+}
+
+static void
+disconnect_service_exec (struct _disconnect_msg *m,
+ GCancellable *cancellable,
+ GError **error)
+{
+ em_utils_disconnect_service_sync (
+ CAMEL_SERVICE (m->store), TRUE, cancellable, error);
+}
+
+static void
+disconnect_service_free (struct _disconnect_msg *m)
+{
+ g_object_unref (m->store);
+}
+
+static MailMsgInfo disconnect_service_info = {
+ sizeof (struct _disconnect_msg),
+ (MailMsgDescFunc) disconnect_service_desc,
+ (MailMsgExecFunc) disconnect_service_exec,
+ (MailMsgDoneFunc) NULL,
+ (MailMsgFreeFunc) disconnect_service_free
+};
+
+gint
+mail_disconnect_store (CamelStore *store)
+{
+ struct _disconnect_msg *m;
+ gint id;
+
+ g_return_val_if_fail (store != NULL, -1);
+
+ m = mail_msg_new (&disconnect_service_info);
+ m->store = g_object_ref (store);
+
+ id = m->base.seq;
+ mail_msg_unordered_push (m);
+
+ return id;
+}
diff --git a/libemail-engine/mail-ops.h b/libemail-engine/mail-ops.h
new file mode 100644
index 0000000000..236dd2325f
--- /dev/null
+++ b/libemail-engine/mail-ops.h
@@ -0,0 +1,98 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Peter Williams <peterw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef MAIL_OPS_H
+#define MAIL_OPS_H
+
+G_BEGIN_DECLS
+
+#include <camel/camel.h>
+#include <libemail-utils/mail-mt.h>
+#include <libemail-engine/e-mail-session.h>
+
+void mail_transfer_messages (EMailSession *session,
+ CamelFolder *source,
+ GPtrArray *uids,
+ gboolean delete_from_source,
+ const gchar *dest_uri,
+ guint32 dest_flags,
+ void (*done) (gboolean ok, gpointer data),
+ gpointer data);
+
+void mail_sync_folder (CamelFolder *folder,
+ void (*done) (CamelFolder *folder, gpointer data), gpointer data);
+
+void mail_sync_store (CamelStore *store, gint expunge,
+ void (*done) (CamelStore *store, gpointer data), gpointer data);
+
+void mail_refresh_folder (CamelFolder *folder,
+ void (*done) (CamelFolder *folder, gpointer data),
+ gpointer data);
+
+void mail_expunge_folder (CamelFolder *folder);
+void mail_empty_trash (CamelStore *store);
+
+/* transfer (copy/move) a folder */
+void mail_xfer_folder (const gchar *src_uri, const gchar *dest_uri, gboolean remove_source,
+ void (*done) (gchar *src_uri, gchar *dest_uri, gboolean remove_source,
+ CamelFolder *folder, gpointer data),
+ gpointer data);
+
+/* yeah so this is messy, but it does a lot, maybe i can consolidate all user_data's to be the one */
+void mail_send_queue (EMailSession *session,
+ CamelFolder *queue,
+ CamelTransport *transport,
+ const gchar *type,
+ GCancellable *cancellable,
+ CamelFilterGetFolderFunc get_folder,
+ gpointer get_data,
+ CamelFilterStatusFunc *status,
+ gpointer status_data,
+ void (*done)(gpointer data),
+ gpointer data);
+
+void mail_fetch_mail (CamelStore *store,
+ gint keep,
+ const gchar *type,
+ GCancellable *cancellable,
+ CamelFilterGetFolderFunc get_folder,
+ gpointer get_data,
+ CamelFilterStatusFunc *status,
+ gpointer status_data,
+ void (*done)(gpointer data),
+ gpointer data);
+
+void mail_filter_folder (EMailSession *session,
+ CamelFolder *source_folder,
+ GPtrArray *uids,
+ const gchar *type,
+ gboolean notify);
+
+/* filter driver execute shell command async callback */
+void mail_execute_shell_command (CamelFilterDriver *driver, gint argc, gchar **argv, gpointer data);
+
+gint mail_disconnect_store (CamelStore *store);
+
+G_END_DECLS
+
+#endif /* MAIL_OPS_H */
diff --git a/libemail-engine/mail-tools.c b/libemail-engine/mail-tools.c
new file mode 100644
index 0000000000..82b2146b92
--- /dev/null
+++ b/libemail-engine/mail-tools.c
@@ -0,0 +1,244 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ * Peter Williams <peterw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <glib/gi18n.h>
+
+#include "e-mail-session.h"
+#include "mail-folder-cache.h"
+#include "mail-tools.h"
+
+/* **************************************** */
+
+#ifndef G_OS_WIN32
+
+static gchar *
+mail_tool_get_local_movemail_path (CamelStore *store,
+ GError **error)
+{
+ const gchar *uid;
+ guchar *safe_uid, *c;
+ const gchar *data_dir;
+ gchar *path, *full;
+ struct stat st;
+
+ uid = camel_service_get_uid (CAMEL_SERVICE (store));
+ safe_uid = (guchar *) g_strdup ((const gchar *) uid);
+ for (c = safe_uid; *c; c++)
+ if (strchr("/:;=|%&#!*^()\\, ", *c) || !isprint((gint) *c))
+ *c = '_';
+
+ data_dir = mail_session_get_data_dir ();
+ path = g_build_filename (data_dir, "spool", NULL);
+
+ if (g_stat (path, &st) == -1 && g_mkdir_with_parents (path, 0700) == -1) {
+ g_set_error (
+ error, G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Could not create spool directory '%s': %s"),
+ path, g_strerror (errno));
+ g_free (path);
+ return NULL;
+ }
+
+ full = g_strdup_printf("%s/movemail.%s", path, safe_uid);
+ g_free (path);
+ g_free (safe_uid);
+
+ return full;
+}
+
+#endif
+
+gchar *
+mail_tool_do_movemail (CamelStore *store,
+ GError **error)
+{
+#ifndef G_OS_WIN32
+ CamelService *service;
+ CamelProvider *provider;
+ CamelSettings *settings;
+ const gchar *src_path;
+ gchar *dest_path;
+ struct stat sb;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+
+ service = CAMEL_SERVICE (store);
+ provider = camel_service_get_provider (service);
+ settings = camel_service_get_settings (service);
+
+ g_return_val_if_fail (provider != NULL, NULL);
+
+ if (g_strcmp0 (provider->protocol, "mbox") != 0) {
+ /* This is really only an internal error anyway */
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("Trying to movemail a non-mbox source '%s'"),
+ camel_service_get_uid (CAMEL_SERVICE (store)));
+ return NULL;
+ }
+
+ src_path = camel_local_settings_get_path (
+ CAMEL_LOCAL_SETTINGS (settings));
+
+ /* Set up our destination. */
+ dest_path = mail_tool_get_local_movemail_path (store, error);
+ if (dest_path == NULL)
+ return NULL;
+
+ /* Movemail from source to dest_path */
+ success = camel_movemail (src_path, dest_path, error) != -1;
+
+ if (g_stat (dest_path, &sb) < 0 || sb.st_size == 0) {
+ g_unlink (dest_path); /* Clean up the movemail.foo file. */
+ g_free (dest_path);
+ return NULL;
+ }
+
+ if (!success) {
+ g_free (dest_path);
+ return NULL;
+ }
+
+ return dest_path;
+#else
+ /* Unclear yet whether camel-movemail etc makes any sense on
+ * Win32, at least it is not ported yet.
+ */
+ g_warning("%s: Not implemented", __FUNCTION__);
+ return NULL;
+#endif
+}
+
+gchar *
+mail_tool_generate_forward_subject (CamelMimeMessage *msg)
+{
+ const gchar *subject;
+ gchar *fwd_subj;
+ const gint max_subject_length = 1024;
+
+ subject = camel_mime_message_get_subject (msg);
+
+ if (subject && *subject) {
+ /* Truncate insanely long subjects */
+ if (strlen (subject) < max_subject_length) {
+ fwd_subj = g_strdup_printf ("[Fwd: %s]", subject);
+ } else {
+ /* We can't use %.*s because it depends on the
+ * locale being C/POSIX or UTF-8 to work correctly
+ * in glibc. */
+ fwd_subj = g_malloc (max_subject_length + 11);
+ memcpy (fwd_subj, "[Fwd: ", 6);
+ memcpy (fwd_subj + 6, subject, max_subject_length);
+ memcpy (fwd_subj + 6 + max_subject_length, "...]", 5);
+ }
+ } else {
+ const CamelInternetAddress *from;
+ gchar *fromstr;
+
+ from = camel_mime_message_get_from (msg);
+ if (from) {
+ fromstr = camel_address_format (CAMEL_ADDRESS (from));
+ fwd_subj = g_strdup_printf ("[Fwd: %s]", fromstr);
+ g_free (fromstr);
+ } else
+ fwd_subj = g_strdup ("[Fwd: No Subject]");
+ }
+
+ return fwd_subj;
+}
+
+struct _camel_header_raw *
+mail_tool_remove_xevolution_headers (CamelMimeMessage *message)
+{
+ struct _camel_header_raw *scan, *list = NULL;
+
+ for (scan = ((CamelMimePart *) message)->headers; scan; scan = scan->next)
+ if (!strncmp(scan->name, "X-Evolution", 11))
+ camel_header_raw_append (&list, scan->name, scan->value, scan->offset);
+
+ for (scan = list; scan; scan = scan->next)
+ camel_medium_remove_header ((CamelMedium *) message, scan->name);
+
+ return list;
+}
+
+void
+mail_tool_restore_xevolution_headers (CamelMimeMessage *message,
+ struct _camel_header_raw *xev)
+{
+ CamelMedium *medium;
+
+ medium = CAMEL_MEDIUM (message);
+
+ for (; xev; xev = xev->next)
+ camel_medium_add_header (medium, xev->name, xev->value);
+}
+
+CamelMimePart *
+mail_tool_make_message_attachment (CamelMimeMessage *message)
+{
+ CamelMimePart *part;
+ const gchar *subject;
+ struct _camel_header_raw *xev;
+ gchar *desc;
+
+ subject = camel_mime_message_get_subject (message);
+ if (subject)
+ desc = g_strdup_printf (_("Forwarded message - %s"), subject);
+ else
+ desc = g_strdup (_("Forwarded message"));
+
+ /* rip off the X-Evolution headers */
+ xev = mail_tool_remove_xevolution_headers (message);
+ camel_header_raw_clear (&xev);
+
+ /* remove Bcc headers */
+ camel_medium_remove_header (CAMEL_MEDIUM (message), "Bcc");
+
+ part = camel_mime_part_new ();
+ camel_mime_part_set_disposition (part, "inline");
+ camel_mime_part_set_description (part, desc);
+ camel_medium_set_content (
+ CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (message));
+ camel_mime_part_set_content_type (part, "message/rfc822");
+ g_free (desc);
+
+ return part;
+}
diff --git a/libemail-engine/mail-tools.h b/libemail-engine/mail-tools.h
new file mode 100644
index 0000000000..94b19c0d12
--- /dev/null
+++ b/libemail-engine/mail-tools.h
@@ -0,0 +1,41 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Peter Williams <peterw@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef MAIL_TOOLS_H
+#define MAIL_TOOLS_H
+
+#include <camel/camel.h>
+
+/* Does a camel_movemail into the local movemail folder
+ * and returns the path to the new movemail folder that was created. which shoudl be freed later */
+gchar *mail_tool_do_movemail (CamelStore *store, GError **error);
+
+struct _camel_header_raw *mail_tool_remove_xevolution_headers (CamelMimeMessage *message);
+void mail_tool_restore_xevolution_headers (CamelMimeMessage *message, struct _camel_header_raw *);
+
+/* Generates the subject for a message forwarding @msg */
+gchar *mail_tool_generate_forward_subject (CamelMimeMessage *msg);
+
+/* Make a message into an attachment */
+CamelMimePart *mail_tool_make_message_attachment (CamelMimeMessage *message);
+
+#endif