From f14e85f771600855dedae6d29301d656ea86185b Mon Sep 17 00:00:00 2001 From: Not Zed Date: Tue, 12 Jun 2001 14:29:28 +0000 Subject: A new provider, for spool mailboxes. Mostly a cut and paste of the mbox 2001-06-12 Not Zed * 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 * providers/local/camel-local-provider.c (camel_provider_module_init): Added spool provider. svn path=/trunk/; revision=10198 --- camel/ChangeLog | 21 + camel/providers/local/Makefile.am | 10 +- camel/providers/local/camel-local-private.h | 14 + camel/providers/local/camel-local-provider.c | 15 + camel/providers/local/camel-spool-folder.c | 609 ++++++++++++ camel/providers/local/camel-spool-folder.h | 101 ++ camel/providers/local/camel-spool-store.c | 194 ++++ camel/providers/local/camel-spool-store.h | 67 ++ camel/providers/local/camel-spool-summary.c | 1273 ++++++++++++++++++++++++++ camel/providers/local/camel-spool-summary.h | 96 ++ camel/providers/local/libcamellocal.urls | 1 + camel/tests/folder/Makefile.am | 1 + camel/tests/folder/test1.c | 2 +- camel/tests/folder/test2.c | 10 +- camel/tests/folder/test4.c | 2 +- camel/tests/folder/test5.c | 2 +- camel/tests/lib/folders.c | 92 +- camel/tests/lib/folders.h | 4 +- camel/tests/message/Makefile.am | 2 + camel/tests/misc/Makefile.am | 1 + camel/tests/smime/Makefile.am | 1 + camel/tests/stream/Makefile.am | 1 + 22 files changed, 2468 insertions(+), 51 deletions(-) create mode 100644 camel/providers/local/camel-spool-folder.c create mode 100644 camel/providers/local/camel-spool-folder.h create mode 100644 camel/providers/local/camel-spool-store.c create mode 100644 camel/providers/local/camel-spool-store.h create mode 100644 camel/providers/local/camel-spool-summary.c create mode 100644 camel/providers/local/camel-spool-summary.h diff --git a/camel/ChangeLog b/camel/ChangeLog index d4f1d375bc..3ea6d1ba8e 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -1,3 +1,24 @@ +2001-06-12 Not Zed + + * 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 + + * providers/local/camel-local-provider.c + (camel_provider_module_init): Added spool provider. + 2001-06-07 Jon Trowbridge * camel-filter-driver.c (camel_filter_driver_filter_folder): Add a 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 + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include +#include +#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 + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#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 + * + * 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 + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#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 && iflags & (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 && iflags & 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(¶ms, "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(¶ms, "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 + * + * 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 +#include +#include +#include + +#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 diff --git a/camel/tests/folder/Makefile.am b/camel/tests/folder/Makefile.am index f6dd852dd8..1b3e3338ec 100644 --- a/camel/tests/folder/Makefile.am +++ b/camel/tests/folder/Makefile.am @@ -8,6 +8,7 @@ LDADD = \ $(top_builddir)/camel/libcamel.la \ $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/libibex/libibex.la \ + $(DB3_LDADD) \ $(GNOME_LIBDIR) \ $(top_builddir)/camel/tests/lib/libcameltest.a \ $(GNOMEUI_LIBS) $(INTLLIBS) $(EXTRA_GNOME_LIBS) diff --git a/camel/tests/folder/test1.c b/camel/tests/folder/test1.c index 4d589099f5..c1d3809852 100644 --- a/camel/tests/folder/test1.c +++ b/camel/tests/folder/test1.c @@ -38,7 +38,7 @@ int main(int argc, char **argv) for (i=0;i +#include +#include + #include "camel-test.h" #include "messages.h" #include "folders.h" @@ -40,9 +44,13 @@ int main(int argc, char **argv) for (i=0;i