/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Copyright (C) 2000-2003 Ximian Inc.
 *
 *  Authors: Michael Zucchi <notzed@ximian.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>

#include "libedataserver/e-iconv.h"

#include "camel-folder.h"
#include "camel-folder-summary.h"
#include "camel-file-utils.h"
#include "camel-mime-filter.h"
#include "camel-mime-filter-index.h"
#include "camel-mime-filter-charset.h"
#include "camel-mime-filter-basic.h"
#include "camel-mime-filter-html.h"
#include "camel-mime-message.h"
#include "camel-multipart.h"
#include "camel-private.h"
#include "camel-stream-mem.h"
#include "camel-stream-null.h"
#include "camel-stream-filter.h"
#include "camel-string-utils.h"

#include "libedataserver/md5-utils.h"
#include "libedataserver/e-memory.h"

#include <glib/gstdio.h>

static pthread_mutex_t info_lock = PTHREAD_MUTEX_INITIALIZER;

/* this lock is ONLY for the standalone messageinfo stuff */
#define GLOBAL_INFO_LOCK(i) pthread_mutex_lock(&info_lock)
#define GLOBAL_INFO_UNLOCK(i) pthread_mutex_unlock(&info_lock)


/* this should probably be conditional on it existing */
#define USE_BSEARCH

#define d(x)
#define io(x)			/* io debug */
#define w(x)

#if 0
extern int strdup_count, malloc_count, free_count;
#endif

#define CAMEL_FOLDER_SUMMARY_VERSION (13)

#define _PRIVATE(o) (((CamelFolderSummary *)(o))->priv)

/* trivial lists, just because ... */
struct _node {
	struct _node *next;
};

static struct _node *my_list_append(struct _node **list, struct _node *n);
static int my_list_size(struct _node **list);

static int summary_header_load(CamelFolderSummary *, FILE *);
static int summary_header_save(CamelFolderSummary *, FILE *);

static CamelMessageInfo * message_info_new_from_header(CamelFolderSummary *, struct _camel_header_raw *);
static CamelMessageInfo * message_info_new_from_parser(CamelFolderSummary *, CamelMimeParser *);
static CamelMessageInfo * message_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg);
static CamelMessageInfo * message_info_load(CamelFolderSummary *, FILE *);
static int		  message_info_save(CamelFolderSummary *, FILE *, CamelMessageInfo *);
static void		  message_info_free(CamelFolderSummary *, CamelMessageInfo *);

static CamelMessageContentInfo * content_info_new_from_header(CamelFolderSummary *, struct _camel_header_raw *);
static CamelMessageContentInfo * content_info_new_from_parser(CamelFolderSummary *, CamelMimeParser *);
static CamelMessageContentInfo * content_info_new_from_message(CamelFolderSummary *s, CamelMimePart *mp);
static CamelMessageContentInfo * content_info_load(CamelFolderSummary *, FILE *);
static int		         content_info_save(CamelFolderSummary *, FILE *, CamelMessageContentInfo *);
static void		         content_info_free(CamelFolderSummary *, CamelMessageContentInfo *);

static char *next_uid_string(CamelFolderSummary *s);

static CamelMessageContentInfo * summary_build_content_info(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimeParser *mp);
static CamelMessageContentInfo * summary_build_content_info_message(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimePart *object);

static void camel_folder_summary_class_init (CamelFolderSummaryClass *klass);
static void camel_folder_summary_init       (CamelFolderSummary *obj);
static void camel_folder_summary_finalize   (CamelObject *obj);

static CamelObjectClass *camel_folder_summary_parent;

static void
camel_folder_summary_init (CamelFolderSummary *s)
{
	struct _CamelFolderSummaryPrivate *p;

	p = _PRIVATE(s) = g_malloc0(sizeof(*p));

	p->filter_charset = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);

	s->message_info_size = sizeof(CamelMessageInfoBase);
	s->content_info_size = sizeof(CamelMessageContentInfo);

	s->message_info_chunks = NULL;
	s->content_info_chunks = NULL;

#if defined (DOESTRV) || defined (DOEPOOLV)
	s->message_info_strings = CAMEL_MESSAGE_INFO_LAST;
#endif

	s->version = CAMEL_FOLDER_SUMMARY_VERSION;
	s->flags = 0;
	s->time = 0;
	s->nextuid = 1;

	s->messages = g_ptr_array_new();
	s->messages_uid = g_hash_table_new(g_str_hash, g_str_equal);
	
	p->summary_lock = g_mutex_new();
	p->io_lock = g_mutex_new();
	p->filter_lock = g_mutex_new();
	p->alloc_lock = g_mutex_new();
	p->ref_lock = g_mutex_new();
}

static void free_o_name(void *key, void *value, void *data)
{
	camel_object_unref((CamelObject *)value);
	g_free(key);
}

static void
camel_folder_summary_finalize (CamelObject *obj)
{
	struct _CamelFolderSummaryPrivate *p;
	CamelFolderSummary *s = (CamelFolderSummary *)obj;

	p = _PRIVATE(obj);

	camel_folder_summary_clear(s);
	g_ptr_array_free(s->messages, TRUE);
	g_hash_table_destroy(s->messages_uid);

	g_hash_table_foreach(p->filter_charset, free_o_name, 0);
	g_hash_table_destroy(p->filter_charset);

	g_free(s->summary_path);

	if (s->message_info_chunks)
		e_memchunk_destroy(s->message_info_chunks);
	if (s->content_info_chunks)
		e_memchunk_destroy(s->content_info_chunks);

	if (p->filter_index)
		camel_object_unref((CamelObject *)p->filter_index);
	if (p->filter_64)
		camel_object_unref((CamelObject *)p->filter_64);
	if (p->filter_qp)
		camel_object_unref((CamelObject *)p->filter_qp);
	if (p->filter_uu)
		camel_object_unref((CamelObject *)p->filter_uu);
	if (p->filter_save)
		camel_object_unref((CamelObject *)p->filter_save);
	if (p->filter_html)
		camel_object_unref((CamelObject *)p->filter_html);

	if (p->filter_stream)
		camel_object_unref((CamelObject *)p->filter_stream);
	if (p->index)
		camel_object_unref((CamelObject *)p->index);
	
	g_mutex_free(p->summary_lock);
	g_mutex_free(p->io_lock);
	g_mutex_free(p->filter_lock);
	g_mutex_free(p->alloc_lock);
	g_mutex_free(p->ref_lock);
	
	g_free(p);
}

CamelType
camel_folder_summary_get_type (void)
{
	static CamelType type = CAMEL_INVALID_TYPE;
	
	if (type == CAMEL_INVALID_TYPE) {
		type = camel_type_register (camel_object_get_type (), "CamelFolderSummary",
					    sizeof (CamelFolderSummary),
					    sizeof (CamelFolderSummaryClass),
					    (CamelObjectClassInitFunc) camel_folder_summary_class_init,
					    NULL,
					    (CamelObjectInitFunc) camel_folder_summary_init,
					    (CamelObjectFinalizeFunc) camel_folder_summary_finalize);
	}
	
	return type;
}


/**
 * camel_folder_summary_new:
 * @folder: parent #CamelFolder object
 *
 * Create a new #CamelFolderSummary object.
 * 
 * Returns a new #CamelFolderSummary object
 **/
CamelFolderSummary *
camel_folder_summary_new (struct _CamelFolder *folder)
{
	CamelFolderSummary *new = CAMEL_FOLDER_SUMMARY ( camel_object_new (camel_folder_summary_get_type ()));

	new->folder = folder;

	return new;
}


/**
 * camel_folder_summary_set_filename:
 * @summary: a #CamelFolderSummary object
 * @filename: a filename
 * 
 * Set the filename where the summary will be loaded to/saved from.
 **/
void
camel_folder_summary_set_filename(CamelFolderSummary *s, const char *name)
{
	CAMEL_SUMMARY_LOCK(s, summary_lock);

	g_free(s->summary_path);
	s->summary_path = g_strdup(name);

	CAMEL_SUMMARY_UNLOCK(s, summary_lock);
}


/**
 * camel_folder_summary_set_index:
 * @summary: a #CamelFolderSummary object
 * @index: a #CamelIndex
 * 
 * Set the index used to index body content.  If the index is %NULL, or
 * not set (the default), no indexing of body content will take place.
 *
 * Unlike earlier behaviour, build_content need not be set to perform indexing.
 **/
void
camel_folder_summary_set_index(CamelFolderSummary *s, CamelIndex *index)
{
	struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);

	if (p->index)
		camel_object_unref((CamelObject *)p->index);

	p->index = index;
	if (index)
		camel_object_ref((CamelObject *)index);
}


/**
 * camel_folder_summary_set_build_content:
 * @summary: a #CamelFolderSummary object
 * @state: to build or not to build the content
 * 
 * Set a flag to tell the summary to build the content info summary
 * (#CamelMessageInfo.content).  The default is not to build content
 * info summaries.
 **/
void
camel_folder_summary_set_build_content(CamelFolderSummary *s, gboolean state)
{
	s->build_content = state;
}


/**
 * camel_folder_summary_count:
 * @summary: a #CamelFolderSummary object
 * 
 * Get the number of summary items stored in this summary.
 * 
 * Returns the number of items in the summary
 **/
int
camel_folder_summary_count(CamelFolderSummary *s)
{
	return s->messages->len;
}


/**
 * camel_folder_summary_index:
 * @summary: a #CamelFolderSummary object
 * @index: item index
 * 
 * Retrieve a summary item by index number.
 *
 * A referenced to the summary item is returned, which may be
 * ref'd or free'd as appropriate.
 * 
 * Returns the summary item, or %NULL if @index is out of range
 **/
CamelMessageInfo *
camel_folder_summary_index(CamelFolderSummary *s, int i)
{
	CamelMessageInfo *info = NULL;

	CAMEL_SUMMARY_LOCK(s, summary_lock);
	CAMEL_SUMMARY_LOCK(s, ref_lock);

	if (i<s->messages->len)
		info = g_ptr_array_index(s->messages, i);

	if (info)
		info->refcount++;

	CAMEL_SUMMARY_UNLOCK(s, ref_lock);
	CAMEL_SUMMARY_UNLOCK(s, summary_lock);

	return info;
}


/**
 * camel_folder_summary_array:
 * @summary: a #CamelFolderSummary object
 * 
 * Obtain a copy of the summary array.  This is done atomically,
 * so cannot contain empty entries.
 *
 * It must be freed using #camel_folder_summary_array_free.
 *
 * Returns a #GPtrArray of #CamelMessageInfo items
 **/
GPtrArray *
camel_folder_summary_array(CamelFolderSummary *s)
{
	CamelMessageInfo *info;
	GPtrArray *res = g_ptr_array_new();
	int i;
	
	CAMEL_SUMMARY_LOCK(s, summary_lock);
	CAMEL_SUMMARY_LOCK(s, ref_lock);

	g_ptr_array_set_size(res, s->messages->len);
	for (i=0;i<s->messages->len;i++) {
		info = res->pdata[i] = g_ptr_array_index(s->messages, i);
		info->refcount++;
	}

	CAMEL_SUMMARY_UNLOCK(s, ref_lock);
	CAMEL_SUMMARY_UNLOCK(s, summary_lock);

	return res;
}


/**
 * camel_folder_summary_array_free:
 * @summary: a #CamelFolderSummary object
 * @array: array of #CamelMessageInfo items as returned from #camel_folder_summary_array
 * 
 * Free the folder summary array.
 **/
void
camel_folder_summary_array_free(CamelFolderSummary *s, GPtrArray *array)
{
	int i;

	/* FIXME: do the locking around the whole lot to make it faster */
	for (i=0;i<array->len;i++)
		camel_message_info_free(array->pdata[i]);

	g_ptr_array_free(array, TRUE);
}


/**
 * camel_folder_summary_uid:
 * @summary: a #CamelFolderSummary object
 * @uid: a uid
 * 
 * Retrieve a summary item by uid.
 *
 * A referenced to the summary item is returned, which may be
 * ref'd or free'd as appropriate.
 * 
 * Returns the summary item, or %NULL if the uid @uid is not available
 **/
CamelMessageInfo *
camel_folder_summary_uid(CamelFolderSummary *s, const char *uid)
{
	CamelMessageInfo *info;

	CAMEL_SUMMARY_LOCK(s, summary_lock);
	CAMEL_SUMMARY_LOCK(s, ref_lock);

	info = g_hash_table_lookup(s->messages_uid, uid);

	if (info)
		info->refcount++;

	CAMEL_SUMMARY_UNLOCK(s, ref_lock);
	CAMEL_SUMMARY_UNLOCK(s, summary_lock);

	return info;
}


/**
 * camel_folder_summary_next_uid:
 * @summary: a #CamelFolderSummary object
 * 
 * Generate a new unique uid value as an integer.  This
 * may be used to create a unique sequence of numbers.
 * 
 * Returns the next unique uid value
 **/
guint32
camel_folder_summary_next_uid(CamelFolderSummary *s)
{
	guint32 uid;


	CAMEL_SUMMARY_LOCK(s, summary_lock);

	uid = s->nextuid++;

	CAMEL_SUMMARY_UNLOCK(s, summary_lock);

	/* FIXME: sync this to disk */
/*	summary_header_save(s);*/
	return uid;
}


/**
 * camel_folder_summary_set_uid:
 * @summary: a #CamelFolderSummary object
 * @uid: The next minimum uid to assign.  To avoid clashing
 * uid's, set this to the uid of a given messages + 1.
 * 
 * Set the next minimum uid available.  This can be used to
 * ensure new uid's do not clash with existing uid's.
 **/
void
camel_folder_summary_set_uid(CamelFolderSummary *s, guint32 uid)
{
	/* TODO: sync to disk? */
	CAMEL_SUMMARY_LOCK(s, summary_lock);

	s->nextuid = MAX(s->nextuid, uid);

	CAMEL_SUMMARY_UNLOCK(s, summary_lock);
}


/**
 * camel_folder_summary_next_uid_string:
 * @summary: a #CamelFolderSummary object
 *
 * Retrieve the next uid, but as a formatted string.
 *
 * Returns the next uid as an unsigned integer string.
 * This string must be freed by the caller.
 **/
char *
camel_folder_summary_next_uid_string(CamelFolderSummary *s)
{
	return ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->next_uid_string(s);
}

/* loads the content descriptions, recursively */
static CamelMessageContentInfo *
perform_content_info_load(CamelFolderSummary *s, FILE *in)
{
	int i;
	guint32 count;
	CamelMessageContentInfo *ci, *part;

	ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_load(s, in);
	if (ci == NULL)
		return NULL;

	if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500) {
		camel_folder_summary_content_info_free(s, ci);
		return NULL;
	}

	for (i=0;i<count;i++) {
		part = perform_content_info_load(s, in);
		if (part) {
			my_list_append((struct _node **)&ci->childs, (struct _node *)part);
			part->parent = ci;
		} else {
			d(fprintf (stderr, "Summary file format messed up?"));
			camel_folder_summary_content_info_free(s, ci);
			return NULL;
		}
	}
	return ci;
}


/**
 * camel_folder_summary_load:
 * @summary: a #CamelFolderSummary object
 *
 * Load the summary from disk.
 *
 * Returns %0 on success or %-1 on fail
 **/
int
camel_folder_summary_load(CamelFolderSummary *s)
{
	FILE *in;
	int i;
	CamelMessageInfo *mi;

	if (s->summary_path == NULL)
		return 0;

	in = g_fopen(s->summary_path, "rb");
	if (in == NULL)
		return -1;

	CAMEL_SUMMARY_LOCK(s, io_lock);
	if ( ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_load(s, in) == -1)
		goto error;

	/* now read in each message ... */
	for (i=0;i<s->saved_count;i++) {
		mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_load(s, in);

		if (mi == NULL)
			goto error;

		/* FIXME: this should be done differently, how i don't know */
		if (s->build_content) {
			((CamelMessageInfoBase *)mi)->content = perform_content_info_load(s, in);
			if (((CamelMessageInfoBase *)mi)->content == NULL) {
				camel_message_info_free(mi);
				goto error;
			}
		}

		camel_folder_summary_add(s, mi);
	}

	CAMEL_SUMMARY_UNLOCK(s, io_lock);
	
	if (fclose (in) != 0)
		return -1;

	s->flags &= ~CAMEL_SUMMARY_DIRTY;

	return 0;

error:
	if (errno != EINVAL)
		g_warning ("Cannot load summary file: `%s': %s", s->summary_path, g_strerror (errno));
	
	CAMEL_SUMMARY_UNLOCK(s, io_lock);
	fclose (in);
	s->flags |= ~CAMEL_SUMMARY_DIRTY;

	return -1;
}

/* saves the content descriptions, recursively */
static int
perform_content_info_save(CamelFolderSummary *s, FILE *out, CamelMessageContentInfo *ci)
{
	CamelMessageContentInfo *part;

	if (((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (s)))->content_info_save (s, out, ci) == -1)
		return -1;
	
	if (camel_file_util_encode_uint32 (out, my_list_size ((struct _node **)&ci->childs)) == -1)
		return -1;
	
	part = ci->childs;
	while (part) {
		if (perform_content_info_save (s, out, part) == -1)
			return -1;
		part = part->next;
	}
	
	return 0;
}


/**
 * camel_folder_summary_save:
 * @summary: a #CamelFolderSummary object
 * 
 * Writes the summary to disk.  The summary is only written if changes
 * have occured.
 * 
 * Returns %0 on success or %-1 on fail
 **/
int
camel_folder_summary_save(CamelFolderSummary *s)
{
	FILE *out;
	int fd, i;
	guint32 count;
	CamelMessageInfo *mi;
	char *path;

	g_assert(s->message_info_size >= sizeof(CamelMessageInfoBase));

	if (s->summary_path == NULL
	    || (s->flags & CAMEL_SUMMARY_DIRTY) == 0)
		return 0;

	path = alloca(strlen(s->summary_path)+4);
	sprintf(path, "%s~", s->summary_path);
	fd = g_open(path, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0600);
	if (fd == -1)
		return -1;
	out = fdopen(fd, "wb");
	if (out == NULL) {
		i = errno;
		g_unlink(path);
		close(fd);
		errno = i;
		return -1;
	}

	io(printf("saving header\n"));

	CAMEL_SUMMARY_LOCK(s, io_lock);

	if (((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_save(s, out) == -1)
		goto exception;
	
	/* now write out each message ... */
	/* we check ferorr when done for i/o errors */
	count = s->messages->len;
	for (i = 0; i < count; i++) {
		mi = s->messages->pdata[i];
		if (((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (s)))->message_info_save (s, out, mi) == -1)
			goto exception;
		
		if (s->build_content) {
			if (perform_content_info_save (s, out, ((CamelMessageInfoBase *)mi)->content) == -1)
				goto exception;
		}
	}
	
	if (fflush (out) != 0 || fsync (fileno (out)) == -1)
		goto exception;
	
	fclose (out);
	
	CAMEL_SUMMARY_UNLOCK(s, io_lock);
	
#ifdef G_OS_WIN32
	g_unlink(s->summary_path);
#endif
	if (g_rename(path, s->summary_path) == -1) {
		i = errno;
		g_unlink(path);
		errno = i;
		return -1;
	}
	
	s->flags &= ~CAMEL_SUMMARY_DIRTY;
	return 0;
	
 exception:
	
	i = errno;
	
	fclose (out);
	
	CAMEL_SUMMARY_UNLOCK(s, io_lock);
	
	g_unlink (path);
	errno = i;
	
	return -1;
}


/**
 * camel_folder_summary_header_load:
 * @summary: a #CamelFolderSummary object
 * 
 * Only load the header information from the summary,
 * keep the rest on disk.  This should only be done on
 * a fresh summary object.
 * 
 * Returns %0 on success or %-1 on fail
 **/
int
camel_folder_summary_header_load(CamelFolderSummary *s)
{
	FILE *in;
	int ret;

	if (s->summary_path == NULL)
		return 0;

	in = g_fopen(s->summary_path, "rb");
	if (in == NULL)
		return -1;

	CAMEL_SUMMARY_LOCK(s, io_lock);
	ret = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_load(s, in);
	CAMEL_SUMMARY_UNLOCK(s, io_lock);
	
	fclose(in);
	s->flags &= ~CAMEL_SUMMARY_DIRTY;
	return ret;
}

static int
summary_assign_uid(CamelFolderSummary *s, CamelMessageInfo *info)
{
	const char *uid;
	CamelMessageInfo *mi;

	uid = camel_message_info_uid(info);
	if (uid == NULL || uid[0] == 0) {
		g_free(info->uid);
		uid = info->uid = camel_folder_summary_next_uid_string(s);
	}

	CAMEL_SUMMARY_LOCK(s, summary_lock);

	while ((mi = g_hash_table_lookup(s->messages_uid, uid))) {
		CAMEL_SUMMARY_UNLOCK(s, summary_lock);
		if (mi == info)
			return 0;
		d(printf ("Trying to insert message with clashing uid (%s).  new uid re-assigned", camel_message_info_uid(info)));
		g_free(info->uid);
		uid = info->uid = camel_folder_summary_next_uid_string(s);
		camel_message_info_set_flags(info, CAMEL_MESSAGE_FOLDER_FLAGGED, CAMEL_MESSAGE_FOLDER_FLAGGED);
		CAMEL_SUMMARY_LOCK(s, summary_lock);
	}

	CAMEL_SUMMARY_UNLOCK(s, summary_lock);

	return 1;
}


/**
 * camel_folder_summary_add:
 * @summary: a #CamelFolderSummary object
 * @info: a #CamelMessageInfo
 * 
 * Adds a new @info record to the summary.  If @info->uid is %NULL,
 * then a new uid is automatically re-assigned by calling
 * #camel_folder_summary_next_uid_string.
 *
 * The @info record should have been generated by calling one of the
 * info_new_*() functions, as it will be free'd based on the summary
 * class.  And MUST NOT be allocated directly using malloc.
 **/
void
camel_folder_summary_add(CamelFolderSummary *s, CamelMessageInfo *info)
{
	if (info == NULL)
		return;

	if (summary_assign_uid(s, info) == 0)
		return;

	CAMEL_SUMMARY_LOCK(s, summary_lock);

/* unnecessary for pooled vectors */
#ifdef DOESTRV
	/* this is vitally important, and also if this is ever modified, then
	   the hash table needs to be resynced */
	info->strings = e_strv_pack(info->strings);
#endif

	g_ptr_array_add(s->messages, info);
	g_hash_table_insert(s->messages_uid, (char *)camel_message_info_uid(info), info);
	s->flags |= CAMEL_SUMMARY_DIRTY;

	CAMEL_SUMMARY_UNLOCK(s, summary_lock);
}


/**
 * camel_folder_summary_add_from_header:
 * @summary: a #CamelFolderSummary object
 * @headers: rfc822 headers
 * 
 * Build a new info record based on a set of headers, and add it to
 * the summary.
 *
 * Note that this function should not be used if build_content_info
 * has been specified for this summary.
 * 
 * Returns the newly added record
 **/
CamelMessageInfo *
camel_folder_summary_add_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
{
	CamelMessageInfo *info = camel_folder_summary_info_new_from_header(s, h);

	camel_folder_summary_add(s, info);

	return info;
}


/**
 * camel_folder_summary_add_from_parser:
 * @summary: a #CamelFolderSummary object
 * @parser: a #CamelMimeParser object
 * 
 * Build a new info record based on the current position of a #CamelMimeParser.
 *
 * The parser should be positioned before the start of the message to summarise.
 * This function may be used if build_contnet_info or an index has been
 * specified for the summary.
 * 
 * Returns the newly added record
 **/
CamelMessageInfo *
camel_folder_summary_add_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
{
	CamelMessageInfo *info = camel_folder_summary_info_new_from_parser(s, mp);

	camel_folder_summary_add(s, info);

	return info;
}


/**
 * camel_folder_summary_add_from_message:
 * @summary: a #CamelFolderSummary object
 * @message: a #CamelMimeMessage object
 * 
 * Add a summary item from an existing message.
 * 
 * Returns the newly added record
 **/
CamelMessageInfo *
camel_folder_summary_add_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
{
	CamelMessageInfo *info = camel_folder_summary_info_new_from_message(s, msg);

	camel_folder_summary_add(s, info);

	return info;
}


/**
 * camel_folder_summary_info_new_from_header:
 * @summary: a #CamelFolderSummary object
 * @headers: rfc822 headers
 * 
 * Create a new info record from a header.
 *
 * Returns the newly allocated record which must be freed with
 * #camel_message_info_free
 **/
CamelMessageInfo *
camel_folder_summary_info_new_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
{
	return ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_header(s, h);
}


/**
 * camel_folder_summary_info_new_from_parser:
 * @summary: a #CamelFolderSummary object
 * @parser: a #CamelMimeParser object
 * 
 * Create a new info record from a parser.  If the parser cannot
 * determine a uid, then none will be assigned.
 *
 * If indexing is enabled, and the parser cannot determine a new uid, then
 * one is automatically assigned.
 *
 * If indexing is enabled, then the content will be indexed based
 * on this new uid.  In this case, the message info MUST be
 * added using :add().
 *
 * Once complete, the parser will be positioned at the end of
 * the message.
 *
 * Returns the newly allocated record which must be freed with
 * #camel_message_info_free
 **/
CamelMessageInfo *
camel_folder_summary_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
{
	CamelMessageInfo *info = NULL;
	char *buffer;
	size_t len;
	struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
	off_t start;
	CamelIndexName *name = NULL;

	/* should this check the parser is in the right state, or assume it is?? */

	start = camel_mime_parser_tell(mp);
	if (camel_mime_parser_step(mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_EOF) {
		info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_parser(s, mp);

		camel_mime_parser_unstep(mp);

		/* assign a unique uid, this is slightly 'wrong' as we do not really
		 * know if we are going to store this in the summary, but no matter */
		if (p->index)
			summary_assign_uid(s, info);

		CAMEL_SUMMARY_LOCK(s, filter_lock);

		if (p->index) {
			if (p->filter_index == NULL)
				p->filter_index = camel_mime_filter_index_new_index(p->index);
			camel_index_delete_name(p->index, camel_message_info_uid(info));
			name = camel_index_add_name(p->index, camel_message_info_uid(info));
			camel_mime_filter_index_set_name(p->filter_index, name);
		}

		/* always scan the content info, even if we dont save it */
		((CamelMessageInfoBase *)info)->content = summary_build_content_info(s, info, mp);

		if (name && p->index) {
			camel_index_write_name(p->index, name);
			camel_object_unref((CamelObject *)name);
			camel_mime_filter_index_set_name(p->filter_index, NULL);
		}

		CAMEL_SUMMARY_UNLOCK(s, filter_lock);

		((CamelMessageInfoBase *)info)->size = camel_mime_parser_tell(mp) - start;
	}
	return info;
}


/**
 * camel_folder_summary_info_new_from_message:
 * @summary: a #CamelFodlerSummary object
 * @message: a #CamelMimeMessage object
 * 
 * Create a summary item from a message.
 * 
 * Returns the newly allocated record which must be freed using
 * #camel_message_info_free
 **/
CamelMessageInfo *
camel_folder_summary_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
{
	CamelMessageInfo *info;
	struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
	CamelIndexName *name = NULL;

	info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_message(s, msg);

	/* assign a unique uid, this is slightly 'wrong' as we do not really
	 * know if we are going to store this in the summary, but we need it set for indexing */
	if (p->index)
		summary_assign_uid(s, info);

	CAMEL_SUMMARY_LOCK(s, filter_lock);

	if (p->index) {
		if (p->filter_index == NULL)
			p->filter_index = camel_mime_filter_index_new_index(p->index);
		camel_index_delete_name(p->index, camel_message_info_uid(info));
		name = camel_index_add_name(p->index, camel_message_info_uid(info));
		camel_mime_filter_index_set_name(p->filter_index, name);

		if (p->filter_stream == NULL) {
			CamelStream *null = camel_stream_null_new();

			p->filter_stream = camel_stream_filter_new_with_stream(null);
			camel_object_unref((CamelObject *)null);
		}
	}

	((CamelMessageInfoBase *)info)->content = summary_build_content_info_message(s, info, (CamelMimePart *)msg);

	if (name) {
		camel_index_write_name(p->index, name);
		camel_object_unref((CamelObject *)name);
		camel_mime_filter_index_set_name(p->filter_index, NULL);
	}

	CAMEL_SUMMARY_UNLOCK(s, filter_lock);

	return info;
}


/**
 * camel_folder_summary_content_info_free:
 * @summary: a #CamelFolderSummary object
 * @ci: a #CamelMessageContentInfo
 * 
 * Free the content info @ci, and all associated memory.
 **/
void
camel_folder_summary_content_info_free(CamelFolderSummary *s, CamelMessageContentInfo *ci)
{
	CamelMessageContentInfo *pw, *pn;

	pw = ci->childs;
	((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_free(s, ci);
	while (pw) {
		pn = pw->next;
		camel_folder_summary_content_info_free(s, pw);
		pw = pn;
	}
}


/**
 * camel_folder_summary_touch:
 * @summary: a #CamelFolderSummary object
 * 
 * Mark the summary as changed, so that a save will force it to be
 * written back to disk.
 **/
void
camel_folder_summary_touch(CamelFolderSummary *s)
{
	CAMEL_SUMMARY_LOCK(s, summary_lock);
	s->flags |= CAMEL_SUMMARY_DIRTY;
	CAMEL_SUMMARY_UNLOCK(s, summary_lock);
}


/**
 * camel_folder_summary_clear:
 * @summary: a #CamelFolderSummary object
 * 
 * Empty the summary contents.
 **/
void
camel_folder_summary_clear(CamelFolderSummary *s)
{
	int i;

	CAMEL_SUMMARY_LOCK(s, summary_lock);
	if (camel_folder_summary_count(s) == 0) {
		CAMEL_SUMMARY_UNLOCK(s, summary_lock);
		return;
	}

	for (i=0;i<s->messages->len;i++)
		camel_message_info_free(s->messages->pdata[i]);

	g_ptr_array_set_size(s->messages, 0);
	g_hash_table_destroy(s->messages_uid);
	s->messages_uid = g_hash_table_new(g_str_hash, g_str_equal);
	s->flags |= CAMEL_SUMMARY_DIRTY;
	CAMEL_SUMMARY_UNLOCK(s, summary_lock);
}


/**
 * camel_folder_summary_remove:
 * @summary: a #CamelFolderSummary object
 * @info: a #CamelMessageInfo
 * 
 * Remove a specific @info record from the summary.
 **/
void
camel_folder_summary_remove(CamelFolderSummary *s, CamelMessageInfo *info)
{
	CAMEL_SUMMARY_LOCK(s, summary_lock);
	g_hash_table_remove(s->messages_uid, camel_message_info_uid(info));
	g_ptr_array_remove(s->messages, info);
	s->flags |= CAMEL_SUMMARY_DIRTY;
	CAMEL_SUMMARY_UNLOCK(s, summary_lock);

	camel_message_info_free(info);
}


/**
 * camel_folder_summary_remove_uid:
 * @summary: a #CamelFolderSummary object
 * @uid: a uid
 * 
 * Remove a specific info record from the summary, by @uid.
 **/
void
camel_folder_summary_remove_uid(CamelFolderSummary *s, const char *uid)
{
        CamelMessageInfo *oldinfo;
        char *olduid;

	CAMEL_SUMMARY_LOCK(s, summary_lock);
	CAMEL_SUMMARY_LOCK(s, ref_lock);
        if (g_hash_table_lookup_extended(s->messages_uid, uid, (void *)&olduid, (void *)&oldinfo)) {
		/* make sure it doesn't vanish while we're removing it */
		oldinfo->refcount++;
		CAMEL_SUMMARY_UNLOCK(s, ref_lock);
		CAMEL_SUMMARY_UNLOCK(s, summary_lock);
		camel_folder_summary_remove(s, oldinfo);
		camel_message_info_free(oldinfo);
        } else {
		CAMEL_SUMMARY_UNLOCK(s, ref_lock);
		CAMEL_SUMMARY_UNLOCK(s, summary_lock);
	}
}


/**
 * camel_folder_summary_remove_index:
 * @summary: a #CamelFolderSummary object
 * @index: record index
 * 
 * Remove a specific info record from the summary, by index.
 **/
void
camel_folder_summary_remove_index(CamelFolderSummary *s, int index)
{
	CAMEL_SUMMARY_LOCK(s, summary_lock);
	if (index < s->messages->len) {
		CamelMessageInfo *info = s->messages->pdata[index];

		g_hash_table_remove(s->messages_uid, camel_message_info_uid(info));
		g_ptr_array_remove_index(s->messages, index);
		s->flags |= CAMEL_SUMMARY_DIRTY;

		CAMEL_SUMMARY_UNLOCK(s, summary_lock);
		camel_message_info_free(info);
	} else {
		CAMEL_SUMMARY_UNLOCK(s, summary_lock);
	}
}


/**
 * camel_folder_summary_remove_range:
 * @summary: a #CamelFolderSummary object
 * @start: initial index
 * @end: last index to remove
 * 
 * Removes an indexed range of info records.
 **/
void
camel_folder_summary_remove_range(CamelFolderSummary *s, int start, int end)
{
	if (end < start)
		return;

	CAMEL_SUMMARY_LOCK(s, summary_lock);
	if (start < s->messages->len) {
		CamelMessageInfo **infos;
		int i;

		end = MIN(end+1, s->messages->len);
		infos = g_malloc((end-start)*sizeof(infos[0]));

		for (i=start;i<end;i++) {
			CamelMessageInfo *info = s->messages->pdata[i];

			infos[i-start] = info;
			g_hash_table_remove(s->messages_uid, camel_message_info_uid(info));
		}

		memmove(s->messages->pdata+start, s->messages->pdata+end, (s->messages->len-end)*sizeof(s->messages->pdata[0]));
		g_ptr_array_set_size(s->messages, s->messages->len - (end - start));
		s->flags |= CAMEL_SUMMARY_DIRTY;

		CAMEL_SUMMARY_UNLOCK(s, summary_lock);

		for (i=start;i<end;i++)
			camel_message_info_free(infos[i-start]);
		g_free(infos);
	} else {
		CAMEL_SUMMARY_UNLOCK(s, summary_lock);
	}
}

/* should be sorted, for binary search */
/* This is a tokenisation mechanism for strings written to the
   summary - to save space.
   This list can have at most 31 words. */
static char * tokens[] = {
	"7bit",
	"8bit",
	"alternative",
	"application",
	"base64",
	"boundary",
	"charset",
	"filename",
	"html",
	"image",
	"iso-8859-1",
	"iso-8859-8",
	"message",
	"mixed",
	"multipart",
	"name",
	"octet-stream",
	"parallel",
	"plain",
	"postscript",
	"quoted-printable",
	"related",
	"rfc822",
	"text",
	"us-ascii",		/* 25 words */
};

#define tokens_len (sizeof(tokens)/sizeof(tokens[0]))

/* baiscally ...
    0 = null
    1-tokens_len == tokens[id-1]
    >=32 string, length = n-32
*/

#ifdef USE_BSEARCH
static int
token_search_cmp(char *key, char **index)
{
	d(printf("comparing '%s' to '%s'\n", key, *index));
	return strcmp(key, *index);
}
#endif

/**
 * camel_folder_summary_encode_token:
 * @out: output FILE pointer
 * @str: string token to encode
 * 
 * Encode a string value, but use tokenisation and compression
 * to reduce the size taken for common mailer words.  This
 * can still be used to encode normal strings as well.
 * 
 * Returns %0 on success or %-1 on fail
 **/
int
camel_folder_summary_encode_token(FILE *out, const char *str)
{
	io(printf("Encoding token: '%s'\n", str));

	if (str == NULL) {
		return camel_file_util_encode_uint32(out, 0);
	} else {
		int len = strlen(str);
		int i, token=-1;

		if (len <= 16) {
			char lower[32];
			char **match;

			for (i=0;i<len;i++)
				lower[i] = tolower(str[i]);
			lower[i] = 0;
#ifdef USE_BSEARCH
			match = bsearch(lower, tokens, tokens_len, sizeof(char *), (int (*)(const void *, const void *))token_search_cmp);
			if (match)
				token = match-tokens;
#else
			for (i=0;i<tokens_len;i++) {
				if (!strcmp(tokens[i], lower)) {
					token = i;
					break;
				}
			}
#endif
		}
		if (token != -1) {
			return camel_file_util_encode_uint32(out, token+1);
		} else {
			if (camel_file_util_encode_uint32(out, len+32) == -1)
				return -1;
			if (fwrite(str, len, 1, out) != 1)
				return -1;
		}
	}
	return 0;
}


/**
 * camel_folder_summary_decode_token:
 * @in: input FILE pointer
 * @str: string pointer to hold the decoded result
 * 
 * Decode a token value.
 * 
 * Returns %0 on success or %-1 on fail
 **/
int
camel_folder_summary_decode_token(FILE *in, char **str)
{
	char *ret;
	guint32 len;

	io(printf("Decode token ...\n"));
	
	if (camel_file_util_decode_uint32(in, &len) == -1) {
		io(printf ("Could not decode token from file"));
		*str = NULL;
		return -1;
	}

	if (len<32) {
		if (len <= 0) {
			ret = NULL;
		} else if (len<= tokens_len) {
			ret = g_strdup(tokens[len-1]);
		} else {
			io(printf ("Invalid token encountered: %d", len));
			*str = NULL;
			return -1;
		}
	} else if (len > 10240) {
		io(printf ("Got broken string header length: %d bytes", len));
		*str = NULL;
		return -1;
	} else {
		len -= 32;
		ret = g_malloc(len+1);
		if (len > 0 && fread(ret, len, 1, in) != 1) {
			g_free(ret);
			*str = NULL;
			return -1;
		}
		ret[len]=0;
	}

	io(printf("Token = '%s'\n", ret));

	*str = ret;
	return 0;
}

static struct _node *
my_list_append(struct _node **list, struct _node *n)
{
	struct _node *ln = (struct _node *)list;
	while (ln->next)
		ln = ln->next;
	n->next = 0;
	ln->next = n;
	return n;
}

static int
my_list_size(struct _node **list)
{
	int len = 0;
	struct _node *ln = (struct _node *)list;
	while (ln->next) {
		ln = ln->next;
		len++;
	}
	return len;
}

static int
summary_header_load(CamelFolderSummary *s, FILE *in)
{
	fseek(in, 0, SEEK_SET);

	io(printf("Loading header\n"));

	if (camel_file_util_decode_fixed_int32(in, &s->version) == -1)
		return -1;

	/* Legacy version check, before version 12 we have no upgrade knowledge */
	if ((s->version > 0xff) && (s->version & 0xff) < 12) {
		io(printf ("Summary header version mismatch"));
		errno = EINVAL;
		return -1;
	}

	if (!(s->version < 0x100 && s->version >= 13))
		io(printf("Loading legacy summary\n"));
	else
		io(printf("loading new-format summary\n"));

	/* legacy version */
	if (camel_file_util_decode_fixed_int32(in, &s->flags) == -1
	    || camel_file_util_decode_fixed_int32(in, &s->nextuid) == -1
	    || camel_file_util_decode_time_t(in, &s->time) == -1
	    || camel_file_util_decode_fixed_int32(in, &s->saved_count) == -1) {
		return -1;
	}

	/* version 13 */
	if (s->version < 0x100 && s->version >= 13
	    && (camel_file_util_decode_fixed_int32(in, &s->unread_count) == -1
		|| camel_file_util_decode_fixed_int32(in, &s->deleted_count) == -1
		|| camel_file_util_decode_fixed_int32(in, &s->junk_count) == -1)) {
		return -1;
	}

	return 0;
}

static int
summary_header_save(CamelFolderSummary *s, FILE *out)
{
	int unread = 0, deleted = 0, junk = 0, count, i;

	fseek(out, 0, SEEK_SET);

	io(printf("Savining header\n"));

	/* we always write out the current version */
	camel_file_util_encode_fixed_int32(out, CAMEL_FOLDER_SUMMARY_VERSION);
	camel_file_util_encode_fixed_int32(out, s->flags);
	camel_file_util_encode_fixed_int32(out, s->nextuid);
	camel_file_util_encode_time_t(out, s->time);

	count = camel_folder_summary_count(s);
	for (i=0; i<count; i++) {
		CamelMessageInfo *info = camel_folder_summary_index(s, i);
		guint32 flags;

		if (info == NULL)
			continue;

		flags = camel_message_info_flags(info);
		if ((flags & CAMEL_MESSAGE_SEEN) == 0)
			unread++;
		if ((flags & CAMEL_MESSAGE_DELETED) != 0)
			deleted++;
		if ((flags & CAMEL_MESSAGE_JUNK) != 0)
			junk++;

		camel_message_info_free(info);
	}

	camel_file_util_encode_fixed_int32(out, count);
	camel_file_util_encode_fixed_int32(out, unread);
	camel_file_util_encode_fixed_int32(out, deleted);

	return camel_file_util_encode_fixed_int32(out, junk);
}

/* are these even useful for anything??? */
static CamelMessageInfo *
message_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
{
	CamelMessageInfo *mi = NULL;
	int state;

	state = camel_mime_parser_state(mp);
	switch (state) {
	case CAMEL_MIME_PARSER_STATE_HEADER:
	case CAMEL_MIME_PARSER_STATE_MESSAGE:
	case CAMEL_MIME_PARSER_STATE_MULTIPART:
		mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_header(s, camel_mime_parser_headers_raw(mp));
		break;
	default:
		g_error("Invalid parser state");
	}

	return mi;
}

static CamelMessageContentInfo *
content_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
{
	CamelMessageContentInfo *ci = NULL;

	switch (camel_mime_parser_state(mp)) {
	case CAMEL_MIME_PARSER_STATE_HEADER:
	case CAMEL_MIME_PARSER_STATE_MESSAGE:
	case CAMEL_MIME_PARSER_STATE_MULTIPART:
		ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new_from_header(s, camel_mime_parser_headers_raw(mp));
		if (ci) {
			ci->type = camel_mime_parser_content_type(mp);
			camel_content_type_ref(ci->type);
		}
		break;
	default:
		g_error("Invalid parser state");
	}

	return ci;
}

static CamelMessageInfo *
message_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
{
	CamelMessageInfo *mi;

	mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_header(s, ((CamelMimePart *)msg)->headers);

	return mi;
}

static CamelMessageContentInfo *
content_info_new_from_message(CamelFolderSummary *s, CamelMimePart *mp)
{
	CamelMessageContentInfo *ci;

	ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new_from_header(s, mp->headers);

	return ci;
}

static char *
summary_format_address(struct _camel_header_raw *h, const char *name, const char *charset)
{
	struct _camel_header_address *addr;
	const char *text;
	char *ret;

	text = camel_header_raw_find (&h, name, NULL);
	addr = camel_header_address_decode (text, charset);
	if (addr) {
		ret = camel_header_address_list_format (addr);
		camel_header_address_list_clear (&addr);
	} else {
		ret = g_strdup (text);
	}
	
	return ret;
}

static char *
summary_format_string (struct _camel_header_raw *h, const char *name, const char *charset)
{
	const char *text;
	
	text = camel_header_raw_find (&h, name, NULL);
	if (text) {
		while (isspace ((unsigned) *text))
			text++;
		return camel_header_decode_string (text, charset);
	} else {
		return NULL;
	}
}


/**
 * camel_folder_summary_content_info_new:
 * @summary: a #CamelFolderSummary object
 * 
 * Allocate a new #CamelMessageContentInfo, suitable for adding
 * to this summary.
 * 
 * Returns a newly allocated #CamelMessageContentInfo
 **/
CamelMessageContentInfo *
camel_folder_summary_content_info_new(CamelFolderSummary *s)
{
	CamelMessageContentInfo *ci;

	CAMEL_SUMMARY_LOCK(s, alloc_lock);
	if (s->content_info_chunks == NULL)
		s->content_info_chunks = e_memchunk_new(32, s->content_info_size);
	ci = e_memchunk_alloc(s->content_info_chunks);
	CAMEL_SUMMARY_UNLOCK(s, alloc_lock);

	memset(ci, 0, s->content_info_size);
	return ci;
}

static CamelMessageInfo *
message_info_new_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
{
	CamelMessageInfoBase *mi;
	const char *received;
	guchar digest[16];
	struct _camel_header_references *refs, *irt, *scan;
	char *msgid;
	int count;
	char *subject, *from, *to, *cc, *mlist;
	CamelContentType *ct = NULL;
	const char *content, *charset = NULL;

	mi = (CamelMessageInfoBase *)camel_message_info_new(s);

	if ((content = camel_header_raw_find(&h, "Content-Type", NULL))
	     && (ct = camel_content_type_decode(content))
	     && (charset = camel_content_type_param(ct, "charset"))
	     && (g_ascii_strcasecmp(charset, "us-ascii") == 0))
		charset = NULL;
	
	charset = charset ? e_iconv_charset_name (charset) : NULL;
	
	subject = summary_format_string(h, "subject", charset);
	from = summary_format_address(h, "from", charset);
	to = summary_format_address(h, "to", charset);
	cc = summary_format_address(h, "cc", charset);
	mlist = camel_header_raw_check_mailing_list(&h);

	if (ct)
		camel_content_type_unref(ct);

	mi->subject = camel_pstring_strdup(subject);
	mi->from = camel_pstring_strdup(from);
	mi->to = camel_pstring_strdup(to);
	mi->cc = camel_pstring_strdup(cc);
	mi->mlist = camel_pstring_strdup(mlist);

	g_free(subject);
	g_free(from);
	g_free(to);
	g_free(cc);
	g_free(mlist);

	mi->user_flags = NULL;
	mi->user_tags = NULL;
	mi->date_sent = camel_header_decode_date(camel_header_raw_find(&h, "date", NULL), NULL);
	received = camel_header_raw_find(&h, "received", NULL);
	if (received)
		received = strrchr(received, ';');
	if (received)
		mi->date_received = camel_header_decode_date(received + 1, NULL);
	else
		mi->date_received = 0;

	msgid = camel_header_msgid_decode(camel_header_raw_find(&h, "message-id", NULL));
	if (msgid) {
		md5_get_digest(msgid, strlen(msgid), digest);
		memcpy(mi->message_id.id.hash, digest, sizeof(mi->message_id.id.hash));
		g_free(msgid);
	}
	
	/* decode our references and in-reply-to headers */
	refs = camel_header_references_decode (camel_header_raw_find (&h, "references", NULL));
	irt = camel_header_references_inreplyto_decode (camel_header_raw_find (&h, "in-reply-to", NULL));
	if (refs || irt) {
		if (irt) {
			/* The References field is populated from the ``References'' and/or ``In-Reply-To''
			   headers. If both headers exist, take the first thing in the In-Reply-To header
			   that looks like a Message-ID, and append it to the References header. */
			
			if (refs)
				irt->next = refs;
			
			refs = irt;
		}
		
		count = camel_header_references_list_size(&refs);
		mi->references = g_malloc(sizeof(*mi->references) + ((count-1) * sizeof(mi->references->references[0])));
		count = 0;
		scan = refs;
		while (scan) {
			md5_get_digest(scan->id, strlen(scan->id), digest);
			memcpy(mi->references->references[count].id.hash, digest, sizeof(mi->message_id.id.hash));
			count++;
			scan = scan->next;
		}
		mi->references->size = count;
		camel_header_references_list_clear(&refs);
	}

	return (CamelMessageInfo *)mi;
}

static CamelMessageInfo *
message_info_load(CamelFolderSummary *s, FILE *in)
{
	CamelMessageInfoBase *mi;
	guint count;
	int i;
	char *subject, *from, *to, *cc, *mlist, *uid;

	mi = (CamelMessageInfoBase *)camel_message_info_new(s);

	io(printf("Loading message info\n"));

	camel_file_util_decode_string(in, &uid);
	camel_file_util_decode_uint32(in, &mi->flags);
	camel_file_util_decode_uint32(in, &mi->size);
	camel_file_util_decode_time_t(in, &mi->date_sent);
	camel_file_util_decode_time_t(in, &mi->date_received);
	camel_file_util_decode_string(in, &subject);
	camel_file_util_decode_string(in, &from);
	camel_file_util_decode_string(in, &to);
	camel_file_util_decode_string(in, &cc);
	camel_file_util_decode_string(in, &mlist);

	mi->uid = uid;
	mi->subject = camel_pstring_strdup(subject);
	mi->from = camel_pstring_strdup(from);
	mi->to = camel_pstring_strdup(to);
	mi->cc = camel_pstring_strdup(cc);
	mi->mlist = camel_pstring_strdup(mlist);

	g_free(subject);
	g_free(from);
	g_free(to);
	g_free(cc);
	g_free(mlist);

	mi->content = NULL;

	camel_file_util_decode_fixed_int32(in, &mi->message_id.id.part.hi);
	camel_file_util_decode_fixed_int32(in, &mi->message_id.id.part.lo);

	if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500)
		goto error;

	if (count > 0) {
		mi->references = g_malloc(sizeof(*mi->references) + ((count-1) * sizeof(mi->references->references[0])));
		mi->references->size = count;
		for (i=0;i<count;i++) {
			camel_file_util_decode_fixed_int32(in, &mi->references->references[i].id.part.hi);
			camel_file_util_decode_fixed_int32(in, &mi->references->references[i].id.part.lo);
		}
	}

	if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500)
		goto error;

	for (i=0;i<count;i++) {
		char *name;
		if (camel_file_util_decode_string(in, &name) == -1 || name == NULL)
			goto error;
		camel_flag_set(&mi->user_flags, name, TRUE);
		g_free(name);
	}

	if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500)
		goto error;

	for (i=0;i<count;i++) {
		char *name, *value;
		if (camel_file_util_decode_string(in, &name) == -1 || name == NULL
		    || camel_file_util_decode_string(in, &value) == -1)
			goto error;
		camel_tag_set(&mi->user_tags, name, value);
		g_free(name);
		g_free(value);
	}

	if (!ferror(in))
		return (CamelMessageInfo *)mi;

error:
	camel_message_info_free((CamelMessageInfo *)mi);

	return NULL;
}

static int
message_info_save(CamelFolderSummary *s, FILE *out, CamelMessageInfo *info)
{
	guint32 count;
	CamelFlag *flag;
	CamelTag *tag;
	int i;
	CamelMessageInfoBase *mi = (CamelMessageInfoBase *)info;

	io(printf("Saving message info\n"));

	camel_file_util_encode_string(out, camel_message_info_uid(mi));
	camel_file_util_encode_uint32(out, mi->flags);
	camel_file_util_encode_uint32(out, mi->size);
	camel_file_util_encode_time_t(out, mi->date_sent);
	camel_file_util_encode_time_t(out, mi->date_received);
	camel_file_util_encode_string(out, camel_message_info_subject(mi));
	camel_file_util_encode_string(out, camel_message_info_from(mi));
	camel_file_util_encode_string(out, camel_message_info_to(mi));
	camel_file_util_encode_string(out, camel_message_info_cc(mi));
	camel_file_util_encode_string(out, camel_message_info_mlist(mi));

	camel_file_util_encode_fixed_int32(out, mi->message_id.id.part.hi);
	camel_file_util_encode_fixed_int32(out, mi->message_id.id.part.lo);

	if (mi->references) {
		camel_file_util_encode_uint32(out, mi->references->size);
		for (i=0;i<mi->references->size;i++) {
			camel_file_util_encode_fixed_int32(out, mi->references->references[i].id.part.hi);
			camel_file_util_encode_fixed_int32(out, mi->references->references[i].id.part.lo);
		}
	} else {
		camel_file_util_encode_uint32(out, 0);
	}

	count = camel_flag_list_size(&mi->user_flags);
	camel_file_util_encode_uint32(out, count);
	flag = mi->user_flags;
	while (flag) {
		camel_file_util_encode_string(out, flag->name);
		flag = flag->next;
	}

	count = camel_tag_list_size(&mi->user_tags);
	camel_file_util_encode_uint32(out, count);
	tag = mi->user_tags;
	while (tag) {
		camel_file_util_encode_string(out, tag->name);
		camel_file_util_encode_string(out, tag->value);
		tag = tag->next;
	}

	return ferror(out);
}

static void
message_info_free(CamelFolderSummary *s, CamelMessageInfo *info)
{
	CamelMessageInfoBase *mi = (CamelMessageInfoBase *)info;

	g_free(mi->uid);
	camel_pstring_free(mi->subject);
	camel_pstring_free(mi->from);
	camel_pstring_free(mi->to);
	camel_pstring_free(mi->cc);
	camel_pstring_free(mi->mlist);
	g_free(mi->references);
	camel_flag_list_free(&mi->user_flags);
	camel_tag_list_free(&mi->user_tags);
	if (s)
		e_memchunk_free(s->message_info_chunks, mi);
	else
		g_free(mi);
}

static CamelMessageContentInfo *
content_info_new_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
{
	CamelMessageContentInfo *ci;
	const char *charset;
	
	ci = camel_folder_summary_content_info_new (s);
	
	charset = e_iconv_locale_charset ();
	ci->id = camel_header_msgid_decode (camel_header_raw_find (&h, "content-id", NULL));
	ci->description = camel_header_decode_string (camel_header_raw_find (&h, "content-description", NULL), charset);
	ci->encoding = camel_content_transfer_encoding_decode (camel_header_raw_find (&h, "content-transfer-encoding", NULL));
	ci->type = camel_content_type_decode(camel_header_raw_find(&h, "content-type", NULL));

	return ci;
}

static CamelMessageContentInfo *
content_info_load(CamelFolderSummary *s, FILE *in)
{
	CamelMessageContentInfo *ci;
	char *type, *subtype;
	guint32 count, i;
	CamelContentType *ct;

	io(printf("Loading content info\n"));

	ci = camel_folder_summary_content_info_new(s);
	
	camel_folder_summary_decode_token(in, &type);
	camel_folder_summary_decode_token(in, &subtype);
	ct = camel_content_type_new(type, subtype);
	g_free(type);		/* can this be removed? */
	g_free(subtype);
	if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500)
		goto error;
	
	for (i=0;i<count;i++) {
		char *name, *value;
		camel_folder_summary_decode_token(in, &name);
		camel_folder_summary_decode_token(in, &value);
		if (!(name && value))
			goto error;
		
		camel_content_type_set_param(ct, name, value);
		/* TODO: do this so we dont have to double alloc/free */
		g_free(name);
		g_free(value);
	}
	ci->type = ct;

	camel_folder_summary_decode_token(in, &ci->id);
	camel_folder_summary_decode_token(in, &ci->description);
	camel_folder_summary_decode_token(in, &ci->encoding);

	camel_file_util_decode_uint32(in, &ci->size);

	ci->childs = NULL;

	if (!ferror(in))
		return ci;

 error:
	camel_folder_summary_content_info_free(s, ci);
	return NULL;
}

static int
content_info_save(CamelFolderSummary *s, FILE *out, CamelMessageContentInfo *ci)
{
	CamelContentType *ct;
	struct _camel_header_param *hp;

	io(printf("Saving content info\n"));

	ct = ci->type;
	if (ct) {
		camel_folder_summary_encode_token(out, ct->type);
		camel_folder_summary_encode_token(out, ct->subtype);
		camel_file_util_encode_uint32(out, my_list_size((struct _node **)&ct->params));
		hp = ct->params;
		while (hp) {
			camel_folder_summary_encode_token(out, hp->name);
			camel_folder_summary_encode_token(out, hp->value);
			hp = hp->next;
		}
	} else {
		camel_folder_summary_encode_token(out, NULL);
		camel_folder_summary_encode_token(out, NULL);
		camel_file_util_encode_uint32(out, 0);
	}
	camel_folder_summary_encode_token(out, ci->id);
	camel_folder_summary_encode_token(out, ci->description);
	camel_folder_summary_encode_token(out, ci->encoding);
	return camel_file_util_encode_uint32(out, ci->size);
}

static void
content_info_free(CamelFolderSummary *s, CamelMessageContentInfo *ci)
{
	camel_content_type_unref(ci->type);
	g_free(ci->id);
	g_free(ci->description);
	g_free(ci->encoding);
	e_memchunk_free(s->content_info_chunks, ci);
}

static char *
next_uid_string(CamelFolderSummary *s)
{
	return g_strdup_printf("%u", camel_folder_summary_next_uid(s));
}

/*
  OK
  Now this is where all the "smarts" happen, where the content info is built,
  and any indexing and what not is performed
*/

/* must have filter_lock before calling this function */
static CamelMessageContentInfo *
summary_build_content_info(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimeParser *mp)
{
	int state;
	size_t len;
	char *buffer;
	CamelMessageContentInfo *info = NULL;
	CamelContentType *ct;
	int enc_id = -1, chr_id = -1, html_id = -1, idx_id = -1;
	struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
	CamelMimeFilterCharset *mfc;
	CamelMessageContentInfo *part;

	d(printf("building content info\n"));

	/* start of this part */
	state = camel_mime_parser_step(mp, &buffer, &len);
	
	if (s->build_content)
		info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new_from_parser(s, mp);

	switch(state) {
	case CAMEL_MIME_PARSER_STATE_HEADER:
		/* check content type for indexing, then read body */
		ct = camel_mime_parser_content_type(mp);
		/* update attachments flag as we go */
		if (camel_content_type_is(ct, "application", "pgp-signature")
#ifdef ENABLE_SMIME
		    || camel_content_type_is(ct, "application", "x-pkcs7-signature")
		    || camel_content_type_is(ct, "application", "pkcs7-signature")
#endif
			)
			camel_message_info_set_flags(msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);

		if (p->index && camel_content_type_is(ct, "text", "*")) {
			char *encoding;
			const char *charset;

			d(printf("generating index:\n"));
			
			encoding = camel_content_transfer_encoding_decode(camel_mime_parser_header(mp, "content-transfer-encoding", NULL));
			if (encoding) {
				if (!g_ascii_strcasecmp(encoding, "base64")) {
					d(printf(" decoding base64\n"));
					if (p->filter_64 == NULL)
						p->filter_64 = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
					else
						camel_mime_filter_reset((CamelMimeFilter *)p->filter_64);
					enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_64);
				} else if (!g_ascii_strcasecmp(encoding, "quoted-printable")) {
					d(printf(" decoding quoted-printable\n"));
					if (p->filter_qp == NULL)
						p->filter_qp = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_QP_DEC);
					else
						camel_mime_filter_reset((CamelMimeFilter *)p->filter_qp);
					enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_qp);
				} else if (!g_ascii_strcasecmp (encoding, "x-uuencode")) {
					d(printf(" decoding x-uuencode\n"));
					if (p->filter_uu == NULL)
						p->filter_uu = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_UU_DEC);
					else
						camel_mime_filter_reset((CamelMimeFilter *)p->filter_uu);
					enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_uu);
				} else {
					d(printf(" ignoring encoding %s\n", encoding));
				}
				g_free(encoding);
			}

			charset = camel_content_type_param(ct, "charset");
			if (charset!=NULL
			    && !(g_ascii_strcasecmp(charset, "us-ascii")==0
				 || g_ascii_strcasecmp(charset, "utf-8")==0)) {
				d(printf(" Adding conversion filter from %s to UTF-8\n", charset));
				mfc = g_hash_table_lookup(p->filter_charset, charset);
				if (mfc == NULL) {
					mfc = camel_mime_filter_charset_new_convert(charset, "UTF-8");
					if (mfc)
						g_hash_table_insert(p->filter_charset, g_strdup(charset), mfc);
				} else {
					camel_mime_filter_reset((CamelMimeFilter *)mfc);
				}
				if (mfc) {
					chr_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfc);
				} else {
					w(g_warning("Cannot convert '%s' to 'UTF-8', message index may be corrupt", charset));
				}
			}

			/* we do charset conversions before this filter, which isn't strictly correct,
			   but works in most cases */
			if (camel_content_type_is(ct, "text", "html")) {
				if (p->filter_html == NULL)
					p->filter_html = camel_mime_filter_html_new();
				else
					camel_mime_filter_reset((CamelMimeFilter *)p->filter_html);
				html_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_html);
			}
			
			/* and this filter actually does the indexing */
			idx_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_index);
		}
		/* and scan/index everything */
		while (camel_mime_parser_step(mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
			;
		/* and remove the filters */
		camel_mime_parser_filter_remove(mp, enc_id);
		camel_mime_parser_filter_remove(mp, chr_id);
		camel_mime_parser_filter_remove(mp, html_id);
		camel_mime_parser_filter_remove(mp, idx_id);
		break;
	case CAMEL_MIME_PARSER_STATE_MULTIPART:
		d(printf("Summarising multipart\n"));
		/* update attachments flag as we go */
		ct = camel_mime_parser_content_type(mp);
		if (camel_content_type_is(ct, "multipart", "mixed"))
			camel_message_info_set_flags(msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
		if (camel_content_type_is(ct, "multipart", "signed")
		    || camel_content_type_is(ct, "multipart", "encrypted"))
			camel_message_info_set_flags(msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);

		while (camel_mime_parser_step(mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
			camel_mime_parser_unstep(mp);
			part = summary_build_content_info(s, msginfo, mp);
			if (part) {
				part->parent = info;
				my_list_append((struct _node **)&info->childs, (struct _node *)part);
			}
		}
		break;
	case CAMEL_MIME_PARSER_STATE_MESSAGE:
		d(printf("Summarising message\n"));
		/* update attachments flag as we go */
		camel_message_info_set_flags(msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);

		part = summary_build_content_info(s, msginfo, mp);
		if (part) {
			part->parent = info;
			my_list_append((struct _node **)&info->childs, (struct _node *)part);
		}
		state = camel_mime_parser_step(mp, &buffer, &len);
		if (state != CAMEL_MIME_PARSER_STATE_MESSAGE_END) {
			g_error("Bad parser state: Expecing MESSAGE_END or MESSAGE_EOF, got: %d", state);
			camel_mime_parser_unstep(mp);
		}
		break;
	}

	d(printf("finished building content info\n"));

	return info;
}

/* build the content-info, from a message */
/* this needs the filter lock since it uses filters to perform indexing */
static CamelMessageContentInfo *
summary_build_content_info_message(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimePart *object)
{
	CamelDataWrapper *containee;
	int parts, i;
	struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
	CamelMessageContentInfo *info = NULL, *child;
	CamelContentType *ct;

	if (s->build_content)
		info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new_from_message(s, object);
	
	containee = camel_medium_get_content_object(CAMEL_MEDIUM(object));

	if (containee == NULL)
		return info;

	/* TODO: I find it odd that get_part and get_content_object do not
	   add a reference, probably need fixing for multithreading */

	/* check for attachments */
	ct = ((CamelDataWrapper *)containee)->mime_type;
	if (camel_content_type_is(ct, "multipart", "*")) {
		if (camel_content_type_is(ct, "multipart", "mixed"))
			camel_message_info_set_flags(msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
		if (camel_content_type_is(ct, "multipart", "signed")
		    || camel_content_type_is(ct, "multipart", "encrypted"))
			camel_message_info_set_flags(msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
	} else if (camel_content_type_is(ct, "application", "pgp-signature")
#ifdef ENABLE_SMIME
		    || camel_content_type_is(ct, "application", "x-pkcs7-signature")
		    || camel_content_type_is(ct, "application", "pkcs7-signature")
#endif
		) {
		camel_message_info_set_flags(msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
	}

	/* using the object types is more accurate than using the mime/types */
	if (CAMEL_IS_MULTIPART(containee)) {
		parts = camel_multipart_get_number(CAMEL_MULTIPART(containee));
		for (i=0;i<parts;i++) {
			CamelMimePart *part = camel_multipart_get_part(CAMEL_MULTIPART(containee), i);
			g_assert(part);
			child = summary_build_content_info_message(s, msginfo, part);
			if (child) {
				child->parent = info;
				my_list_append((struct _node **)&info->childs, (struct _node *)child);
			}
		}
	} else if (CAMEL_IS_MIME_MESSAGE(containee)) {
		/* for messages we only look at its contents */
		child = summary_build_content_info_message(s, msginfo, (CamelMimePart *)containee);
		if (child) {
			child->parent = info;
			my_list_append((struct _node **)&info->childs, (struct _node *)child);
		}
	} else if (p->filter_stream
		   && camel_content_type_is(ct, "text", "*")) {
		int html_id = -1, idx_id = -1;

		/* pre-attach html filter if required, otherwise just index filter */
		if (camel_content_type_is(ct, "text", "html")) {
			if (p->filter_html == NULL)
				p->filter_html = camel_mime_filter_html_new();
			else
				camel_mime_filter_reset((CamelMimeFilter *)p->filter_html);
			html_id = camel_stream_filter_add(p->filter_stream, (CamelMimeFilter *)p->filter_html);
		}
		idx_id = camel_stream_filter_add(p->filter_stream, (CamelMimeFilter *)p->filter_index);

		camel_data_wrapper_decode_to_stream(containee, (CamelStream *)p->filter_stream);
		camel_stream_flush((CamelStream *)p->filter_stream);

		camel_stream_filter_remove(p->filter_stream, idx_id);
		camel_stream_filter_remove(p->filter_stream, html_id);
	}

	return info;
}

/**
 * camel_flag_get:
 * @list: the address of a #CamelFlag list
 * @name: name of the flag to get
 * 
 * Find the state of the flag @name in @list.
 * 
 * Returns the state of the flag (%TRUE or %FALSE)
 **/
gboolean
camel_flag_get(CamelFlag **list, const char *name)
{
	CamelFlag *flag;
	flag = *list;
	while (flag) {
		if (!strcmp(flag->name, name))
			return TRUE;
		flag = flag->next;
	}
	return FALSE;
}


/**
 * camel_flag_set:
 * @list: the address of a #CamelFlag list
 * @name: name of the flag to set or change
 * @value: the value to set on the flag
 * 
 * Set the state of a flag @name in the list @list to @value.
 *
 * Returns %TRUE if the value of the flag has been changed or %FALSE
 * otherwise
 **/
gboolean
camel_flag_set(CamelFlag **list, const char *name, gboolean value)
{
	CamelFlag *flag, *tmp;

	/* this 'trick' works because flag->next is the first element */
	flag = (CamelFlag *)list;
	while (flag->next) {
		tmp = flag->next;
		if (!strcmp(flag->next->name, name)) {
			if (!value) {
				flag->next = tmp->next;
				g_free(tmp);
			}
			return !value;
		}
		flag = tmp;
	}

	if (value) {
		tmp = g_malloc(sizeof(*tmp) + strlen(name));
		strcpy(tmp->name, name);
		tmp->next = 0;
		flag->next = tmp;
	}
	return value;
}


/**
 * camel_flag_list_size:
 * @list: the address of a #CamelFlag list
 * 
 * Get the length of the flag list.
 * 
 * Returns the number of flags in the list
 **/
int
camel_flag_list_size(CamelFlag **list)
{
	int count=0;
	CamelFlag *flag;

	flag = *list;
	while (flag) {
		count++;
		flag = flag->next;
	}
	return count;
}


/**
 * camel_flag_list_free:
 * @list: the address of a #CamelFlag list
 * 
 * Free the memory associated with the flag list @list.
 **/
void
camel_flag_list_free(CamelFlag **list)
{
	CamelFlag *flag, *tmp;
	flag = *list;
	while (flag) {
		tmp = flag->next;
		g_free(flag);
		flag = tmp;
	}
	*list = NULL;
}


/**
 * camel_flag_list_copy:
 * @to: the address of the #CamelFlag list to copy to
 * @from: the address of the #CamelFlag list to copy from
 * 
 * Copy a flag list.
 * 
 * Returns %TRUE if @to is changed or %FALSE otherwise
 **/
gboolean
camel_flag_list_copy(CamelFlag **to, CamelFlag **from)
{
	CamelFlag *flag, *tmp;
	int changed = FALSE;

	if (*to == NULL && from == NULL)
		return FALSE;

	/* Remove any now-missing flags */
	flag = (CamelFlag *)to;
	while (flag->next) {
		tmp = flag->next;
		if (!camel_flag_get(from, tmp->name)) {
			flag->next = tmp->next;
			g_free(tmp);
			changed = TRUE;
		} else {
			flag = tmp;
		}
	}

	/* Add any new flags */
	flag = *from;
	while (flag) {
		changed |= camel_flag_set(to, flag->name, TRUE);
		flag = flag->next;
	}

	return changed;
}


/**
 * camel_tag_get:
 * @list: the address of a #CamelTag list
 * @name: name of the tag to get
 *
 * Find the flag @name in @list and get the value.
 * 
 * Returns the value of the flag  or %NULL if unset
 **/
const char *
camel_tag_get(CamelTag **list, const char *name)
{
	CamelTag *tag;

	tag = *list;
	while (tag) {
		if (!strcmp(tag->name, name))
			return (const char *)tag->value;
		tag = tag->next;
	}
	return NULL;
}


/**
 * camel_tag_set:
 * @list: the address of a #CamelTag list
 * @name: name of the tag to set
 * @value: value to set on the tag
 * 
 * Set the tag @name in the tag list @list to @value.
 *
 * Returns %TRUE if the value on the tag changed or %FALSE otherwise
 **/
gboolean
camel_tag_set(CamelTag **list, const char *name, const char *value)
{
	CamelTag *tag, *tmp;

	/* this 'trick' works because tag->next is the first element */
	tag = (CamelTag *)list;
	while (tag->next) {
		tmp = tag->next;
		if (!strcmp(tmp->name, name)) {
			if (value == NULL) { /* clear it? */
				tag->next = tmp->next;
				g_free(tmp->value);
				g_free(tmp);
				return TRUE;
			} else if (strcmp(tmp->value, value)) { /* has it changed? */
				g_free(tmp->value);
				tmp->value = g_strdup(value);
				return TRUE;
			}
			return FALSE;
		}
		tag = tmp;
	}

	if (value) {
		tmp = g_malloc(sizeof(*tmp)+strlen(name));
		strcpy(tmp->name, name);
		tmp->value = g_strdup(value);
		tmp->next = 0;
		tag->next = tmp;
		return TRUE;
	}
	return FALSE;
}


/**
 * camel_tag_list_size:
 * @list: the address of a #CamelTag list
 * 
 * Get the number of tags present in the tag list @list.
 * 
 * Returns the number of tags
 **/
int
camel_tag_list_size(CamelTag **list)
{
	int count=0;
	CamelTag *tag;

	tag = *list;
	while (tag) {
		count++;
		tag = tag->next;
	}
	return count;
}

static void
rem_tag(char *key, char *value, CamelTag **to)
{
	camel_tag_set(to, key, NULL);
}


/**
 * camel_tag_list_copy:
 * @to: the address of the #CamelTag list to copy to
 * @from: the address of the #CamelTag list to copy from
 * 
 * Copy a tag list.
 * 
 * Returns %TRUE if @to is changed or %FALSE otherwise
 **/
gboolean
camel_tag_list_copy(CamelTag **to, CamelTag **from)
{
	int changed = FALSE;
	CamelTag *tag;
	GHashTable *left;

	if (*to == NULL && from == NULL)
		return FALSE;

	left = g_hash_table_new(g_str_hash, g_str_equal);
	tag = *to;
	while (tag) {
		g_hash_table_insert(left, tag->name, tag);
		tag = tag->next;
	}

	tag = *from;
	while (tag) {
		changed |= camel_tag_set(to, tag->name, tag->value);
		g_hash_table_remove(left, tag->name);
		tag = tag->next;
	}

	if (g_hash_table_size(left)>0) {
		g_hash_table_foreach(left, (GHFunc)rem_tag, to);
		changed = TRUE;
	}
	g_hash_table_destroy(left);

	return changed;
}


/**
 * camel_tag_list_free:
 * @list: the address of a #CamelTag list
 * 
 * Free the tag list @list.
 **/
void
camel_tag_list_free(CamelTag **list)
{
	CamelTag *tag, *tmp;
	tag = *list;
	while (tag) {
		tmp = tag->next;
		g_free(tag->value);
		g_free(tag);
		tag = tmp;
	}
	*list = NULL;
}

static struct flag_names_t {
	char *name;
	guint32 value;
} flag_names[] = {
	{ "answered", CAMEL_MESSAGE_ANSWERED },
	{ "deleted", CAMEL_MESSAGE_DELETED },
	{ "draft", CAMEL_MESSAGE_DELETED },
	{ "flagged", CAMEL_MESSAGE_FLAGGED },
	{ "seen", CAMEL_MESSAGE_SEEN },
	{ "attachments", CAMEL_MESSAGE_ATTACHMENTS },
	{ "junk", CAMEL_MESSAGE_JUNK },
	{ "secure", CAMEL_MESSAGE_SECURE },
	{ NULL, 0 }
};


/**
 * camel_system_flag:
 * @name: name of a system flag
 * 
 * Returns the integer value of the system flag string
 **/
guint32
camel_system_flag (const char *name)
{
	struct flag_names_t *flag;
	
	g_return_val_if_fail (name != NULL, 0);
	
	for (flag = flag_names; *flag->name; flag++)
		if (!g_ascii_strcasecmp (name, flag->name))
			return flag->value;
	
	return 0;
}


/**
 * camel_system_flag_get:
 * @flags: bitwise system flags
 * @name: name of the flag to check for
 * 
 * Find the state of the flag @name in @flags.
 * 
 * Returns %TRUE if the named flag is set or %FALSE otherwise
 **/
gboolean
camel_system_flag_get (guint32 flags, const char *name)
{
	g_return_val_if_fail (name != NULL, FALSE);
	
	return flags & camel_system_flag (name);
}


/**
 * camel_message_info_new:
 * @summary: a #CamelFolderSummary object or %NULL
 *
 * Create a new #CamelMessageInfo.
 *
 * Returns a new #CamelMessageInfo
 **/
void *
camel_message_info_new (CamelFolderSummary *s)
{
	CamelMessageInfo *info;

	if (s) {
		CAMEL_SUMMARY_LOCK(s, alloc_lock);
		if (s->message_info_chunks == NULL)
			s->message_info_chunks = e_memchunk_new(32, s->message_info_size);
		info = e_memchunk_alloc0(s->message_info_chunks);
		CAMEL_SUMMARY_UNLOCK(s, alloc_lock);
	} else {
		info = g_malloc0(sizeof(CamelMessageInfoBase));
	}

	info->refcount = 1;
	info->summary = s;

	return info;
}


/**
 * camel_message_info_ref:
 * @info: a #CamelMessageInfo
 * 
 * Reference an info.
 **/
void
camel_message_info_ref(void *o)
{
	CamelMessageInfo *mi = o;

	if (mi->summary) {
		CAMEL_SUMMARY_LOCK(mi->summary, ref_lock);
		g_assert(mi->refcount >= 1);
		mi->refcount++;
		CAMEL_SUMMARY_UNLOCK(mi->summary, ref_lock);
	} else {
		GLOBAL_INFO_LOCK(info);
		g_assert(mi->refcount >= 1);
		mi->refcount++;
		GLOBAL_INFO_UNLOCK(info);
	}
}


/**
 * camel_message_info_new_from_header:
 * @summary: a #CamelFolderSummary object or %NULL
 * @header: raw header
 *
 * Create a new #CamelMessageInfo pre-populated with info from
 * @header.
 *
 * Returns a new #CamelMessageInfo
 **/
CamelMessageInfo *
camel_message_info_new_from_header(CamelFolderSummary *s, struct _camel_header_raw *header)
{
	if (s)
		return ((CamelFolderSummaryClass *)((CamelObject *)s)->klass)->message_info_new_from_header(s, header);
	else
		return message_info_new_from_header(NULL, header);
}


/**
 * camel_message_info_free:
 * @info: a #CamelMessageInfo
 *
 * Unref's and potentially frees a #CamelMessageInfo and its contents.
 **/
void
camel_message_info_free(void *o)
{
	CamelMessageInfo *mi = o;

	g_return_if_fail(mi != NULL);

	if (mi->summary) {
		CAMEL_SUMMARY_LOCK(mi->summary, ref_lock);

		if (mi->refcount >= 1)
			mi->refcount--;
		if (mi->refcount > 0) {
			CAMEL_SUMMARY_UNLOCK(mi->summary, ref_lock);
			return;
		}

		CAMEL_SUMMARY_UNLOCK(mi->summary, ref_lock);

		/* FIXME: this is kinda busted, should really be handled by message info free */
		if (mi->summary->build_content
		    && ((CamelMessageInfoBase *)mi)->content) {
			camel_folder_summary_content_info_free(mi->summary, ((CamelMessageInfoBase *)mi)->content);
		}

		((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(mi->summary)))->message_info_free(mi->summary, mi);
	} else {
		GLOBAL_INFO_LOCK(info);
		mi->refcount--;
		if (mi->refcount > 0) {
			GLOBAL_INFO_UNLOCK(info);
			return;
		}
		GLOBAL_INFO_UNLOCK(info);

		message_info_free(NULL, mi);
	}
}

static CamelMessageInfo *
message_info_clone(CamelFolderSummary *s, const CamelMessageInfo *mi)
{
	CamelMessageInfoBase *to, *from = (CamelMessageInfoBase *)mi;
	CamelFlag *flag;
	CamelTag *tag;

	to = (CamelMessageInfoBase *)camel_message_info_new(s);

	to->flags = from->flags;
	to->size = from->size;
	to->date_sent = from->date_sent;
	to->date_received = from->date_received;
	to->refcount = 1;

	/* NB: We don't clone the uid */

	to->subject = camel_pstring_strdup(from->subject);
	to->from = camel_pstring_strdup(from->from);
	to->to = camel_pstring_strdup(from->to);
	to->cc = camel_pstring_strdup(from->cc);
	to->mlist = camel_pstring_strdup(from->mlist);
	memcpy(&to->message_id, &from->message_id, sizeof(to->message_id));

	if (from->references) {
		int len = sizeof(*from->references) + ((from->references->size-1) * sizeof(from->references->references[0]));

		to->references = g_malloc(len);
		memcpy(to->references, from->references, len);
	}

	flag = from->user_flags;
	while (flag) {
		camel_flag_set(&to->user_flags, flag->name, TRUE);
		flag = flag->next;
	}

	tag = from->user_tags;
	while (tag) {
		camel_tag_set(&to->user_tags, tag->name, tag->value);
		tag = tag->next;
	}

	if (from->content) {
		/* FIXME: copy content-infos */
	}

	return (CamelMessageInfo *)to;
}


/**
 * camel_message_info_clone:
 * @info: a #CamelMessageInfo
 *
 * Duplicate a #CamelMessageInfo.
 *
 * Returns the duplicated #CamelMessageInfo
 **/
void *
camel_message_info_clone(const void *o)
{
	const CamelMessageInfo *mi = o;

	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->message_info_clone(mi->summary, mi);
	else
		return message_info_clone(NULL, mi);
}

static const void *
info_ptr(const CamelMessageInfo *mi, int id)
{
	switch (id) {
	case CAMEL_MESSAGE_INFO_SUBJECT:
		return ((const CamelMessageInfoBase *)mi)->subject;
	case CAMEL_MESSAGE_INFO_FROM:
		return ((const CamelMessageInfoBase *)mi)->from;
	case CAMEL_MESSAGE_INFO_TO:
		return ((const CamelMessageInfoBase *)mi)->to;
	case CAMEL_MESSAGE_INFO_CC:
		return ((const CamelMessageInfoBase *)mi)->cc;
	case CAMEL_MESSAGE_INFO_MLIST:
		return ((const CamelMessageInfoBase *)mi)->mlist;
	case CAMEL_MESSAGE_INFO_MESSAGE_ID:
		return &((const CamelMessageInfoBase *)mi)->message_id;
	case CAMEL_MESSAGE_INFO_REFERENCES:
		return ((const CamelMessageInfoBase *)mi)->references;
	case CAMEL_MESSAGE_INFO_USER_FLAGS:
		return ((const CamelMessageInfoBase *)mi)->user_flags;
	case CAMEL_MESSAGE_INFO_USER_TAGS:
		return ((const CamelMessageInfoBase *)mi)->user_tags;
	default:
		abort();
	}
}

static guint32
info_uint32(const CamelMessageInfo *mi, int id)
{
	switch (id) {
	case CAMEL_MESSAGE_INFO_FLAGS:
		return ((const CamelMessageInfoBase *)mi)->flags;
	case CAMEL_MESSAGE_INFO_SIZE:
		return ((const CamelMessageInfoBase *)mi)->size;
	default:
		abort();
	}
}

static time_t
info_time(const CamelMessageInfo *mi, int id)
{
	switch (id) {
	case CAMEL_MESSAGE_INFO_DATE_SENT:
		return ((const CamelMessageInfoBase *)mi)->date_sent;
	case CAMEL_MESSAGE_INFO_DATE_RECEIVED:
		return ((const CamelMessageInfoBase *)mi)->date_received;
	default:
		abort();
	}
}

static gboolean
info_user_flag(const CamelMessageInfo *mi, const char *id)
{
	return camel_flag_get(&((CamelMessageInfoBase *)mi)->user_flags, id);
}

static const char *
info_user_tag(const CamelMessageInfo *mi, const char *id)
{
	return camel_tag_get(&((CamelMessageInfoBase *)mi)->user_tags, id);
}


/**
 * camel_message_info_ptr:
 * @mi: a #CamelMessageInfo
 * @id: info to get
 *
 * Generic accessor method for getting pointer data.
 *
 * Returns the pointer data
 **/
const void *
camel_message_info_ptr(const CamelMessageInfo *mi, int id)
{
	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->info_ptr(mi, id);
	else
		return info_ptr(mi, id);
}


/**
 * camel_message_info_uint32:
 * @mi: a #CamelMessageInfo
 * @id: info to get
 *
 * Generic accessor method for getting 32bit int data.
 *
 * Returns the int data
 **/
guint32
camel_message_info_uint32(const CamelMessageInfo *mi, int id)
{
	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->info_uint32(mi, id);
	else
		return info_uint32(mi, id);
}


/**
 * camel_message_info_time:
 * @mi: a #CamelMessageInfo
 * @id: info to get
 *
 * Generic accessor method for getting time_t data.
 *
 * Returns the time_t data
 **/
time_t
camel_message_info_time(const CamelMessageInfo *mi, int id)
{
	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->info_time(mi, id);
	else
		return info_time(mi, id);
}


/**
 * camel_message_info_user_flag:
 * @mi: a #CamelMessageInfo
 * @id: user flag to get
 *
 * Get the state of a user flag named @id.
 *
 * Returns the state of the user flag
 **/
gboolean
camel_message_info_user_flag(const CamelMessageInfo *mi, const char *id)
{
	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->info_user_flag(mi, id);
	else
		return info_user_flag(mi, id);
}


/**
 * camel_message_info_user_tag:
 * @mi: a #CamelMessageInfo
 * @id: user tag to get
 *
 * Get the value of a user tag named @id.
 *
 * Returns the value of the user tag
 **/
const char *
camel_message_info_user_tag(const CamelMessageInfo *mi, const char *id)
{
	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->info_user_tag(mi, id);
	else
		return info_user_tag(mi, id);
}

static gboolean
info_set_flags(CamelMessageInfo *info, guint32 flags, guint32 set)
{
	guint32 old;
	CamelMessageInfoBase *mi = (CamelMessageInfoBase *)info;

	/* TODO: locking? */

	old = mi->flags;
	mi->flags = (old & ~flags) | (set & flags);
	if (old != mi->flags) {
		mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
		if (mi->summary)
			camel_folder_summary_touch(mi->summary);
	}

	if ((old & ~CAMEL_MESSAGE_SYSTEM_MASK) == (mi->flags & ~CAMEL_MESSAGE_SYSTEM_MASK))
		return FALSE;

	if (mi->summary && mi->summary->folder && mi->uid) {
		CamelFolderChangeInfo *changes = camel_folder_change_info_new();

		camel_folder_change_info_change_uid(changes, camel_message_info_uid(info));
		camel_object_trigger_event(mi->summary->folder, "folder_changed", changes);
		camel_folder_change_info_free(changes);
	}

	return TRUE;
}


/**
 * camel_message_info_set_flags:
 * @mi: a #CamelMessageInfo
 * @flags: mask of flags to change
 * @set: state the flags should be changed to
 *
 * Change the state of the system flags on the #CamelMessageInfo
 *
 * Returns %TRUE if any of the flags changed or %FALSE otherwise
 **/
gboolean
camel_message_info_set_flags(CamelMessageInfo *mi, guint32 flags, guint32 set)
{
	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->info_set_flags(mi, flags, set);
	else
		return info_set_flags(mi, flags, set);
}

static gboolean
info_set_user_flag(CamelMessageInfo *info, const char *name, gboolean value)
{
	CamelMessageInfoBase *mi = (CamelMessageInfoBase *)info;
	int res;

	res = camel_flag_set(&mi->user_flags, name, value);

	/* TODO: check this item is still in the summary first */
	if (mi->summary && res && mi->summary->folder && mi->uid) {
		CamelFolderChangeInfo *changes = camel_folder_change_info_new();

		mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
		camel_folder_summary_touch(mi->summary);
		camel_folder_change_info_change_uid(changes, camel_message_info_uid(info));
		camel_object_trigger_event(mi->summary->folder, "folder_changed", changes);
		camel_folder_change_info_free(changes);
	}

	return res;
}


/**
 * camel_message_info_set_user_flag:
 * @mi: a #CamelMessageInfo
 * @id: name of the user flag to set
 * @state: state to set the flag to
 *
 * Set the state of a user flag on a #CamelMessageInfo.
 *
 * Returns %TRUE if the state changed or %FALSE otherwise
 **/
gboolean
camel_message_info_set_user_flag(CamelMessageInfo *mi, const char *id, gboolean state)
{
	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->info_set_user_flag(mi, id, state);
	else
		return info_set_user_flag(mi, id, state);
}

static gboolean
info_set_user_tag(CamelMessageInfo *info, const char *name, const char *value)
{
	CamelMessageInfoBase *mi = (CamelMessageInfoBase *)info;
	int res;

	res = camel_tag_set(&mi->user_tags, name, value);

	if (mi->summary && res && mi->summary->folder && mi->uid) {
		CamelFolderChangeInfo *changes = camel_folder_change_info_new();

		mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
		camel_folder_summary_touch(mi->summary);
		camel_folder_change_info_change_uid(changes, camel_message_info_uid(info));
		camel_object_trigger_event(mi->summary->folder, "folder_changed", changes);
		camel_folder_change_info_free(changes);
	}

	return res;
}


/**
 * camel_message_info_set_user_tag:
 * @mi: a #CamelMessageInfo
 * @id: name of the user tag to set
 * @val: value to set
 *
 * Set the value of a user tag on a #CamelMessageInfo.
 *
 * Returns %TRUE if the value changed or %FALSE otherwise
 **/
gboolean
camel_message_info_set_user_tag(CamelMessageInfo *mi, const char *id, const char *val)
{
	if (mi->summary)
		return ((CamelFolderSummaryClass *)((CamelObject *)mi->summary)->klass)->info_set_user_tag(mi, id, val);
	else
		return info_set_user_tag(mi, id, val);
}

void
camel_content_info_dump (CamelMessageContentInfo *ci, int depth)
{
	char *p;
	
	p = alloca (depth * 4 + 1);
	memset (p, ' ', depth * 4);
	p[depth * 4] = 0;
	
	if (ci == NULL) {
		printf ("%s<empty>\n", p);
		return;
	}
	
	if (ci->type)
		printf ("%scontent-type: %s/%s\n", p, ci->type->type ? ci->type->type : "(null)",
			ci->type->subtype ? ci->type->subtype : "(null)");
	else
		printf ("%scontent-type: <unset>\n", p);
	printf ("%scontent-transfer-encoding: %s\n", p, ci->encoding ? ci->encoding : "(null)");
	printf ("%scontent-description: %s\n", p, ci->description ? ci->description : "(null)");
	printf ("%ssize: %lu\n", p, (unsigned long) ci->size);
	ci = ci->childs;
	while (ci) {
		camel_content_info_dump (ci, depth + 1);
		ci = ci->next;
	}
}

void
camel_message_info_dump (CamelMessageInfo *mi)
{
	if (mi == NULL) {
		printf("No message?\n");
		return;
	}

	printf("Subject: %s\n", camel_message_info_subject(mi));
	printf("To: %s\n", camel_message_info_to(mi));
	printf("Cc: %s\n", camel_message_info_cc(mi));
	printf("mailing list: %s\n", camel_message_info_mlist(mi));
	printf("From: %s\n", camel_message_info_from(mi));
	printf("UID: %s\n", camel_message_info_uid(mi));
	printf("Flags: %04x\n", camel_message_info_flags(mi));
	/*camel_content_info_dump(mi->content, 0);*/
}


static void
camel_folder_summary_class_init (CamelFolderSummaryClass *klass)
{
	camel_folder_summary_parent = camel_type_get_global_classfuncs (camel_object_get_type ());

	klass->summary_header_load = summary_header_load;
	klass->summary_header_save = summary_header_save;

	klass->message_info_new_from_header  = message_info_new_from_header;
	klass->message_info_new_from_parser = message_info_new_from_parser;
	klass->message_info_new_from_message = message_info_new_from_message;
	klass->message_info_load = message_info_load;
	klass->message_info_save = message_info_save;
	klass->message_info_free = message_info_free;
	klass->message_info_clone = message_info_clone;

	klass->content_info_new_from_header  = content_info_new_from_header;
	klass->content_info_new_from_parser = content_info_new_from_parser;
	klass->content_info_new_from_message = content_info_new_from_message;
	klass->content_info_load = content_info_load;
	klass->content_info_save = content_info_save;
	klass->content_info_free = content_info_free;

	klass->next_uid_string = next_uid_string;

	klass->info_ptr = info_ptr;
	klass->info_uint32 = info_uint32;
	klass->info_time = info_time;
	klass->info_user_flag = info_user_flag;
	klass->info_user_tag = info_user_tag;

#if 0
	klass->info_set_string = info_set_string;
	klass->info_set_uint32 = info_set_uint32;
	klass->info_set_time = info_set_time;
	klass->info_set_ptr = info_set_ptr;
#endif
	klass->info_set_user_flag = info_set_user_flag;
	klass->info_set_user_tag = info_set_user_tag;

	klass->info_set_flags = info_set_flags;
}
