aboutsummaryrefslogtreecommitdiffstats
path: root/camel/providers
diff options
context:
space:
mode:
authorNot Zed <NotZed@Ximian.com>2001-06-12 22:29:28 +0800
committerMichael Zucci <zucchi@src.gnome.org>2001-06-12 22:29:28 +0800
commitf14e85f771600855dedae6d29301d656ea86185b (patch)
tree2a2dd4e515e0398c3f737c800c966f55acae6a38 /camel/providers
parent8131304fd121b4c3b4df98a17b3a376c1bffaada (diff)
downloadgsoc2013-evolution-f14e85f771600855dedae6d29301d656ea86185b.tar.gz
gsoc2013-evolution-f14e85f771600855dedae6d29301d656ea86185b.tar.zst
gsoc2013-evolution-f14e85f771600855dedae6d29301d656ea86185b.zip
A new provider, for spool mailboxes. Mostly a cut and paste of the mbox
2001-06-12 Not Zed <NotZed@Ximian.com> * providers/local/camel-spool-*.[ch]: A new provider, for spool mailboxes. Mostly a cut and paste of the mbox code, but not a subclass CamelLocal*. Not tested a lot, doesn't lock yet, use with extreme caution. * tests/lib/folders.c (test_folder_message_ops): Added spool arg, spool folders can't be deleted, renamed, etc. (test_folder_basic): Same. * tests/folder/test2.c (main): Added checks for spool type. * tests/[message|stream|folder|misc|smime]/Makefile.am (LDADD): Added db3 flags, so make check compiles, doesn't run though. 2001-05-24 Not Zed <NotZed@Ximian.com> * providers/local/camel-local-provider.c (camel_provider_module_init): Added spool provider. svn path=/trunk/; revision=10198
Diffstat (limited to 'camel/providers')
-rw-r--r--camel/providers/local/Makefile.am10
-rw-r--r--camel/providers/local/camel-local-private.h14
-rw-r--r--camel/providers/local/camel-local-provider.c15
-rw-r--r--camel/providers/local/camel-spool-folder.c609
-rw-r--r--camel/providers/local/camel-spool-folder.h101
-rw-r--r--camel/providers/local/camel-spool-store.c194
-rw-r--r--camel/providers/local/camel-spool-store.h67
-rw-r--r--camel/providers/local/camel-spool-summary.c1273
-rw-r--r--camel/providers/local/camel-spool-summary.h96
-rw-r--r--camel/providers/local/libcamellocal.urls1
10 files changed, 2378 insertions, 2 deletions
diff --git a/camel/providers/local/Makefile.am b/camel/providers/local/Makefile.am
index 9c6b353186..d49cb13a60 100644
--- a/camel/providers/local/Makefile.am
+++ b/camel/providers/local/Makefile.am
@@ -32,7 +32,10 @@ libcamellocal_la_SOURCES = \
camel-mbox-summary.c \
camel-maildir-folder.c \
camel-maildir-store.c \
- camel-maildir-summary.c
+ camel-maildir-summary.c \
+ camel-spool-folder.c \
+ camel-spool-store.c \
+ camel-spool-summary.c
libcamellocalinclude_HEADERS = \
camel-local-folder.h \
@@ -46,7 +49,10 @@ libcamellocalinclude_HEADERS = \
camel-mbox-summary.h \
camel-maildir-folder.h \
camel-maildir-store.h \
- camel-maildir-summary.h
+ camel-maildir-summary.h \
+ camel-spool-folder.h \
+ camel-spool-store.h \
+ camel-spool-summary.h
noinst_HEADERS = \
camel-local-private.h
diff --git a/camel/providers/local/camel-local-private.h b/camel/providers/local/camel-local-private.h
index 1d1a89ea27..5008f7bb23 100644
--- a/camel/providers/local/camel-local-private.h
+++ b/camel/providers/local/camel-local-private.h
@@ -52,6 +52,20 @@ struct _CamelLocalFolderPrivate {
#define CAMEL_LOCAL_FOLDER_UNLOCK(f, l)
#endif
+struct _CamelSpoolFolderPrivate {
+#ifdef ENABLE_THREADS
+ GMutex *search_lock; /* for locking the search object */
+#endif
+};
+
+#ifdef ENABLE_THREADS
+#define CAMEL_SPOOL_FOLDER_LOCK(f, l) (g_mutex_lock(((CamelSpoolFolder *)f)->priv->l))
+#define CAMEL_SPOOL_FOLDER_UNLOCK(f, l) (g_mutex_unlock(((CamelSpoolFolder *)f)->priv->l))
+#else
+#define CAMEL_SPOOL_FOLDER_LOCK(f, l)
+#define CAMEL_SPOOL_FOLDER_UNLOCK(f, l)
+#endif
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/camel/providers/local/camel-local-provider.c b/camel/providers/local/camel-local-provider.c
index a63ff84eb0..078945a764 100644
--- a/camel/providers/local/camel-local-provider.c
+++ b/camel/providers/local/camel-local-provider.c
@@ -33,6 +33,7 @@
#include "camel-mh-store.h"
#include "camel-mbox-store.h"
#include "camel-maildir-store.h"
+#include "camel-spool-store.h"
static CamelProvider mh_provider = {
"mh",
@@ -64,6 +65,16 @@ static CamelProvider maildir_provider = {
/* ... */
};
+static CamelProvider spool_provider = {
+ "spool",
+ N_("Unix mbox spool-format mail files"),
+ N_("For storing local mail in standard Unix spool directories"),
+ "mail",
+ CAMEL_PROVIDER_IS_SOURCE | CAMEL_PROVIDER_IS_STORAGE,
+ CAMEL_URL_NEED_PATH | CAMEL_URL_PATH_IS_ABSOLUTE,
+ /* ... */
+};
+
void camel_provider_module_init(CamelSession * session)
{
mh_provider.object_types[CAMEL_PROVIDER_STORE] = camel_mh_store_get_type();
@@ -77,4 +88,8 @@ void camel_provider_module_init(CamelSession * session)
maildir_provider.object_types[CAMEL_PROVIDER_STORE] = camel_maildir_store_get_type();
maildir_provider.service_cache = g_hash_table_new(camel_url_hash, camel_url_equal);
camel_session_register_provider(session, &maildir_provider);
+
+ spool_provider.object_types[CAMEL_PROVIDER_STORE] = camel_spool_store_get_type();
+ spool_provider.service_cache = g_hash_table_new(camel_url_hash, camel_url_equal);
+ camel_session_register_provider(session, &spool_provider);
}
diff --git a/camel/providers/local/camel-spool-folder.c b/camel/providers/local/camel-spool-folder.c
new file mode 100644
index 0000000000..0df6fae026
--- /dev/null
+++ b/camel/providers/local/camel-spool-folder.c
@@ -0,0 +1,609 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2001 Ximian Inc (http://www.ximian.com/)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "camel-spool-folder.h"
+#include "camel-spool-store.h"
+#include "string-utils.h"
+#include "camel-stream-fs.h"
+#include "camel-spool-summary.h"
+#include "camel-data-wrapper.h"
+#include "camel-mime-message.h"
+#include "camel-stream-filter.h"
+#include "camel-mime-filter-from.h"
+#include "camel-exception.h"
+
+#include "camel-local-private.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+static CamelFolderClass *parent_class = NULL;
+
+/* Returns the class for a CamelSpoolFolder */
+#define CSPOOLF_CLASS(so) CAMEL_SPOOL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CSPOOLS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static int spool_lock(CamelSpoolFolder *lf, CamelLockType type, CamelException *ex);
+static void spool_unlock(CamelSpoolFolder *lf);
+
+static void spool_sync(CamelFolder *folder, gboolean expunge, CamelException *ex);
+static void spool_expunge(CamelFolder *folder, CamelException *ex);
+
+static GPtrArray *spool_search_by_expression(CamelFolder *folder, const char *expression, CamelException *ex);
+static void spool_search_free(CamelFolder *folder, GPtrArray * result);
+
+static void spool_append_message(CamelFolder *folder, CamelMimeMessage * message, const CamelMessageInfo * info, CamelException *ex);
+static CamelMimeMessage *spool_get_message(CamelFolder *folder, const gchar * uid, CamelException *ex);
+static void spool_set_message_user_flag(CamelFolder *folder, const char *uid, const char *name, gboolean value);
+static void spool_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value);
+
+static void spool_finalize(CamelObject * object);
+
+static void
+camel_spool_folder_class_init(CamelSpoolFolderClass * camel_spool_folder_class)
+{
+ CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_spool_folder_class);
+
+ parent_class = CAMEL_FOLDER_CLASS(camel_type_get_global_classfuncs(camel_folder_get_type()));
+
+ /* virtual method definition */
+
+ /* virtual method overload */
+ camel_folder_class->sync = spool_sync;
+ camel_folder_class->expunge = spool_expunge;
+
+ camel_folder_class->search_by_expression = spool_search_by_expression;
+ camel_folder_class->search_free = spool_search_free;
+
+ /* virtual method overload */
+ camel_folder_class->append_message = spool_append_message;
+ camel_folder_class->get_message = spool_get_message;
+
+ camel_folder_class->set_message_user_flag = spool_set_message_user_flag;
+ camel_folder_class->set_message_user_tag = spool_set_message_user_tag;
+
+ camel_spool_folder_class->lock = spool_lock;
+ camel_spool_folder_class->unlock = spool_unlock;
+}
+
+static void
+spool_init(gpointer object, gpointer klass)
+{
+ CamelFolder *folder = object;
+ CamelSpoolFolder *spool_folder = object;
+
+ folder->has_summary_capability = TRUE;
+ folder->has_search_capability = TRUE;
+
+ folder->permanent_flags = CAMEL_MESSAGE_ANSWERED |
+ CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_DRAFT |
+ CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_USER;
+
+ folder->summary = NULL;
+ spool_folder->search = NULL;
+
+ spool_folder->priv = g_malloc0(sizeof(*spool_folder->priv));
+#ifdef ENABLE_THREADS
+ spool_folder->priv->search_lock = g_mutex_new();
+#endif
+}
+
+static void
+spool_finalize(CamelObject * object)
+{
+ CamelSpoolFolder *spool_folder = CAMEL_SPOOL_FOLDER(object);
+ CamelFolder *folder = (CamelFolder *)object;
+
+ if (folder->summary) {
+ camel_spool_summary_sync((CamelSpoolSummary *)folder->summary, FALSE, spool_folder->changes, NULL);
+ camel_object_unref((CamelObject *)folder->summary);
+ folder->summary = NULL;
+ }
+
+ if (spool_folder->search) {
+ camel_object_unref((CamelObject *)spool_folder->search);
+ }
+
+ while (spool_folder->locked> 0)
+ camel_spool_folder_unlock(spool_folder);
+
+ g_free(spool_folder->base_path);
+ g_free(spool_folder->folder_path);
+
+ camel_folder_change_info_free(spool_folder->changes);
+
+#ifdef ENABLE_THREADS
+ g_mutex_free(spool_folder->priv->search_lock);
+#endif
+ g_free(spool_folder->priv);
+}
+
+CamelType camel_spool_folder_get_type(void)
+{
+ static CamelType camel_spool_folder_type = CAMEL_INVALID_TYPE;
+
+ if (camel_spool_folder_type == CAMEL_INVALID_TYPE) {
+ camel_spool_folder_type = camel_type_register(CAMEL_FOLDER_TYPE, "CamelSpoolFolder",
+ sizeof(CamelSpoolFolder),
+ sizeof(CamelSpoolFolderClass),
+ (CamelObjectClassInitFunc) camel_spool_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) spool_init,
+ (CamelObjectFinalizeFunc) spool_finalize);
+ }
+
+ return camel_spool_folder_type;
+}
+
+CamelSpoolFolder *
+camel_spool_folder_construct(CamelSpoolFolder *lf, CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex)
+{
+ CamelFolderInfo *fi;
+ CamelFolder *folder;
+ const char *root_dir_path, *name;
+
+ folder = (CamelFolder *)lf;
+
+ name = strrchr(full_name, '/');
+ if (name)
+ name++;
+ else
+ name = full_name;
+
+ camel_folder_construct(folder, parent_store, full_name, name);
+
+ root_dir_path = camel_spool_store_get_toplevel_dir(CAMEL_SPOOL_STORE(folder->parent_store));
+
+ lf->base_path = g_strdup(root_dir_path);
+ lf->folder_path = g_strdup_printf("%s/%s", root_dir_path, full_name);
+
+ lf->changes = camel_folder_change_info_new();
+ lf->flags = flags;
+
+ folder->summary = (CamelFolderSummary *)camel_spool_summary_new(lf->folder_path);
+ if (camel_spool_folder_lock(lf, CAMEL_LOCK_WRITE, ex) != -1) {
+ camel_spool_summary_check((CamelSpoolSummary *)folder->summary, NULL, ex);
+ camel_spool_folder_unlock(lf);
+ }
+
+ fi = g_malloc0(sizeof(*fi));
+ fi->full_name = g_strdup(full_name);
+ fi->name = g_strdup(name);
+ fi->url = g_strdup(lf->folder_path);
+ fi->unread_message_count = -1;
+ camel_object_trigger_event(CAMEL_OBJECT(parent_store), "folder_created", fi);
+
+ camel_folder_info_free (fi);
+
+ return lf;
+}
+
+CamelFolder *
+camel_spool_folder_new(CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex)
+{
+ CamelFolder *folder;
+
+ d(printf("Creating spool folder: %s in %s\n", full_name, camel_local_store_get_toplevel_dir((CamelLocalStore *)parent_store)));
+
+ folder = (CamelFolder *)camel_object_new(CAMEL_SPOOL_FOLDER_TYPE);
+ folder = (CamelFolder *)camel_spool_folder_construct((CamelSpoolFolder *)folder,
+ parent_store, full_name, flags, ex);
+
+ return folder;
+}
+
+/* lock the folder, may be called repeatedly (with matching unlock calls),
+ with type the same or less than the first call */
+int camel_spool_folder_lock(CamelSpoolFolder *lf, CamelLockType type, CamelException *ex)
+{
+ if (lf->locked > 0) {
+ /* lets be anal here - its important the code knows what its doing */
+ g_assert(lf->locktype == type || lf->locktype == CAMEL_LOCK_WRITE);
+ } else {
+ if (CSPOOLF_CLASS(lf)->lock(lf, type, ex) == -1)
+ return -1;
+ lf->locktype = type;
+ }
+
+ lf->locked++;
+
+ return 0;
+}
+
+/* unlock folder */
+int camel_spool_folder_unlock(CamelSpoolFolder *lf)
+{
+ g_assert(lf->locked>0);
+ lf->locked--;
+ if (lf->locked == 0)
+ CSPOOLF_CLASS(lf)->unlock(lf);
+
+ return 0;
+}
+
+static int
+spool_lock(CamelSpoolFolder *lf, CamelLockType type, CamelException *ex)
+{
+ return 0;
+}
+
+static void
+spool_unlock(CamelSpoolFolder *lf)
+{
+ /* nothing */
+}
+
+static void
+spool_sync(CamelFolder *folder, gboolean expunge, CamelException *ex)
+{
+ CamelSpoolFolder *lf = CAMEL_SPOOL_FOLDER(folder);
+
+ d(printf("spool sync, expunge=%s\n", expunge?"true":"false"));
+
+ if (camel_spool_folder_lock(lf, CAMEL_LOCK_WRITE, ex) == -1)
+ return;
+
+ /* if sync fails, we'll pass it up on exit through ex */
+ camel_spool_summary_sync((CamelSpoolSummary *)folder->summary, expunge, lf->changes, ex);
+ camel_spool_folder_unlock(lf);
+
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event(CAMEL_OBJECT(folder), "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+}
+
+static void
+spool_expunge(CamelFolder *folder, CamelException *ex)
+{
+ d(printf("expunge\n"));
+
+ /* Just do a sync with expunge, serves the same purpose */
+ /* call the callback directly, to avoid locking problems */
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->sync(folder, TRUE, ex);
+}
+
+static GPtrArray *
+spool_search_by_expression(CamelFolder *folder, const char *expression, CamelException *ex)
+{
+ CamelSpoolFolder *spool_folder = CAMEL_SPOOL_FOLDER(folder);
+ GPtrArray *summary, *matches;
+
+ /* NOTE: could get away without the search lock by creating a new
+ search object each time */
+
+ CAMEL_SPOOL_FOLDER_LOCK(folder, search_lock);
+
+ if (spool_folder->search == NULL)
+ spool_folder->search = camel_folder_search_new();
+
+ camel_folder_search_set_folder(spool_folder->search, folder);
+ summary = camel_folder_get_summary(folder);
+ camel_folder_search_set_summary(spool_folder->search, summary);
+
+ matches = camel_folder_search_execute_expression(spool_folder->search, expression, ex);
+
+ CAMEL_SPOOL_FOLDER_UNLOCK(folder, search_lock);
+
+ camel_folder_free_summary(folder, summary);
+
+ return matches;
+}
+
+static void
+spool_search_free(CamelFolder *folder, GPtrArray * result)
+{
+ CamelSpoolFolder *spool_folder = CAMEL_SPOOL_FOLDER(folder);
+
+ /* we need to lock this free because of the way search_free_result works */
+ /* FIXME: put the lock inside search_free_result */
+ CAMEL_SPOOL_FOLDER_LOCK(folder, search_lock);
+
+ camel_folder_search_free_result(spool_folder->search, result);
+
+ CAMEL_SPOOL_FOLDER_UNLOCK(folder, search_lock);
+}
+
+static void
+spool_append_message(CamelFolder *folder, CamelMimeMessage * message, const CamelMessageInfo * info, CamelException *ex)
+{
+ CamelSpoolFolder *lf = (CamelSpoolFolder *)folder;
+ CamelStream *output_stream = NULL, *filter_stream = NULL;
+ CamelMimeFilter *filter_from = NULL;
+ CamelSpoolSummary *mbs = (CamelSpoolSummary *)folder->summary;
+ CamelMessageInfo *mi;
+ char *fromline = NULL;
+ int fd;
+ struct stat st;
+#if 0
+ char *xev;
+#endif
+ /* If we can't lock, dont do anything */
+ if (camel_spool_folder_lock(lf, CAMEL_LOCK_WRITE, ex) == -1)
+ return;
+
+ d(printf("Appending message\n"));
+
+ /* first, check the summary is correct (updates folder_size too) */
+ camel_spool_summary_check((CamelSpoolSummary *)folder->summary, lf->changes, ex);
+ if (camel_exception_is_set(ex))
+ goto fail;
+
+ /* add it to the summary/assign the uid, etc */
+ mi = camel_spool_summary_add((CamelSpoolSummary *)folder->summary, message, info, lf->changes, ex);
+ if (camel_exception_is_set(ex))
+ goto fail;
+
+ d(printf("Appending message: uid is %s\n", camel_message_info_uid(mi)));
+
+ output_stream = camel_stream_fs_new_with_name(lf->folder_path, O_WRONLY|O_APPEND, 0600);
+ if (output_stream == NULL) {
+ camel_exception_setv(ex, 1, _("Cannot open mailbox: %s: %s\n"), lf->folder_path, strerror(errno));
+ goto fail;
+ }
+
+ /* and we need to set the frompos/XEV explicitly */
+ ((CamelSpoolMessageInfo *)mi)->frompos = mbs->folder_size?mbs->folder_size+1:0;
+#if 0
+ xev = camel_spool_summary_encode_x_evolution((CamelLocalSummary *)folder->summary, mi);
+ if (xev) {
+ /* the x-ev header should match the 'current' flags, no problem, so store as much */
+ camel_medium_set_header((CamelMedium *)message, "X-Evolution", xev);
+ mi->flags &= ~ CAMEL_MESSAGE_FOLDER_NOXEV|CAMEL_MESSAGE_FOLDER_FLAGGED;
+ g_free(xev);
+ }
+#endif
+
+ /* we must write this to the non-filtered stream ... prepend a \n if not at the start of the file */
+ fromline = camel_spool_summary_build_from(((CamelMimePart *)message)->headers);
+ if (camel_stream_printf(output_stream, mbs->folder_size==0?"%s":"\n%s", fromline) == -1)
+ goto fail_write;
+
+ /* and write the content to the filtering stream, that translated '\nFrom' into '\n>From' */
+ filter_stream = (CamelStream *) camel_stream_filter_new_with_stream(output_stream);
+ filter_from = (CamelMimeFilter *) camel_mime_filter_from_new();
+ camel_stream_filter_add((CamelStreamFilter *) filter_stream, filter_from);
+ if (camel_data_wrapper_write_to_stream((CamelDataWrapper *)message, filter_stream) == -1)
+ goto fail_write;
+
+ if (camel_stream_close(filter_stream) == -1)
+ goto fail_write;
+
+ /* unlock as soon as we can */
+ camel_spool_folder_unlock(lf);
+
+ /* filter stream ref's the output stream itself, so we need to unref it too */
+ camel_object_unref((CamelObject *)filter_from);
+ camel_object_unref((CamelObject *)filter_stream);
+ camel_object_unref((CamelObject *)output_stream);
+ g_free(fromline);
+
+ /* now we 'fudge' the summary to tell it its uptodate, because its idea of uptodate has just changed */
+ /* the stat really shouldn't fail, we just wrote to it */
+ if (stat(lf->folder_path, &st) == 0) {
+ mbs->folder_size = st.st_size;
+ ((CamelFolderSummary *)mbs)->time = st.st_mtime;
+ }
+
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+
+ return;
+
+fail_write:
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot append message to spool file: %s: %s"),
+ lf->folder_path, g_strerror (errno));
+
+ if (filter_stream)
+ camel_object_unref(CAMEL_OBJECT(filter_stream));
+
+ if (output_stream)
+ camel_object_unref(CAMEL_OBJECT(output_stream));
+
+ if (filter_from)
+ camel_object_unref(CAMEL_OBJECT(filter_from));
+
+ g_free(fromline);
+
+ /* reset the file to original size */
+ fd = open(lf->folder_path, O_WRONLY, 0600);
+ if (fd != -1) {
+ ftruncate(fd, mbs->folder_size);
+ close(fd);
+ }
+
+ /* remove the summary info so we are not out-of-sync with the spool */
+ camel_folder_summary_remove_uid (CAMEL_FOLDER_SUMMARY (mbs), camel_message_info_uid (mi));
+
+ /* and tell the summary its uptodate */
+ if (stat(lf->folder_path, &st) == 0) {
+ mbs->folder_size = st.st_size;
+ ((CamelFolderSummary *)mbs)->time = st.st_mtime;
+ }
+
+fail:
+ /* make sure we unlock the folder - before we start triggering events into appland */
+ camel_spool_folder_unlock(lf);
+
+ /* cascade the changes through, anyway, if there are any outstanding */
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+}
+
+static CamelMimeMessage *
+spool_get_message(CamelFolder *folder, const gchar * uid, CamelException *ex)
+{
+ CamelSpoolFolder *lf = (CamelSpoolFolder *)folder;
+ CamelMimeMessage *message;
+ CamelSpoolMessageInfo *info;
+ CamelMimeParser *parser;
+ int fd;
+ int retried = FALSE;
+
+ d(printf("Getting message %s\n", uid));
+
+ /* lock the folder first, burn if we can't */
+ if (camel_spool_folder_lock(lf, CAMEL_LOCK_READ, ex) == -1)
+ return NULL;
+
+retry:
+ /* get the message summary info */
+ info = (CamelSpoolMessageInfo *) camel_folder_summary_uid(folder->summary, uid);
+
+ if (info == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("Cannot get message: %s\n %s"), uid, _("No such message"));
+ camel_spool_folder_unlock(lf);
+ return NULL;
+ }
+
+ /* no frompos, its an error in the library (and we can't do anything with it */
+ g_assert(info->frompos != -1);
+
+ /* we use an fd instead of a normal stream here - the reason is subtle, camel_mime_part will cache
+ the whole message in memory if the stream is non-seekable (which it is when built from a parser
+ with no stream). This means we dont have to lock the spool for the life of the message, but only
+ while it is being created. */
+
+ fd = open(lf->folder_path, O_RDONLY);
+ if (fd == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("Cannot get message: %s from folder %s\n %s"), uid, lf->folder_path,
+ strerror(errno));
+ camel_spool_folder_unlock(lf);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)info);
+ return NULL;
+ }
+
+ /* we use a parser to verify the message is correct, and in the correct position */
+ parser = camel_mime_parser_new();
+ camel_mime_parser_init_with_fd(parser, fd);
+ camel_mime_parser_scan_from(parser, TRUE);
+
+ camel_mime_parser_seek(parser, info->frompos, SEEK_SET);
+ if (camel_mime_parser_step(parser, NULL, NULL) != HSCAN_FROM
+ || camel_mime_parser_tell_start_from(parser) != info->frompos) {
+
+ g_warning("Summary doesn't match the folder contents! eek!\n"
+ " expecting offset %ld got %ld, state = %d", (long int)info->frompos,
+ (long int)camel_mime_parser_tell_start_from(parser),
+ camel_mime_parser_state(parser));
+
+ camel_object_unref((CamelObject *)parser);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)info);
+
+ if (!retried) {
+ retried = TRUE;
+ camel_spool_summary_check((CamelSpoolSummary *)folder->summary, lf->changes, ex);
+ if (!camel_exception_is_set(ex))
+ goto retry;
+ }
+
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("Cannot get message: %s from folder %s\n %s"), uid, lf->folder_path,
+ _("The folder appears to be irrecoverably corrupted."));
+
+ camel_spool_folder_unlock(lf);
+ return NULL;
+ }
+
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)info);
+
+ message = camel_mime_message_new();
+ if (camel_mime_part_construct_from_parser((CamelMimePart *)message, parser) == -1) {
+ g_warning("Construction failed");
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("Cannot get message: %s from folder %s\n %s"), uid, lf->folder_path,
+ _("Message construction failed: Corrupt mailbox?"));
+ camel_object_unref((CamelObject *)parser);
+ camel_object_unref((CamelObject *)message);
+ camel_spool_folder_unlock(lf);
+ return NULL;
+ }
+
+ /* and unlock now we're finished with it */
+ camel_spool_folder_unlock(lf);
+
+ camel_object_unref((CamelObject *)parser);
+
+ /* use the opportunity to notify of changes (particularly if we had a rebuild) */
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+
+ return message;
+}
+
+static void
+spool_set_message_user_flag(CamelFolder *folder, const char *uid, const char *name, gboolean value)
+{
+ CamelMessageInfo *info;
+
+ g_return_if_fail(folder->summary != NULL);
+
+ info = camel_folder_summary_uid(folder->summary, uid);
+ g_return_if_fail(info != NULL);
+
+ if (camel_flag_set(&info->user_flags, name, value)) {
+ info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED|CAMEL_MESSAGE_FOLDER_XEVCHANGE;
+ camel_folder_summary_touch(folder->summary);
+ camel_object_trigger_event(CAMEL_OBJECT(folder), "message_changed", (char *) uid);
+ }
+ camel_folder_summary_info_free(folder->summary, info);
+}
+
+static void
+spool_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value)
+{
+ CamelMessageInfo *info;
+
+ g_return_if_fail(folder->summary != NULL);
+
+ info = camel_folder_summary_uid(folder->summary, uid);
+ g_return_if_fail(info != NULL);
+
+ if (camel_tag_set(&info->user_tags, name, value)) {
+ info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED|CAMEL_MESSAGE_FOLDER_XEVCHANGE;
+ camel_folder_summary_touch(folder->summary);
+ camel_object_trigger_event(CAMEL_OBJECT(folder), "message_changed", (char *) uid);
+ }
+ camel_folder_summary_info_free(folder->summary, info);
+}
diff --git a/camel/providers/local/camel-spool-folder.h b/camel/providers/local/camel-spool-folder.h
new file mode 100644
index 0000000000..87b8c16b0d
--- /dev/null
+++ b/camel/providers/local/camel-spool-folder.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Author: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2001 Ximian Inc (http://www.ximian.com/)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef CAMEL_SPOOL_FOLDER_H
+#define CAMEL_SPOOL_FOLDER_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus }*/
+
+#include <camel/camel-folder.h>
+#include <camel/camel-folder-search.h>
+#include <libibex/ibex.h>
+#include "camel-spool-summary.h"
+#include "camel-lock.h"
+
+/* #include "camel-store.h" */
+
+#define CAMEL_SPOOL_FOLDER_TYPE (camel_spool_folder_get_type ())
+#define CAMEL_SPOOL_FOLDER(obj) (CAMEL_CHECK_CAST((obj), CAMEL_SPOOL_FOLDER_TYPE, CamelSpoolFolder))
+#define CAMEL_SPOOL_FOLDER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_SPOOL_FOLDER_TYPE, CamelSpoolFolderClass))
+#define CAMEL_IS_SPOOL_FOLDER(o) (CAMEL_CHECK_TYPE((o), CAMEL_SPOOL_FOLDER_TYPE))
+
+typedef struct {
+ CamelFolder parent_object;
+ struct _CamelSpoolFolderPrivate *priv;
+
+ guint32 flags; /* open mode flags */
+
+ int locked; /* lock counter */
+ CamelLockType locktype; /* what type of lock we have */
+
+ char *base_path; /* base path of the spool folder */
+ char *folder_path; /* the path to the folder itself */
+#if 0
+ char *summary_path; /* where the summary lives */
+ char *index_path; /* where the index file lives */
+
+ ibex *index; /* index for this folder */
+#endif
+ CamelFolderSearch *search; /* used to run searches, we just use the real thing (tm) */
+ CamelFolderChangeInfo *changes; /* used to store changes to the folder during processing */
+} CamelSpoolFolder;
+
+typedef struct {
+ CamelFolderClass parent_class;
+
+ /* Virtual methods */
+
+ /* summary factory, only used at init */
+ CamelSpoolSummary *(*create_summary)(const char *path, const char *folder, ibex *index);
+
+ /* Lock the folder for my operations */
+ int (*lock)(CamelSpoolFolder *, CamelLockType type, CamelException *ex);
+
+ /* Unlock the folder for my operations */
+ void (*unlock)(CamelSpoolFolder *);
+} CamelSpoolFolderClass;
+
+
+/* public methods */
+/* flags are taken from CAMEL_STORE_FOLDER_* flags */
+CamelSpoolFolder *camel_spool_folder_construct(CamelSpoolFolder *lf, CamelStore *parent_store,
+ const char *full_name, guint32 flags, CamelException *ex);
+
+/* Standard Camel function */
+CamelType camel_spool_folder_get_type(void);
+
+CamelFolder *camel_spool_folder_new(CamelStore *parent_store, const char *full_name,
+ guint32 flags, CamelException *ex);
+
+/* Lock the folder for internal use. May be called repeatedly */
+/* UNIMPLEMENTED */
+int camel_spool_folder_lock(CamelSpoolFolder *lf, CamelLockType type, CamelException *ex);
+int camel_spool_folder_unlock(CamelSpoolFolder *lf);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_SPOOL_FOLDER_H */
diff --git a/camel/providers/local/camel-spool-store.c b/camel/providers/local/camel-spool-store.c
new file mode 100644
index 0000000000..5ef47347d1
--- /dev/null
+++ b/camel/providers/local/camel-spool-store.c
@@ -0,0 +1,194 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2001 Ximian Inc (http://www.ximian.com/)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "camel-spool-store.h"
+#include "camel-spool-folder.h"
+#include "camel-exception.h"
+#include "camel-url.h"
+
+#define d(x)
+
+/* Returns the class for a CamelSpoolStore */
+#define CSPOOLS_CLASS(so) CAMEL_SPOOL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static void construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
+static CamelFolder *get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
+static char *get_name(CamelService *service, gboolean brief);
+static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
+static void rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
+static CamelFolderInfo *get_folder_info (CamelStore *store, const char *top,
+ guint32 flags, CamelException *ex);
+static void delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
+static void rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
+
+static CamelStoreClass *parent_class = NULL;
+
+static void
+camel_spool_store_class_init (CamelSpoolStoreClass *camel_spool_store_class)
+{
+ CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_spool_store_class);
+ CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_spool_store_class);
+
+ parent_class = CAMEL_STORE_CLASS (camel_type_get_global_classfuncs (camel_store_get_type ()));
+
+ /* virtual method overload */
+ camel_service_class->construct = construct;
+ camel_service_class->get_name = get_name;
+ camel_store_class->get_folder = get_folder;
+ camel_store_class->get_inbox = get_inbox;
+ camel_store_class->get_folder_info = get_folder_info;
+ camel_store_class->free_folder_info = camel_store_free_folder_info_full;
+
+ camel_store_class->delete_folder = delete_folder;
+ camel_store_class->rename_folder = rename_folder;
+}
+
+CamelType
+camel_spool_store_get_type (void)
+{
+ static CamelType camel_spool_store_type = CAMEL_INVALID_TYPE;
+
+ if (camel_spool_store_type == CAMEL_INVALID_TYPE) {
+ camel_spool_store_type = camel_type_register (CAMEL_STORE_TYPE, "CamelSpoolStore",
+ sizeof (CamelSpoolStore),
+ sizeof (CamelSpoolStoreClass),
+ (CamelObjectClassInitFunc) camel_spool_store_class_init,
+ NULL,
+ NULL,
+ NULL);
+ }
+
+ return camel_spool_store_type;
+}
+
+static void
+construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
+{
+ int len;
+
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ len = strlen (service->url->path);
+ if (service->url->path[len - 1] != '/') {
+ service->url->path = g_realloc (service->url->path, len + 2);
+ strcpy (service->url->path + len, "/");
+ }
+}
+
+const char *
+camel_spool_store_get_toplevel_dir (CamelSpoolStore *store)
+{
+ CamelURL *url = CAMEL_SERVICE (store)->url;
+
+ g_assert (url != NULL);
+ return url->path;
+}
+
+static CamelFolder *
+get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex)
+{
+ struct stat st;
+ char *path = ((CamelService *)store)->url->path;
+ char *name;
+
+ if (path[0] != '/') {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Store root %s is not an absolute path"), path);
+ return NULL;
+ }
+
+ name = g_strdup_printf("%s%s", CAMEL_SERVICE(store)->url->path, folder_name);
+
+ if (stat(name, &st) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Folder `%s/%s' does not exist."),
+ path, folder_name);
+ g_free(name);
+ return NULL;
+ } else if (!S_ISREG(st.st_mode)) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("`%s' is not a regular file."),
+ name);
+ g_free(name);
+ return NULL;
+ }
+
+ g_free(name);
+
+ return camel_spool_folder_new(store, folder_name, flags, ex);
+}
+
+static CamelFolder *
+get_inbox(CamelStore *store, CamelException *ex)
+{
+ camel_exception_set(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Spool stores do not have an inbox"));
+ return NULL;
+}
+
+static char *
+get_name (CamelService *service, gboolean brief)
+{
+ if (brief)
+ return g_strdup (service->url->path);
+ else
+ return g_strdup_printf (_("Spool mail file %s"), service->url->path);
+}
+
+static CamelFolderInfo *
+get_folder_info (CamelStore *store, const char *top,
+ guint32 flags, CamelException *ex)
+{
+ /* FIXME: This is broken, but it corresponds to what was
+ * there before.
+ */
+ return NULL;
+}
+
+/* default implementation, rename all */
+static void
+rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
+{
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Spool folders cannot be renamed"));
+}
+
+/* default implementation, only delete metadata */
+static void
+delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Spool folders cannot be deleted"));
+}
diff --git a/camel/providers/local/camel-spool-store.h b/camel/providers/local/camel-spool-store.h
new file mode 100644
index 0000000000..1b118d4742
--- /dev/null
+++ b/camel/providers/local/camel-spool-store.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2001 Ximian Inc (http://www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+
+#ifndef CAMEL_SPOOL_STORE_H
+#define CAMEL_SPOOL_STORE_H 1
+
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus }*/
+
+#include "camel-store.h"
+
+#define CAMEL_SPOOL_STORE_TYPE (camel_spool_store_get_type ())
+#define CAMEL_SPOOL_STORE(obj) (CAMEL_CHECK_CAST((obj), CAMEL_SPOOL_STORE_TYPE, CamelSpoolStore))
+#define CAMEL_SPOOL_STORE_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_SPOOL_STORE_TYPE, CamelSpoolStoreClass))
+#define CAMEL_IS_SPOOL_STORE(o) (CAMEL_CHECK_TYPE((o), CAMEL_SPOOL_STORE_TYPE))
+
+
+typedef struct {
+ CamelStore parent_object;
+
+} CamelSpoolStore;
+
+
+
+typedef struct {
+ CamelStoreClass parent_class;
+
+} CamelSpoolStoreClass;
+
+
+/* public methods */
+
+/* Standard Camel function */
+CamelType camel_spool_store_get_type (void);
+
+const gchar *camel_spool_store_get_toplevel_dir (CamelSpoolStore *store);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_SPOOL_STORE_H */
+
+
diff --git a/camel/providers/local/camel-spool-summary.c b/camel/providers/local/camel-spool-summary.c
new file mode 100644
index 0000000000..594a33d744
--- /dev/null
+++ b/camel/providers/local/camel-spool-summary.c
@@ -0,0 +1,1273 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Copyright (C) 2001 Ximian Inc. (http://www.ximian.com)
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "camel-spool-summary.h"
+#include "camel-mime-message.h"
+#include "camel-file-utils.h"
+#include "camel-operation.h"
+
+#define io(x)
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#define CAMEL_SPOOL_SUMMARY_VERSION (0x400)
+
+struct _CamelSpoolSummaryPrivate {
+};
+
+#define _PRIVATE(o) (((CamelSpoolSummary *)(o))->priv)
+
+static int summary_header_load (CamelFolderSummary *, FILE *);
+static int summary_header_save (CamelFolderSummary *, FILE *);
+
+static CamelMessageInfo * message_info_new (CamelFolderSummary *, struct _header_raw *);
+static CamelMessageInfo * message_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp);
+static CamelMessageInfo * message_info_load (CamelFolderSummary *, FILE *);
+static int message_info_save (CamelFolderSummary *, FILE *, CamelMessageInfo *);
+
+static int spool_summary_decode_x_evolution(CamelSpoolSummary *cls, const char *xev, CamelMessageInfo *mi);
+static char *spool_summary_encode_x_evolution(CamelSpoolSummary *cls, const CamelMessageInfo *mi);
+
+static int spool_summary_load(CamelSpoolSummary *cls, int forceindex, CamelException *ex);
+static int spool_summary_check(CamelSpoolSummary *cls, CamelFolderChangeInfo *changeinfo, CamelException *ex);
+static int spool_summary_sync(CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex);
+static CamelMessageInfo *spool_summary_add(CamelSpoolSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *, CamelException *ex);
+
+static int spool_summary_sync_full(CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex);
+
+static void camel_spool_summary_class_init (CamelSpoolSummaryClass *klass);
+static void camel_spool_summary_init (CamelSpoolSummary *obj);
+static void camel_spool_summary_finalise (CamelObject *obj);
+static CamelFolderSummaryClass *camel_spool_summary_parent;
+
+CamelType
+camel_spool_summary_get_type(void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register(camel_folder_summary_get_type(), "CamelSpoolSummary",
+ sizeof (CamelSpoolSummary),
+ sizeof (CamelSpoolSummaryClass),
+ (CamelObjectClassInitFunc) camel_spool_summary_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_spool_summary_init,
+ (CamelObjectFinalizeFunc) camel_spool_summary_finalise);
+ }
+
+ return type;
+}
+
+static void
+camel_spool_summary_class_init(CamelSpoolSummaryClass *klass)
+{
+ CamelFolderSummaryClass *sklass = (CamelFolderSummaryClass *) klass;
+
+ camel_spool_summary_parent = CAMEL_FOLDER_SUMMARY_CLASS(camel_type_get_global_classfuncs(camel_folder_summary_get_type()));
+
+ sklass->summary_header_load = summary_header_load;
+ sklass->summary_header_save = summary_header_save;
+
+ sklass->message_info_new = message_info_new;
+ sklass->message_info_new_from_parser = message_info_new_from_parser;
+ sklass->message_info_load = message_info_load;
+ sklass->message_info_save = message_info_save;
+
+ klass->load = spool_summary_load;
+ klass->check = spool_summary_check;
+ klass->sync = spool_summary_sync;
+ klass->add = spool_summary_add;
+
+ klass->encode_x_evolution = spool_summary_encode_x_evolution;
+ klass->decode_x_evolution = spool_summary_decode_x_evolution;
+}
+
+static void
+camel_spool_summary_init(CamelSpoolSummary *obj)
+{
+ struct _CamelSpoolSummaryPrivate *p;
+ struct _CamelFolderSummary *s = (CamelFolderSummary *)obj;
+
+ p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
+
+ /* subclasses need to set the right instance data sizes */
+ s->message_info_size = sizeof(CamelSpoolMessageInfo);
+ s->content_info_size = sizeof(CamelMessageContentInfo);
+
+ /* and a unique file version */
+ s->version += CAMEL_SPOOL_SUMMARY_VERSION;
+}
+
+static void
+camel_spool_summary_finalise(CamelObject *obj)
+{
+ CamelSpoolSummary *mbs = CAMEL_SPOOL_SUMMARY(obj);
+
+ g_free(mbs->folder_path);
+}
+
+CamelSpoolSummary *
+camel_spool_summary_new(const char *filename)
+{
+ CamelSpoolSummary *new = (CamelSpoolSummary *)camel_object_new(camel_spool_summary_get_type());
+
+ camel_folder_summary_set_build_content(CAMEL_FOLDER_SUMMARY(new), FALSE);
+ new->folder_path = g_strdup(filename);
+
+ return new;
+}
+
+static int
+spool_summary_load(CamelSpoolSummary *cls, int forceindex, CamelException *ex)
+{
+ g_warning("spool_summary_load() should nto b e called\n");
+
+ return camel_folder_summary_load((CamelFolderSummary *)cls);
+}
+
+/* load/check the summary */
+int
+camel_spool_summary_load(CamelSpoolSummary *cls, int forceindex, CamelException *ex)
+{
+ struct stat st;
+ CamelFolderSummary *s = (CamelFolderSummary *)cls;
+
+ g_warning("spool_summary_load() should nto b e called\n");
+
+ d(printf("Loading summary ...\n"));
+
+ if (forceindex
+ || stat(s->summary_path, &st) == -1
+ || ((CamelSpoolSummaryClass *)(CAMEL_OBJECT_GET_CLASS(cls)))->load(cls, forceindex, ex) == -1) {
+ camel_folder_summary_clear((CamelFolderSummary *)cls);
+ }
+
+ return camel_spool_summary_check(cls, NULL, ex);
+}
+
+char *
+camel_spool_summary_encode_x_evolution(CamelSpoolSummary *cls, const CamelMessageInfo *info)
+{
+ return ((CamelSpoolSummaryClass *)(CAMEL_OBJECT_GET_CLASS(cls)))->encode_x_evolution(cls, info);
+}
+
+int
+camel_spool_summary_decode_x_evolution(CamelSpoolSummary *cls, const char *xev, CamelMessageInfo *info)
+{
+ return ((CamelSpoolSummaryClass *)(CAMEL_OBJECT_GET_CLASS(cls)))->decode_x_evolution(cls, xev, info);
+}
+
+int
+camel_spool_summary_check(CamelSpoolSummary *cls, CamelFolderChangeInfo *changeinfo, CamelException *ex)
+{
+ int ret;
+
+ ret = ((CamelSpoolSummaryClass *)(CAMEL_OBJECT_GET_CLASS(cls)))->check(cls, changeinfo, ex);
+
+ return ret;
+}
+
+int
+camel_spool_summary_sync(CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex)
+{
+ return ((CamelSpoolSummaryClass *)(CAMEL_OBJECT_GET_CLASS(cls)))->sync(cls, expunge, changeinfo, ex);
+}
+
+CamelMessageInfo *
+camel_spool_summary_add(CamelSpoolSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *ci, CamelException *ex)
+{
+ return ((CamelSpoolSummaryClass *)(CAMEL_OBJECT_GET_CLASS(cls)))->add(cls, msg, info, ci, ex);
+}
+
+/**
+ * camel_spool_summary_write_headers:
+ * @fd:
+ * @header:
+ * @xevline:
+ *
+ * Write a bunch of headers to the file @fd. IF xevline is non NULL, then
+ * an X-Evolution header line is created at the end of all of the headers.
+ * The headers written are termianted with a blank line.
+ *
+ * Return value: -1 on error, otherwise the number of bytes written.
+ **/
+int
+camel_spool_summary_write_headers(int fd, struct _header_raw *header, char *xevline)
+{
+ int outlen = 0, len;
+ int newfd;
+ FILE *out;
+
+ /* dum de dum, maybe the whole sync function should just use stdio for output */
+ newfd = dup(fd);
+ if (newfd == -1)
+ return -1;
+
+ out = fdopen(newfd, "w");
+ if (out == NULL) {
+ close(newfd);
+ errno = EINVAL;
+ return -1;
+ }
+
+ while (header) {
+ if (strcmp(header->name, "X-Evolution")) {
+ len = fprintf(out, "%s:%s\n", header->name, header->value);
+ if (len == -1) {
+ fclose(out);
+ return -1;
+ }
+ outlen += len;
+ }
+ header = header->next;
+ }
+
+ if (xevline) {
+ len = fprintf(out, "X-Evolution: %s\n\n", xevline);
+ if (len == -1) {
+ fclose(out);
+ return -1;
+ }
+ outlen += len;
+ }
+
+ if (fclose(out) == -1)
+ return -1;
+
+ return outlen;
+}
+
+static int
+summary_header_load(CamelFolderSummary *s, FILE *in)
+{
+ CamelSpoolSummary *mbs = CAMEL_SPOOL_SUMMARY(s);
+
+ if (((CamelFolderSummaryClass *)camel_spool_summary_parent)->summary_header_load(s, in) == -1)
+ return -1;
+
+ return camel_file_util_decode_uint32(in, &mbs->folder_size);
+}
+
+static int
+summary_header_save(CamelFolderSummary *s, FILE *out)
+{
+ CamelSpoolSummary *mbs = CAMEL_SPOOL_SUMMARY(s);
+
+ if (((CamelFolderSummaryClass *)camel_spool_summary_parent)->summary_header_save(s, out) == -1)
+ return -1;
+
+ return camel_file_util_encode_uint32(out, mbs->folder_size);
+}
+
+static CamelMessageInfo *
+message_info_new(CamelFolderSummary *s, struct _header_raw *h)
+{
+ CamelMessageInfo *mi;
+ CamelSpoolSummary *cls = (CamelSpoolSummary *)s;
+
+ mi = ((CamelFolderSummaryClass *)camel_spool_summary_parent)->message_info_new(s, h);
+ if (mi) {
+ CamelSpoolMessageInfo *mbi = (CamelSpoolMessageInfo *)mi;
+ const char *xev;
+
+ xev = header_raw_find(&h, "X-Evolution", NULL);
+ if (xev==NULL || camel_spool_summary_decode_x_evolution(cls, xev, mi) == -1) {
+ /* to indicate it has no xev header */
+ mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_MESSAGE_FOLDER_NOXEV;
+ camel_message_info_set_uid(mi, camel_folder_summary_next_uid_string(s));
+ }
+
+ mbi->frompos = -1;
+ }
+
+ return mi;
+}
+
+static CamelMessageInfo *
+message_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
+{
+ CamelMessageInfo *mi;
+
+ mi = ((CamelFolderSummaryClass *)camel_spool_summary_parent)->message_info_new_from_parser(s, mp);
+ if (mi) {
+ CamelSpoolMessageInfo *mbi = (CamelSpoolMessageInfo *)mi;
+
+ mbi->frompos = camel_mime_parser_tell_start_from(mp);
+ }
+
+ return mi;
+}
+
+static CamelMessageInfo *
+message_info_load(CamelFolderSummary *s, FILE *in)
+{
+ CamelMessageInfo *mi;
+
+ io(printf("loading spool message info\n"));
+
+ mi = ((CamelFolderSummaryClass *)camel_spool_summary_parent)->message_info_load(s, in);
+ if (mi) {
+ CamelSpoolMessageInfo *mbi = (CamelSpoolMessageInfo *)mi;
+
+ if (camel_file_util_decode_off_t(in, &mbi->frompos) == -1)
+ goto error;
+ }
+
+ return mi;
+error:
+ camel_folder_summary_info_free(s, mi);
+ return NULL;
+}
+
+static int
+message_info_save(CamelFolderSummary *s, FILE *out, CamelMessageInfo *mi)
+{
+ CamelSpoolMessageInfo *mbi = (CamelSpoolMessageInfo *)mi;
+
+ io(printf("saving spool message info\n"));
+
+ if (((CamelFolderSummaryClass *)camel_spool_summary_parent)->message_info_save(s, out, mi) == -1
+ || camel_file_util_encode_off_t(out, mbi->frompos) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int
+summary_rebuild(CamelSpoolSummary *cls, off_t offset, CamelException *ex)
+{
+ CamelFolderSummary *s = (CamelFolderSummary *)cls;
+ CamelMimeParser *mp;
+ int fd;
+ int ok = 0;
+ struct stat st;
+ off_t size = 0;
+
+ /* FIXME: If there is a failure, it shouldn't clear the summary and restart,
+ it should try and merge the summary info's. This is a bit tricky. */
+
+ camel_operation_start(NULL, _("Summarising folder"));
+
+ fd = open(cls->folder_path, O_RDONLY);
+ if (fd == -1) {
+ printf("%s failed to open: %s", cls->folder_path, strerror(errno));
+ camel_exception_setv(ex, 1, _("Could not open folder: %s: summarising from position %ld: %s"),
+ cls->folder_path, offset, strerror(errno));
+ camel_operation_end(NULL);
+ return -1;
+ }
+
+ if (fstat(fd, &st) == 0)
+ size = st.st_size;
+
+ mp = camel_mime_parser_new();
+ camel_mime_parser_init_with_fd(mp, fd);
+ camel_mime_parser_scan_from(mp, TRUE);
+ camel_mime_parser_seek(mp, offset, SEEK_SET);
+
+ if (offset > 0) {
+ if (camel_mime_parser_step(mp, NULL, NULL) == HSCAN_FROM) {
+ if (camel_mime_parser_tell_start_from(mp) != offset) {
+ g_warning("The next message didn't start where I expected, building summary from start");
+ camel_mime_parser_drop_step(mp);
+ offset = 0;
+ camel_mime_parser_seek(mp, offset, SEEK_SET);
+ camel_folder_summary_clear(s);
+ } else {
+ camel_mime_parser_unstep(mp);
+ }
+ } else {
+ d(printf("mime parser state ran out? state is %d\n", camel_mime_parser_state(mp)));
+ camel_object_unref(CAMEL_OBJECT(mp));
+ /* end of file - no content? no error either */
+ camel_operation_end(NULL);
+ return 0;
+ }
+ }
+
+ while (camel_mime_parser_step(mp, NULL, NULL) == HSCAN_FROM) {
+ CamelMessageInfo *info;
+ off_t pc = camel_mime_parser_tell_start_from (mp) + 1;
+
+ camel_operation_progress (NULL, (int) (((float) pc / size) * 100));
+
+ info = camel_folder_summary_add_from_parser(s, mp);
+ if (info == NULL) {
+ camel_exception_setv(ex, 1, _("Fatal mail parser error near position %ld in folder %s"),
+ camel_mime_parser_tell(mp), cls->folder_path);
+ ok = -1;
+ break;
+ }
+
+ g_assert(camel_mime_parser_step(mp, NULL, NULL) == HSCAN_FROM_END);
+ }
+
+ camel_object_unref(CAMEL_OBJECT (mp));
+
+ /* update the file size/mtime in the summary */
+ if (ok != -1) {
+ if (stat(cls->folder_path, &st) == 0) {
+ camel_folder_summary_touch(s);
+ cls->folder_size = st.st_size;
+ s->time = st.st_mtime;
+ }
+ }
+
+ camel_operation_end(NULL);
+
+ return ok;
+}
+
+/* like summary_rebuild, but also do changeinfo stuff (if supplied) */
+static int
+summary_update(CamelSpoolSummary *cls, off_t offset, CamelFolderChangeInfo *changeinfo, CamelException *ex)
+{
+ int ret, i, count;
+ CamelFolderSummary *s = (CamelFolderSummary *)cls;
+
+ d(printf("Calling summary update, from pos %d\n", (int)offset));
+
+ if (changeinfo) {
+ /* we use the diff function of the change_info to build the update list. */
+ for (i = 0; i < camel_folder_summary_count(s); i++) {
+ CamelMessageInfo *mi = camel_folder_summary_index(s, i);
+
+ camel_folder_change_info_add_source(changeinfo, camel_message_info_uid(mi));
+ camel_folder_summary_info_free(s, mi);
+ }
+ }
+
+ /* do the actual work */
+ ret = summary_rebuild(cls, offset, ex);
+
+ if (changeinfo) {
+ count = camel_folder_summary_count(s);
+ for (i = 0; i < count; i++) {
+ CamelMessageInfo *mi = camel_folder_summary_index(s, i);
+ camel_folder_change_info_add_update(changeinfo, camel_message_info_uid(mi));
+ camel_folder_summary_info_free(s, mi);
+ }
+ camel_folder_change_info_build_diff(changeinfo);
+ }
+
+ return ret;
+}
+
+static int
+spool_summary_check(CamelSpoolSummary *cls, CamelFolderChangeInfo *changeinfo, CamelException *ex)
+{
+ CamelFolderSummary *s = (CamelFolderSummary *)cls;
+ struct stat st;
+ int ret = 0;
+
+ d(printf("Checking summary\n"));
+
+ /* check if the summary is up-to-date */
+ if (stat(cls->folder_path, &st) == -1) {
+ camel_folder_summary_clear(s);
+ camel_exception_setv(ex, 1, _("Cannot summarise folder: %s: %s"), cls->folder_path, strerror(errno));
+ return -1;
+ }
+
+ if (st.st_size == 0) {
+ /* empty? No need to scan at all */
+ d(printf("Empty spool, clearing summary\n"));
+ camel_folder_summary_clear(s);
+ ret = 0;
+ } else if (s->messages->len == 0) {
+ /* if we are empty, then we rebuilt from scratch */
+ d(printf("Empty summary, rebuilding from start\n"));
+ ret = summary_update(cls, 0, changeinfo, ex);
+ } else {
+ /* is the summary uptodate? */
+ if (st.st_size != cls->folder_size || st.st_mtime != s->time) {
+ if (cls->folder_size < st.st_size) {
+ /* this will automatically rescan from 0 if there is a problem */
+ d(printf("folder grew, attempting to rebuild from %d\n", cls>folder_size));
+ ret = summary_update(cls, cls->folder_size, changeinfo, ex);
+ } else {
+ d(printf("folder shrank! rebuilding from start\n"));
+ camel_folder_summary_clear(s);
+ ret = summary_update(cls, 0, changeinfo, ex);
+ }
+ }
+ }
+
+ if (ret != -1) {
+ int i, work, count;
+
+ /* check to see if we need to copy/update the file; missing xev headers prompt this */
+ work = FALSE;
+ count = camel_folder_summary_count(s);
+ for (i=0;!work && i<count; i++) {
+ CamelMessageInfo *info = camel_folder_summary_index(s, i);
+ g_assert(info);
+ work = (info->flags & (CAMEL_MESSAGE_FOLDER_NOXEV)) != 0;
+ camel_folder_summary_info_free(s, info);
+ }
+
+ /* if we do, then write out the headers using sync_full, etc */
+ if (work) {
+ ret = spool_summary_sync_full(cls, FALSE, changeinfo, ex);
+
+ if (stat(cls->folder_path, &st) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Unknown error: %s"), strerror(errno));
+ return -1;
+ }
+ }
+ cls->folder_size = st.st_size;
+ s->time = st.st_mtime;
+ }
+
+ return ret;
+}
+
+static char *tz_months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static char *tz_days[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+/* tries to build a From line, based on message headers */
+char *
+camel_spool_summary_build_from(struct _header_raw *header)
+{
+ GString *out = g_string_new("From ");
+ char *ret;
+ const char *tmp;
+ time_t thetime;
+ int offset;
+ struct tm tm;
+
+ tmp = header_raw_find(&header, "Sender", NULL);
+ if (tmp == NULL)
+ tmp = header_raw_find(&header, "From", NULL);
+ if (tmp != NULL) {
+ struct _header_address *addr = header_address_decode(tmp);
+
+ tmp = NULL;
+ if (addr) {
+ if (addr->type == HEADER_ADDRESS_NAME) {
+ g_string_append(out, addr->v.addr);
+ tmp = "";
+ }
+ header_address_unref(addr);
+ }
+ }
+ if (tmp == NULL) {
+ g_string_append(out, "unknown@nodomain.now.au");
+ }
+
+ /* try use the received header to get the date */
+ tmp = header_raw_find(&header, "Received", NULL);
+ if (tmp) {
+ tmp = strrchr(tmp, ';');
+ if (tmp)
+ tmp++;
+ }
+
+ /* if there isn't one, try the Date field */
+ if (tmp == NULL)
+ tmp = header_raw_find(&header, "Date", NULL);
+
+ thetime = header_decode_date(tmp, &offset);
+
+ thetime += ((offset / 100) * (60 * 60)) + (offset % 100) * 60;
+
+ /* a pseudo, but still bogus attempt at thread safing the function */
+ /*memcpy(&tm, gmtime(&thetime), sizeof(tm));*/
+ gmtime_r(&thetime, &tm);
+
+ g_string_sprintfa(out, " %s %s %d %02d:%02d:%02d %4d\n",
+ tz_days[tm.tm_wday],
+ tz_months[tm.tm_mon], tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_year + 1900);
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+/* perform a full sync */
+static int
+spool_summary_sync_full(CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex)
+{
+ CamelFolderSummary *s = (CamelFolderSummary *)cls;
+ CamelMimeParser *mp = NULL;
+ int i, count;
+ CamelSpoolMessageInfo *info = NULL;
+ int fd = -1, fdout = -1;
+ char *tmpname = NULL;
+ char *buffer, *xevnew = NULL, *p;
+ int len;
+ const char *fromline;
+ int lastdel = FALSE;
+ off_t spoollen, outlen;
+ int size, sizeout;
+ struct stat st;
+
+ d(printf("performing full summary/sync\n"));
+
+ camel_operation_start(NULL, _("Synchronising folder"));
+
+ fd = open(cls->folder_path, O_RDWR);
+ if (fd == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not open folder to summarise: %s: %s"),
+ cls->folder_path, strerror(errno));
+ camel_operation_end(NULL);
+ return -1;
+ }
+
+ mp = camel_mime_parser_new();
+ camel_mime_parser_scan_from(mp, TRUE);
+ camel_mime_parser_scan_pre_from(mp, TRUE);
+ camel_mime_parser_init_with_fd(mp, fd);
+
+#ifdef HAVE_MKSTEMP
+ tmpname = alloca(64);
+ sprintf(tmpname, "/tmp/spool.camel.XXXXXX");
+ fdout = mkstemp(tmpname);
+#else
+#warning "Your system has no mkstemp(3), spool updating may be insecure"
+ tmpname = alloca(L_tmpnam);
+ tmpnam(tmpname);
+ fdout = open(tmpname, O_RDWR|O_CREAT|O_EXCL, 0600);
+#endif
+ d(printf("Writing tmp file to %s\n", tmpname));
+ if (fdout == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot open temporary mailbox: %s"), strerror(errno));
+ goto error;
+ }
+
+ count = camel_folder_summary_count(s);
+ for (i = 0; i < count; i++) {
+ int pc = (i + 1) * 100 / count;
+
+ camel_operation_progress(NULL, pc);
+
+ info = (CamelSpoolMessageInfo *)camel_folder_summary_index(s, i);
+
+ g_assert(info);
+
+ d(printf("Looking at message %s\n", info->info.uid));
+
+ /* only need to seek past deleted messages, otherwise we should be at the right spot/state already */
+ if (lastdel) {
+ d(printf("seeking to %d\n", (int)info->frompos));
+ camel_mime_parser_seek(mp, info->frompos, SEEK_SET);
+ }
+
+ if (camel_mime_parser_step(mp, &buffer, &len) != HSCAN_FROM) {
+ g_warning("Expected a From line here, didn't get it");
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Summary and folder mismatch, even after a sync"));
+ goto error;
+ }
+
+ if (camel_mime_parser_tell_start_from(mp) != info->frompos) {
+ g_warning("Didn't get the next message where I expected (%d) got %d instead",
+ (int)info->frompos, (int)camel_mime_parser_tell_start_from(mp));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Summary and folder mismatch, even after a sync"));
+ goto error;
+ }
+
+ lastdel = FALSE;
+ if (expunge && info->info.flags & CAMEL_MESSAGE_DELETED) {
+ const char *uid = camel_message_info_uid(info);
+
+ d(printf("Deleting %s\n", uid));
+
+#if 0
+ if (cls->index)
+ ibex_unindex(cls->index, (char *)uid);
+#endif
+ /* remove it from the change list */
+ camel_folder_change_info_remove_uid(changeinfo, uid);
+ camel_folder_summary_remove(s, (CamelMessageInfo *)info);
+ camel_folder_summary_info_free(s, (CamelMessageInfo *)info);
+ count--;
+ i--;
+ info = NULL;
+ lastdel = TRUE;
+ } else {
+ /* otherwise, the message is staying, copy its From_ line across */
+ if (i>0) {
+ write(fdout, "\n", 1);
+ }
+ info->frompos = lseek(fdout, 0, SEEK_CUR);
+ fromline = camel_mime_parser_from_line(mp);
+ write(fdout, fromline, strlen(fromline));
+ }
+
+ if (info && info->info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_FLAGGED)) {
+ d(printf("Updating header for %s flags = %08x\n", info->info.uid, info->info.flags));
+
+ if (camel_mime_parser_step(mp, &buffer, &len) == HSCAN_FROM_END) {
+ g_warning("camel_mime_parser_step failed (2)");
+ goto error;
+ }
+
+ xevnew = camel_spool_summary_encode_x_evolution(cls, (CamelMessageInfo *)info);
+ if (camel_spool_summary_write_headers(fdout, camel_mime_parser_headers_raw(mp), xevnew) == -1) {
+ d(printf("Error writing to tmp mailbox\n"));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error writing to temp mailbox: %s"),
+ strerror(errno));
+ goto error;
+ }
+ info->info.flags &= 0xffff;
+ g_free(xevnew);
+ xevnew = NULL;
+ camel_mime_parser_drop_step(mp);
+ }
+
+ camel_mime_parser_drop_step(mp);
+ if (info) {
+ d(printf("looking for message content to copy across from %d\n", (int)camel_mime_parser_tell(mp)));
+ while (camel_mime_parser_step(mp, &buffer, &len) == HSCAN_PRE_FROM) {
+ d(printf("copying spool contents to tmp: '%.*s'\n", len, buffer));
+ if (write(fdout, buffer, len) != len) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Writing to tmp mailbox failed: %s: %s"),
+ cls->folder_path, strerror(errno));
+ goto error;
+ }
+ }
+ d(printf("we are now at %d, from = %d\n", (int)camel_mime_parser_tell(mp),
+ (int)camel_mime_parser_tell_start_from(mp)));
+ camel_mime_parser_unstep(mp);
+ camel_folder_summary_info_free(s, (CamelMessageInfo *)info);
+ info = NULL;
+ }
+ }
+
+ /* sync out content */
+ if (fsync(fdout) == -1) {
+ g_warning("Cannot sync temporary folder: %s", strerror(errno));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not sync temporary folder %s: %s"),
+ cls->folder_path, strerror(errno));
+ goto error;
+ }
+
+ /* see if we can write this much to the spool file */
+ if (fstat(fd, &st) == -1) {
+ g_warning("Cannot sync temporary folder: %s", strerror(errno));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not sync temporary folder %s: %s"),
+ cls->folder_path, strerror(errno));
+ goto error;
+ }
+ spoollen = st.st_size;
+
+ if (fstat(fdout, &st) == -1) {
+ g_warning("Cannot sync temporary folder: %s", strerror(errno));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not sync temporary folder %s: %s"),
+ cls->folder_path, strerror(errno));
+ goto error;
+ }
+ outlen = st.st_size;
+
+ /* I think this is the right way to do this */
+ if (outlen>0
+ && (lseek(fd, outlen-1, SEEK_SET) == -1
+ || write(fd, "", 1) != 1
+ || fsync(fd) == -1
+ || lseek(fd, 0, SEEK_SET) == -1
+ || lseek(fdout, 0, SEEK_SET) == -1)) {
+ g_warning("Cannot sync spool folder: %s", strerror(errno));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not sync spool folder %s: %s"),
+ cls->folder_path, strerror(errno));
+ /* incase we ran out of room, remove any trailing space first */
+ ftruncate(fd, spoollen);
+ goto error;
+ }
+
+
+ /* now copy content back */
+ buffer = g_malloc(8192);
+ size = 1;
+ while (size>0) {
+ do {
+ size = read(fdout, buffer, 8192);
+ } while (size == -1 && errno == EINTR);
+
+ if (size > 0) {
+ p = buffer;
+ do {
+ sizeout = write(fd, p, size);
+ if (sizeout > 0) {
+ p+= sizeout;
+ size -= sizeout;
+ }
+ } while ((sizeout == -1 && errno == EINTR) && size > 0);
+ size = sizeout;
+ }
+
+ if (size == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not sync spool folder %s: %s\n"
+ "Folder may be corrupt, copy saved in `%s'"),
+ cls->folder_path, strerror(errno), tmpnam);
+ /* so we dont delete it */
+ close(fdout);
+ tmpname = NULL;
+ fdout = -1;
+ g_free(buffer);
+ goto error;
+ }
+ }
+
+ g_free(buffer);
+
+ d(printf("Closing folders\n"));
+
+ if (ftruncate(fd, outlen) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not sync spool folder %s: %s\n"
+ "Folder may be corrupt, copy saved in `%s'"),
+ cls->folder_path, strerror(errno), tmpnam);
+ close(fdout);
+ tmpname = NULL;
+ fdout = -1;
+ goto error;
+ }
+
+ if (close(fd) == -1) {
+ g_warning("Cannot close source folder: %s", strerror(errno));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not sync spool folder %s: %s\n"
+ "Folder may be corrupt, copy saved in `%s'"),
+ cls->folder_path, strerror(errno), tmpnam);
+ close(fdout);
+ tmpname = NULL;
+ fdout = -1;
+ fd = -1;
+ goto error;
+ }
+
+ close(fdout);
+ unlink(tmpname);
+
+ camel_object_unref((CamelObject *)mp);
+ camel_operation_end(NULL);
+
+ return 0;
+ error:
+ if (fd != -1)
+ close(fd);
+
+ if (fdout != -1)
+ close(fdout);
+
+ g_free(xevnew);
+
+ if (tmpname)
+ unlink(tmpname);
+ if (mp)
+ camel_object_unref((CamelObject *)mp);
+ if (info)
+ camel_folder_summary_info_free(s, (CamelMessageInfo *)info);
+
+ camel_operation_end(NULL);
+
+ return -1;
+}
+
+/* perform a quick sync - only system flags have changed */
+static int
+spool_summary_sync_quick(CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex)
+{
+ CamelFolderSummary *s = (CamelFolderSummary *)cls;
+ CamelMimeParser *mp = NULL;
+ int i, count;
+ CamelSpoolMessageInfo *info = NULL;
+ int fd = -1;
+ char *xevnew, *xevtmp;
+ const char *xev;
+ int len;
+ off_t lastpos;
+
+ d(printf("Performing quick summary sync\n"));
+
+ camel_operation_start(NULL, _("Synchronising folder"));
+
+ fd = open(cls->folder_path, O_RDWR);
+ if (fd == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not open folder to summarise: %s: %s"),
+ cls->folder_path, strerror(errno));
+
+ camel_operation_end(NULL);
+ return -1;
+ }
+
+ mp = camel_mime_parser_new();
+ camel_mime_parser_scan_from(mp, TRUE);
+ camel_mime_parser_scan_pre_from(mp, TRUE);
+ camel_mime_parser_init_with_fd(mp, fd);
+
+ count = camel_folder_summary_count(s);
+ for (i = 0; i < count; i++) {
+ int xevoffset;
+ int pc = (i+1)*100/count;
+
+ camel_operation_progress(NULL, pc);
+
+ info = (CamelSpoolMessageInfo *)camel_folder_summary_index(s, i);
+
+ g_assert(info);
+
+ d(printf("Checking message %s %08x\n", info->info.uid, info->info.flags));
+
+ if ((info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0) {
+ camel_folder_summary_info_free(s, (CamelMessageInfo *)info);
+ info = NULL;
+ continue;
+ }
+
+ d(printf("Updating message %s\n", info->info.uid));
+
+ camel_mime_parser_seek(mp, info->frompos, SEEK_SET);
+
+ if (camel_mime_parser_step(mp, 0, 0) != HSCAN_FROM) {
+ g_warning("Expected a From line here, didn't get it");
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Summary and folder mismatch, even after a sync"));
+ goto error;
+ }
+
+ if (camel_mime_parser_tell_start_from(mp) != info->frompos) {
+ g_warning("Didn't get the next message where I expected (%d) got %d instead",
+ (int)info->frompos, (int)camel_mime_parser_tell_start_from(mp));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Summary and folder mismatch, even after a sync"));
+ goto error;
+ }
+
+ if (camel_mime_parser_step(mp, 0, 0) == HSCAN_FROM_END) {
+ g_warning("camel_mime_parser_step failed (2)");
+ goto error;
+ }
+
+ xev = camel_mime_parser_header(mp, "X-Evolution", &xevoffset);
+ if (xev == NULL || camel_spool_summary_decode_x_evolution(cls, xev, NULL) == -1) {
+ g_warning("We're supposed to have a valid x-ev header, but we dont");
+ goto error;
+ }
+ xevnew = camel_spool_summary_encode_x_evolution(cls, (CamelMessageInfo *)info);
+ /* SIGH: encode_param_list is about the only function which folds headers by itself.
+ This should be fixed somehow differently (either parser doesn't fold headers,
+ or param_list doesn't, or something */
+ xevtmp = header_unfold(xevnew);
+ /* the raw header contains a leading ' ', so (dis)count that too */
+ if (strlen(xev)-1 != strlen(xevtmp)) {
+ g_free(xevnew);
+ g_free(xevtmp);
+ g_warning("Hmm, the xev headers shouldn't have changed size, but they did");
+ goto error;
+ }
+ g_free(xevtmp);
+
+ /* we write out the xevnew string, assuming its been folded identically to the original too! */
+
+ lastpos = lseek(fd, 0, SEEK_CUR);
+ lseek(fd, xevoffset+strlen("X-Evolution: "), SEEK_SET);
+ do {
+ len = write(fd, xevnew, strlen(xevnew));
+ } while (len == -1 && errno == EINTR);
+ lseek(fd, lastpos, SEEK_SET);
+ g_free(xevnew);
+
+ camel_mime_parser_drop_step(mp);
+ camel_mime_parser_drop_step(mp);
+
+ info->info.flags &= 0xffff;
+ camel_folder_summary_info_free(s, (CamelMessageInfo *)info);
+ }
+
+ d(printf("Closing folders\n"));
+
+ if (close(fd) == -1) {
+ g_warning("Cannot close source folder: %s", strerror(errno));
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not close source folder %s: %s"),
+ cls->folder_path, strerror(errno));
+ fd = -1;
+ goto error;
+ }
+
+ camel_object_unref((CamelObject *)mp);
+
+ camel_operation_end(NULL);
+
+ return 0;
+ error:
+ if (fd != -1)
+ close(fd);
+ if (mp)
+ camel_object_unref((CamelObject *)mp);
+ if (info)
+ camel_folder_summary_info_free(s, (CamelMessageInfo *)info);
+
+ camel_operation_end(NULL);
+
+ return -1;
+}
+
+static int
+spool_summary_sync(CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex)
+{
+ struct stat st;
+ CamelFolderSummary *s = (CamelFolderSummary *)cls;
+ int i, count;
+ int quick = TRUE, work=FALSE;
+ int ret;
+
+ /* first, sync ourselves up, just to make sure */
+ summary_update(cls, cls->folder_size, changeinfo, ex);
+ if (camel_exception_is_set(ex))
+ return -1;
+
+ count = camel_folder_summary_count(s);
+ if (count == 0)
+ return 0;
+
+ /* check what work we have to do, if any */
+ for (i=0;quick && i<count; i++) {
+ CamelMessageInfo *info = camel_folder_summary_index(s, i);
+ g_assert(info);
+ if ((expunge && (info->flags & CAMEL_MESSAGE_DELETED)) ||
+ (info->flags & (CAMEL_MESSAGE_FOLDER_NOXEV|CAMEL_MESSAGE_FOLDER_XEVCHANGE)))
+ quick = FALSE;
+ else
+ work |= (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0;
+ camel_folder_summary_info_free(s, info);
+ }
+
+ /* yuck i hate this logic, but its to simplify the 'all ok, update summary' and failover cases */
+ ret = -1;
+ if (quick) {
+ if (work) {
+ ret = spool_summary_sync_quick(cls, expunge, changeinfo, ex);
+ if (ret == -1) {
+ g_warning("failed a quick-sync, trying a full sync");
+ camel_exception_clear(ex);
+ }
+ } else {
+ ret = 0;
+ }
+ }
+
+ if (ret == -1)
+ ret = spool_summary_sync_full(cls, expunge, changeinfo, ex);
+ if (ret == -1)
+ return -1;
+
+ if (stat(cls->folder_path, &st) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Unknown error: %s"), strerror(errno));
+ return -1;
+ }
+
+ camel_folder_summary_touch(s);
+ s->time = st.st_mtime;
+ cls->folder_size = st.st_size;
+ /*camel_folder_summary_save(s);*/
+
+ return 0;
+}
+
+
+static CamelMessageInfo *
+spool_summary_add(CamelSpoolSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *ci, CamelException *ex)
+{
+ CamelMessageInfo *mi;
+ char *xev;
+
+ d(printf("Adding message to summary\n"));
+
+ mi = camel_folder_summary_add_from_message((CamelFolderSummary *)cls, msg);
+ if (mi) {
+ d(printf("Added, uid = %s\n", mi->uid));
+ if (info) {
+ CamelTag *tag = info->user_tags;
+ CamelFlag *flag = info->user_flags;
+
+ while (flag) {
+ camel_flag_set(&mi->user_flags, flag->name, TRUE);
+ flag = flag->next;
+ }
+
+ while (tag) {
+ camel_tag_set(&mi->user_tags, tag->name, tag->value);
+ tag = tag->next;
+ }
+
+ mi->flags = mi->flags | (info->flags & 0xffff);
+ }
+ mi->flags &= ~(CAMEL_MESSAGE_FOLDER_NOXEV|CAMEL_MESSAGE_FOLDER_FLAGGED);
+ xev = camel_spool_summary_encode_x_evolution(cls, mi);
+ camel_medium_set_header((CamelMedium *)msg, "X-Evolution", xev);
+ g_free(xev);
+ camel_folder_change_info_add_uid(ci, camel_message_info_uid(mi));
+ } else {
+ d(printf("Failed!\n"));
+ camel_exception_set(ex, 1, _("Unable to add message to summary: unknown reason"));
+ }
+ return mi;
+}
+
+static char *
+spool_summary_encode_x_evolution(CamelSpoolSummary *cls, const CamelMessageInfo *mi)
+{
+ GString *out = g_string_new("");
+ struct _header_param *params = NULL;
+ GString *val = g_string_new("");
+ CamelFlag *flag = mi->user_flags;
+ CamelTag *tag = mi->user_tags;
+ char *ret;
+ const char *p, *uidstr;
+ guint32 uid;
+
+ /* FIXME: work out what to do with uid's that aren't stored here? */
+ /* FIXME: perhaps make that a mbox folder only issue?? */
+ p = uidstr = camel_message_info_uid(mi);
+ while (*p && isdigit(*p))
+ p++;
+ if (*p == 0 && sscanf(uidstr, "%u", &uid) == 1) {
+ g_string_sprintf(out, "%08x-%04x", uid, mi->flags & 0xffff);
+ } else {
+ g_string_sprintf(out, "%s-%04x", uidstr, mi->flags & 0xffff);
+ }
+
+ if (flag || tag) {
+ val = g_string_new("");
+
+ if (flag) {
+ while (flag) {
+ g_string_append(val, flag->name);
+ if (flag->next)
+ g_string_append_c(val, ',');
+ flag = flag->next;
+ }
+ header_set_param(&params, "flags", val->str);
+ g_string_truncate(val, 0);
+ }
+ if (tag) {
+ while (tag) {
+ g_string_append(val, tag->name);
+ g_string_append_c(val, '=');
+ g_string_append(val, tag->value);
+ if (tag->next)
+ g_string_append_c(val, ',');
+ tag = tag->next;
+ }
+ header_set_param(&params, "tags", val->str);
+ }
+ g_string_free(val, TRUE);
+ header_param_list_format_append(out, params);
+ header_param_list_free(params);
+ }
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+static int
+spool_summary_decode_x_evolution(CamelSpoolSummary *cls, const char *xev, CamelMessageInfo *mi)
+{
+ struct _header_param *params, *scan;
+ guint32 uid, flags;
+ char *header;
+ int i;
+
+ /* check for uid/flags */
+ header = header_token_decode(xev);
+ if (header && strlen(header) == strlen("00000000-0000")
+ && sscanf(header, "%08x-%04x", &uid, &flags) == 2) {
+ char uidstr[20];
+ if (mi) {
+ sprintf(uidstr, "%u", uid);
+ camel_message_info_set_uid(mi, g_strdup(uidstr));
+ mi->flags = flags;
+ }
+ } else {
+ g_free(header);
+ return -1;
+ }
+ g_free(header);
+
+ if (mi == NULL)
+ return 0;
+
+ /* check for additional data */
+ header = strchr(xev, ';');
+ if (header) {
+ params = header_param_list_decode(header+1);
+ scan = params;
+ while (scan) {
+ if (!strcasecmp(scan->name, "flags")) {
+ char **flagv = g_strsplit(scan->value, ",", 1000);
+
+ for (i=0;flagv[i];i++) {
+ camel_flag_set(&mi->user_flags, flagv[i], TRUE);
+ }
+ g_strfreev(flagv);
+ } else if (!strcasecmp(scan->name, "tags")) {
+ char **tagv = g_strsplit(scan->value, ",", 10000);
+ char *val;
+
+ for (i=0;tagv[i];i++) {
+ val = strchr(tagv[i], '=');
+ if (val) {
+ *val++ = 0;
+ camel_tag_set(&mi->user_tags, tagv[i], val);
+ val[-1]='=';
+ }
+ }
+ g_strfreev(tagv);
+ }
+ scan = scan->next;
+ }
+ header_param_list_free(params);
+ }
+ return 0;
+}
+
diff --git a/camel/providers/local/camel-spool-summary.h b/camel/providers/local/camel-spool-summary.h
new file mode 100644
index 0000000000..8cf9daf710
--- /dev/null
+++ b/camel/providers/local/camel-spool-summary.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2001 Ximian Inc. (http://www.ximian.com)
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef _CAMEL_SPOOL_SUMMARY_H
+#define _CAMEL_SPOOL_SUMMARY_H
+
+#include <camel/camel-folder-summary.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-exception.h>
+#include <libibex/ibex.h>
+
+#define CAMEL_SPOOL_SUMMARY(obj) CAMEL_CHECK_CAST (obj, camel_spool_summary_get_type (), CamelSpoolSummary)
+#define CAMEL_SPOOL_SUMMARY_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_spool_summary_get_type (), CamelSpoolSummaryClass)
+#define CAMEL_IS_SPOOL_SUMMARY(obj) CAMEL_CHECK_TYPE (obj, camel_spool_summary_get_type ())
+
+typedef struct _CamelSpoolSummary CamelSpoolSummary;
+typedef struct _CamelSpoolSummaryClass CamelSpoolSummaryClass;
+
+/* extra summary flags */
+enum {
+ CAMEL_MESSAGE_FOLDER_NOXEV = 1<<17,
+ CAMEL_MESSAGE_FOLDER_XEVCHANGE = 1<<18,
+};
+
+typedef struct _CamelSpoolMessageInfo {
+ CamelMessageInfo info;
+
+ off_t frompos;
+} CamelSpoolMessageInfo;
+
+struct _CamelSpoolSummary {
+ CamelFolderSummary parent;
+
+ struct _CamelSpoolSummaryPrivate *priv;
+
+ char *folder_path; /* name of matching folder */
+
+ size_t folder_size;
+};
+
+struct _CamelSpoolSummaryClass {
+ CamelFolderSummaryClass parent_class;
+
+ int (*load)(CamelSpoolSummary *cls, int forceindex, CamelException *ex);
+ int (*check)(CamelSpoolSummary *cls, CamelFolderChangeInfo *changeinfo, CamelException *ex);
+ int (*sync)(CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex);
+ CamelMessageInfo *(*add)(CamelSpoolSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *, CamelException *ex);
+
+ char *(*encode_x_evolution)(CamelSpoolSummary *cls, const CamelMessageInfo *info);
+ int (*decode_x_evolution)(CamelSpoolSummary *cls, const char *xev, CamelMessageInfo *info);
+};
+
+guint camel_spool_summary_get_type (void);
+void camel_spool_summary_construct (CamelSpoolSummary *new, const char *filename, const char *spool_name, ibex *index);
+
+/* create the summary, in-memory only */
+CamelSpoolSummary *camel_spool_summary_new(const char *filename);
+
+/* load/check the summary */
+int camel_spool_summary_load(CamelSpoolSummary *cls, int forceindex, CamelException *ex);
+/* check for new/removed messages */
+int camel_spool_summary_check(CamelSpoolSummary *cls, CamelFolderChangeInfo *, CamelException *ex);
+/* perform a folder sync or expunge, if needed */
+int camel_spool_summary_sync(CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *, CamelException *ex);
+/* add a new message to the summary */
+CamelMessageInfo *camel_spool_summary_add(CamelSpoolSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *, CamelException *ex);
+
+/* generate an X-Evolution header line */
+char *camel_spool_summary_encode_x_evolution(CamelSpoolSummary *cls, const CamelMessageInfo *info);
+int camel_spool_summary_decode_x_evolution(CamelSpoolSummary *cls, const char *xev, CamelMessageInfo *info);
+
+/* utility functions - write headers to a file with optional X-Evolution header */
+int camel_spool_summary_write_headers(int fd, struct _header_raw *header, char *xevline);
+/* build a from line: FIXME: remove, or move to common code */
+char *camel_spool_summary_build_from(struct _header_raw *header);
+
+#endif /* ! _CAMEL_SPOOL_SUMMARY_H */
+
diff --git a/camel/providers/local/libcamellocal.urls b/camel/providers/local/libcamellocal.urls
index 35a7049145..207c19a98f 100644
--- a/camel/providers/local/libcamellocal.urls
+++ b/camel/providers/local/libcamellocal.urls
@@ -1,3 +1,4 @@
mh
mbox
maildir
+spool