view src/lib-index/mail-index-update.c @ 0:3b1985cbc908 HEAD

Initial revision
author Timo Sirainen <tss@iki.fi>
date Fri, 09 Aug 2002 12:15:38 +0300
parents
children 1b34ec11fff8
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "ioloop.h"
#include "rfc822-date.h"
#include "rfc822-tokenize.h"
#include "message-parser.h"
#include "message-size.h"
#include "imap-envelope.h"
#include "imap-bodystructure.h"
#include "mail-index.h"
#include "mail-index-data.h"

struct _MailIndexUpdate {
	Pool pool;

	MailIndex *index;
	MailIndexRecord *rec;

	unsigned int updated_fields;
	char *fields[FIELD_TYPE_MAX_BITS];
	unsigned int field_sizes[FIELD_TYPE_MAX_BITS];
	unsigned int field_extra_sizes[FIELD_TYPE_MAX_BITS];
};

MailIndexUpdate *mail_index_update_begin(MailIndex *index, MailIndexRecord *rec)
{
	Pool pool;
	MailIndexUpdate *update;

	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);

	pool = pool_create("MailIndexUpdate", 1024, FALSE);

	update = p_new(pool, MailIndexUpdate, 1);
	update->pool = pool;
	update->index = index;
	update->rec = rec;
	return update;
}

static int mail_field_get_index(MailField field)
{
	unsigned int i, mask;

	for (i = 0, mask = 1; i < FIELD_TYPE_MAX_BITS; i++, mask <<= 1) {
		if (field == mask)
			return i;
	}

	return -1;
}

static int have_new_fields(MailIndexUpdate *update)
{
	MailField field;

	if (update->rec->cached_fields == 0) {
		/* new record */
		return TRUE;
	}

	for (field = 1; field != FIELD_TYPE_LAST; field <<= 1) {
		if ((update->updated_fields & field) &&
		    (update->rec->cached_fields & field) == 0)
			return TRUE;
	}

	return FALSE;
}

static int have_too_large_fields(MailIndexUpdate *update)
{
	MailIndexDataRecord *rec;
	int index;

	/* start from the first data field - it's required to exist */
	rec = mail_index_data_lookup(update->index->data, update->rec, 1);
	while (rec != NULL) {
		if (rec->field & update->updated_fields) {
			/* field was changed */
			index = mail_field_get_index(rec->field);
			i_assert(index >= 0);

			if (update->field_sizes[index] >= rec->full_field_size)
				return TRUE;
		}
		rec = mail_index_data_next(update->index->data,
					   update->rec, rec);
	}

	return FALSE;
}

/* Append all the data at the end of the data file and update 
   the index's data position */
static int update_by_append(MailIndexUpdate *update)
{
        MailIndexDataRecord *rec, *destrec;
	MailField field;
	off_t fpos;
	void *mem;
	unsigned int max_size, pos;
	int i;

	/* allocate the old size + also the new size of all changed or added
	   fields. this is more than required, but it's much easier than
	   calculating the exact size. */
	max_size = update->rec->data_size;
	for (i = 0; i < FIELD_TYPE_MAX_BITS; i++) {
		max_size += sizeof(MailIndexDataRecord)-sizeof(rec->data) +
			update->field_sizes[i] +
			update->field_extra_sizes[i] + MEM_ALIGN_SIZE-1;
	}

	mem = p_malloc(update->pool, max_size);
	pos = 0;

	rec = mail_index_data_lookup(update->index->data, update->rec, 1);
	for (i = 0, field = 1; field != FIELD_TYPE_LAST; i++, field <<= 1) {
		destrec = (MailIndexDataRecord *) ((char *) mem + pos);

		if (update->fields[i] != NULL) {
			/* value was modified - use it */
			destrec->full_field_size = update->field_sizes[i] +
				update->field_extra_sizes[i];
			memcpy(destrec->data, update->fields[i],
			       update->field_sizes[i]);
		} else if (rec != NULL) {
			/* use the old value */
			destrec->full_field_size = rec->full_field_size;
			memcpy(destrec->data, rec->data, rec->full_field_size);
		} else {
			/* the field doesn't exist, jump to next */
			continue;
		}

		/* memory alignment fix */
		destrec->full_field_size = MEM_ALIGN(destrec->full_field_size);

		destrec->field = field;
		pos += DATA_RECORD_SIZE(destrec);

		if (rec != NULL && rec->field == field) {
			rec = mail_index_data_next(update->index->data,
						   update->rec, rec);
		}
	}

	i_assert(pos <= max_size);

	/* append the data at the end of the data file */
	fpos = mail_index_data_append(update->index->data, mem, pos);
	if (fpos == (off_t)-1)
		return FALSE;

	/* update index file position - it's mmap()ed so it'll be writte
	   into disk when index is unlocked. */
	update->rec->data_position = fpos;
	update->rec->data_size = pos;
	return TRUE;
}

/* Replace the modified fields in the file - assumes there's enough
   space to do it */
static void update_by_replace(MailIndexUpdate *update)
{
	MailIndexDataRecord *rec;
	int index;

	/* start from the first data field - it's required to exist */
	rec = mail_index_data_lookup(update->index->data, update->rec, 1);
	while (rec != NULL) {
		if (rec->field & update->updated_fields) {
			/* field was changed */
			index = mail_field_get_index(rec->field);
			i_assert(index >= 0);

			i_assert(update->field_sizes[index] <
				 rec->full_field_size);

			strcpy(rec->data, update->fields[index]);
		}
		rec = mail_index_data_next(update->index->data,
					   update->rec, rec);
	}
}

int mail_index_update_end(MailIndexUpdate *update)
{
	int failed;

	i_assert(update->index->lock_type == MAIL_LOCK_EXCLUSIVE);

	/* if any of the fields were newly added, or have grown larger
	   than their old max. size, we need to move the record to end
	   of file. */
	if (have_new_fields(update) || have_too_large_fields(update))
		failed = !update_by_append(update);
	else {
		update_by_replace(update);
		failed = FALSE;
	}

	if (!failed) {
		/* update cached fields mask */
		update->rec->cached_fields |= update->updated_fields;
	}

	pool_unref(update->pool);
	return !failed;
}

void mail_index_update_field(MailIndexUpdate *update, MailField field,
			     const char *value, unsigned int extra_space)
{
	unsigned int size;
	int index;

	index = mail_field_get_index(field);
	i_assert(index >= 0);

	size = strlen(value)+1;

	update->updated_fields |= field;
	update->field_sizes[index] = size;
	update->field_extra_sizes[index] = extra_space;
	update->fields[index] = p_malloc(update->pool, size);
	memcpy(update->fields[index], value, size);
}

static MailField mail_header_get_field(const char *str, unsigned int len)
{
	if (len == 10 && strncasecmp(str, "Message-ID", 10) == 0)
		return FIELD_TYPE_MESSAGEID;
	if (len == 7 && strncasecmp(str, "Subject", 7) == 0)
		return FIELD_TYPE_SUBJECT;
	if (len == 4 && strncasecmp(str, "From", 4) == 0)
		return FIELD_TYPE_FROM;
	if (len == 2 && strncasecmp(str, "To", 2) == 0)
		return FIELD_TYPE_TO;
	if (len == 2 && strncasecmp(str, "Cc", 2) == 0)
		return FIELD_TYPE_CC;
	if (len == 3 && strncasecmp(str, "Bcc", 3) == 0)
		return FIELD_TYPE_BCC;

	return 0;
}

static const char *field_get_value(const char *value, unsigned int len)
{
	char *ret, *p;
	unsigned int i;

	ret = t_malloc(len+1);

	/* compress the long headers (remove \r?\n before space or tab) */
	for (i = 0, p = ret; i < len; i++) {
		if (value[i] == '\r' && i+1 != len && value[i+1] == '\n')
			i++;

		if (value[i] == '\n') {
			i_assert(IS_LWSP(value[i+1]));
		} else {
			*p++ = value[i];
		}
	}
	*p = '\0';

	return ret;
}

typedef struct {
	MailIndexUpdate *update;
	Pool envelope_pool;
	MessagePartEnvelopeData *envelope;

	MessageHeaderFunc header_func;
	void *user_data;
} HeaderUpdateData;

static void update_header_func(MessagePart *part,
			       const char *name, unsigned int name_len,
			       const char *value, unsigned int value_len,
			       void *user_data)
{
	HeaderUpdateData *data = user_data;
	MailField field;
	const char *str;

	if (part != NULL && part->parent != NULL)
		return;

	if (data->header_func != NULL) {
		data->header_func(part, name, name_len,
				  value, value_len, data->user_data);
	}

	if (name_len == 4 && strncasecmp(name, "Date", 4) == 0) {
		/* date is stored into index record itself */
		str = field_get_value(value, value_len);
		if (!rfc822_parse_date(str, &data->update->rec->sent_date))
			data->update->rec->sent_date = ioloop_time;
		return;
	}

	/* see if we can do anything with this field */
	field = mail_header_get_field(name, name_len);
	if (field != 0) {
		/* do we want to store this? */
		if (data->update->index->header->cache_fields & field) {
			str = field_get_value(value, value_len);
			data->update->index->update_field(data->update,
							  field, str, 0);
		}
	}

	if (data->update->index->header->cache_fields & FIELD_TYPE_ENVELOPE) {
		if (data->envelope_pool == NULL) {
			data->envelope_pool = pool_create("index envelope",
							  2048, FALSE);
		}
		imap_envelope_parse_header(data->envelope_pool,
					   &data->envelope,
					   t_strndup(name, name_len),
					   value, value_len);
	}
}

void mail_index_update_headers(MailIndexUpdate *update,
			       const char *msg, size_t size,
			       MessageHeaderFunc header_func, void *user_data)
{
	HeaderUpdateData data;
	MailField cache_fields;
	MessagePart *part;
	MessageSize hdr_size, body_size;
	Pool pool;
	const char *value;

	data.update = update;
	data.envelope_pool = NULL;
	data.envelope = NULL;
	data.header_func = header_func;
	data.user_data = user_data;

	cache_fields = update->index->header->cache_fields;
	if ((cache_fields & (FIELD_TYPE_BODY|FIELD_TYPE_BODYSTRUCTURE)) != 0) {
		/* for body / bodystructure, we need need to
		   fully parse the message */
		pool = pool_create("index message parser", 2048, FALSE);
		part = message_parse(pool, msg, size,
				     update_header_func, &data);

		/* update our sizes */
		update->rec->header_size = part->header_size.physical_size;
		update->rec->body_size = part->body_size.physical_size;
		update->rec->full_virtual_size =
			part->header_size.virtual_size +
			part->body_size.virtual_size;

		if (cache_fields & FIELD_TYPE_BODY) {
			t_push();
			value = imap_part_get_bodystructure(pool, &part,
							    msg, size, FALSE);
			update->index->update_field(update, FIELD_TYPE_BODY,
						    value, 0);
			t_pop();
		}

		if (cache_fields & FIELD_TYPE_BODYSTRUCTURE) {
			t_push();
			value = imap_part_get_bodystructure(pool, &part,
							    msg, size, TRUE);
			update->index->update_field(update,
						    FIELD_TYPE_BODYSTRUCTURE,
						    value, 0);
			t_pop();
		}

		pool_unref(pool);
	} else {
		message_parse_header(NULL, msg, size, &hdr_size,
				     update_header_func, &data);

		update->rec->header_size = hdr_size.physical_size;
		update->rec->body_size = size - hdr_size.physical_size;

		if (update->rec->full_virtual_size == 0) {
			/* we need to calculate virtual size of the
			   body as well */
			message_get_body_size(msg + hdr_size.physical_size,
					      size - hdr_size.physical_size,
					      &body_size);

			update->rec->full_virtual_size =
				hdr_size.virtual_size + body_size.virtual_size;
		}
	}

	if (data.envelope != NULL) {
		t_push();
		value = imap_envelope_get_part_data(data.envelope);
		update->index->update_field(update, FIELD_TYPE_ENVELOPE,
					    value, 0);
		t_pop();

		pool_unref(data.envelope_pool);
	}
}