diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2012-01-18 00:07:19 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2012-01-19 12:48:47 +0800 |
commit | 61ae36351b24cc676f60483d576706bf827f2987 (patch) | |
tree | c55d9e000efd47fa14865fad2defa79b5ed61ffd /libemail-engine | |
parent | 37644b9d257369c5c158121ca4807cafbe844595 (diff) | |
download | gsoc2013-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')
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 |