diff options
Diffstat (limited to 'camel/providers/local/camel-spool-summary.c')
-rw-r--r-- | camel/providers/local/camel-spool-summary.c | 1273 |
1 files changed, 1273 insertions, 0 deletions
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(¶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; +} + |