view src/lib-storage/index/dbox/dbox-sync.c @ 3863:55df57c028d4 HEAD

Added "bool" type and changed all ints that were used as booleans to bool.
author Timo Sirainen <tss@iki.fi>
date Fri, 13 Jan 2006 22:25:57 +0200
parents ef482c909771
children 928229f8b3e6
line wrap: on
line source

/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "hash.h"
#include "write-full.h"
#include "dbox-file.h"
#include "dbox-sync.h"
#include "dbox-uidlist.h"
#include "dbox-storage.h"

#include <stddef.h>

int dbox_sync_get_file_offset(struct dbox_sync_context *ctx, uint32_t seq,
			      uint32_t *file_seq_r, uoff_t *offset_r)
{
	int ret;

	ret = dbox_file_lookup_offset(ctx->mbox, ctx->sync_view, seq,
				      file_seq_r, offset_r);
	if (ret <= 0) {
		if (ret == 0) {
			mail_storage_set_critical(STORAGE(ctx->mbox->storage),
				"Unexpectedly lost seq %u in "
				"dbox %s", seq, ctx->mbox->path);
		}
		return -1;
	}
	return 0;
}

static int dbox_sync_add_seq(struct dbox_sync_context *ctx, uint32_t seq,
                             const struct dbox_sync_rec *sync_rec)
{
        struct dbox_sync_file_entry *entry;
	uint32_t file_seq;
	uoff_t offset;

	if (dbox_sync_get_file_offset(ctx, seq, &file_seq, &offset) < 0)
		return -1;

	if (ctx->prev_file_seq == file_seq)
		return 0; /* already added in last sequence */
	ctx->prev_file_seq = file_seq;

	entry = hash_lookup(ctx->syncs, POINTER_CAST(file_seq));
	if (entry != NULL) {
		/* check if it's already added */
		const struct dbox_sync_rec *sync_recs;
		unsigned int count;

		sync_recs = array_get(&entry->sync_recs, &count);
		i_assert(count > 0);
		if (memcmp(&sync_recs[count-1],
			   sync_rec, sizeof(*sync_rec)) == 0)
			return 0; /* already added */
	} else {
		entry = p_new(ctx->pool, struct dbox_sync_file_entry, 1);
		entry->file_seq = file_seq;
		ARRAY_CREATE(&entry->sync_recs, ctx->pool,
			     struct dbox_sync_rec, 3);
		hash_insert(ctx->syncs, POINTER_CAST(file_seq), entry);
	}

	array_append(&entry->sync_recs, sync_rec, 1);
	return 0;
}

static int dbox_sync_add(struct dbox_sync_context *ctx,
			 const struct mail_index_sync_rec *sync_rec)
{
        struct dbox_sync_rec dbox_sync_rec;
	uint32_t seq, seq1, seq2;

	if (sync_rec->type == MAIL_INDEX_SYNC_TYPE_APPEND) {
		/* don't care about appends */
		return 0;
	}

	if (mail_index_lookup_uid_range(ctx->sync_view,
					sync_rec->uid1, sync_rec->uid2,
					&seq1, &seq2) < 0) {
		mail_storage_set_index_error(&ctx->mbox->ibox);
		return -1;
	}

	if (seq1 == 0) {
		/* already expunged everything. nothing to do. */
		return 0;
	}

	/* convert to dbox_sync_rec, which takes a bit less space and has
	   sequences instead of UIDs. */
	memset(&dbox_sync_rec, 0, sizeof(dbox_sync_rec));
	dbox_sync_rec.type = sync_rec->type;
	dbox_sync_rec.seq1 = seq1;
	dbox_sync_rec.seq2 = seq2;
	switch (sync_rec->type) {
	case MAIL_INDEX_SYNC_TYPE_FLAGS:
		dbox_sync_rec.value.flags.add = sync_rec->add_flags;
		dbox_sync_rec.value.flags.remove = sync_rec->remove_flags;
		break;
	case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
	case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
	case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
		dbox_sync_rec.value.keyword_idx = sync_rec->keyword_idx;
		break;
	case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
	case MAIL_INDEX_SYNC_TYPE_APPEND:
		break;
	}

	/* now, add the same sync_rec to each file_seq's entry */
	ctx->prev_file_seq = 0;
	for (seq = seq1; seq <= seq2; seq++) {
		if (dbox_sync_add_seq(ctx, seq, &dbox_sync_rec) < 0)
			return -1;
	}
	return 0;
}

int dbox_sync_update_flags(struct dbox_sync_context *ctx,
			   const struct dbox_sync_rec *sync_rec)
{
	static enum mail_flags dbox_flag_list[] = {
		MAIL_ANSWERED,
		MAIL_FLAGGED,
		MAIL_DELETED,
		MAIL_SEEN,
		MAIL_DRAFT,
		0 /* expunged */
	};
#define DBOX_FLAG_COUNT (sizeof(dbox_flag_list)/sizeof(dbox_flag_list[0]))
	struct dbox_mailbox *mbox = ctx->mbox;
	unsigned char dbox_flag_array[DBOX_FLAG_COUNT];
	unsigned char dbox_flag_mask[DBOX_FLAG_COUNT];
	uint32_t file_seq, uid2;
	uoff_t offset;
	unsigned int i, start, first_flag_offset;
	int ret;

	/* first build flag array and mask */
	if (sync_rec->type == MAIL_INDEX_SYNC_TYPE_EXPUNGE) {
		memset(dbox_flag_array, '0', sizeof(dbox_flag_array));
		memset(dbox_flag_mask, 0, sizeof(dbox_flag_mask));
		dbox_flag_mask[5] = 1;
		dbox_flag_array[5] = '1';
	} else {
		i_assert(sync_rec->type == MAIL_INDEX_SYNC_TYPE_FLAGS);
		for (i = 0; i < DBOX_FLAG_COUNT; i++) {
			dbox_flag_array[i] =
				(sync_rec->value.flags.add &
				 dbox_flag_list[i]) != 0 ? '1' : '0';
			dbox_flag_mask[i] = dbox_flag_array[i] ||
				(sync_rec->value.flags.remove &
				 dbox_flag_list[i]) != 0;
		}
	}
	first_flag_offset = offsetof(struct dbox_mail_header, answered);

	if (dbox_sync_get_file_offset(ctx, sync_rec->seq1,
				      &file_seq, &offset) < 0)
		return -1;

	if (mail_index_lookup_uid(ctx->sync_view, sync_rec->seq2, &uid2) < 0) {
		mail_storage_set_index_error(&ctx->mbox->ibox);
		return -1;
	}

	if ((ret = dbox_file_seek(mbox, file_seq, offset)) <= 0)
		return ret;

	while (mbox->file->seeked_uid <= uid2) {
		for (i = 0; i < DBOX_FLAG_COUNT; ) {
			if (!dbox_flag_mask[i])
				continue;

			start = i;
			while (i < DBOX_FLAG_COUNT) {
				if (!dbox_flag_mask[i])
					break;
				i++;
			}
			ret = pwrite_full(ctx->mbox->file->fd,
					  dbox_flag_array+start, i - start,
					  offset + first_flag_offset + start);
			if (ret < 0) {
				mail_storage_set_critical(
					STORAGE(mbox->storage),
					"pwrite(%s) failed: %m",
					mbox->file->path);
				return -1;
			}
		}

		ret = dbox_file_seek_next_nonexpunged(mbox);
		if (ret <= 0) {
			if (ret == 0)
				break;
			return -1;
		}
		offset = mbox->file->seeked_offset;
	}
	return 0;
}

static int dbox_sync_file(struct dbox_sync_context *ctx,
                          const struct dbox_sync_file_entry *entry)
{
	const struct dbox_sync_rec *sync_recs;
	unsigned int i, count;
	int ret;

	sync_recs = array_get(&entry->sync_recs, &count);
	for (i = 0; i < count; i++) {
		switch (sync_recs[i].type) {
		case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
			ret = dbox_sync_expunge(ctx, entry, i);
			if (ret > 0) {
				/* handled expunging by copying the file.
				   while at it, also wrote all the other sync
				   changes to the file. */
				return 0;
			}
			if (ret < 0)
				return -1;
			/* handled expunging by writing expunge flags */
			break;
		case MAIL_INDEX_SYNC_TYPE_FLAGS:
			if (dbox_sync_update_flags(ctx, &sync_recs[i]) < 0)
				return -1;
			break;
		case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
		case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
		case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
			/* FIXME */
			break;
		case MAIL_INDEX_SYNC_TYPE_APPEND:
			i_unreached();
		}
	}
	return 0;
}

static int dbox_sync_index(struct dbox_sync_context *ctx)
{
	struct mail_index_sync_rec sync_rec;
        struct hash_iterate_context *iter;
	void *key, *value;
	int ret;

	/* read all changes and sort them to file_seq order */
	ctx->pool = pool_alloconly_create("dbox sync pool", 10240);
	ctx->syncs = hash_create(default_pool, ctx->pool, 0, NULL, NULL);
	for (;;) {
		ret = mail_index_sync_next(ctx->index_sync_ctx, &sync_rec);
		if (ret <= 0) {
			if (ret < 0)
				mail_storage_set_index_error(&ctx->mbox->ibox);
			break;
		}
		if (dbox_sync_add(ctx, &sync_rec) < 0) {
			ret = -1;
			break;
		}
	}

	iter = hash_iterate_init(ctx->syncs);
	while (hash_iterate(iter, &key, &value)) {
                const struct dbox_sync_file_entry *entry = value;

		if (dbox_sync_file(ctx, entry) < 0) {
			ret = -1;
			break;
		}
	}
	hash_iterate_deinit(iter);

	hash_destroy(ctx->syncs);
	pool_unref(ctx->pool);

	return ret;
}

int dbox_sync(struct dbox_mailbox *mbox, bool force)
{
	struct dbox_sync_context ctx;
	const struct mail_index_header *hdr;
	uint32_t seq, uid_validity, next_uid;
	uoff_t offset;
	time_t mtime;
	int ret;

	memset(&ctx, 0, sizeof(ctx));
	ctx.mbox = mbox;

	/* always start index syncing before uidlist, so we don't get
	   deadlocks */
	ret = mail_index_sync_begin(mbox->ibox.index, &ctx.index_sync_ctx,
				    &ctx.sync_view, (uint32_t)-1, (uoff_t)-1,
				    !mbox->ibox.keep_recent, TRUE);
	if (ret <= 0) {
		if (ret < 0)
			mail_storage_set_index_error(&mbox->ibox);
		return ret;
	}
	if (dbox_uidlist_sync_init(mbox->uidlist, &ctx.uidlist_sync_ctx,
				   &mtime) < 0) {
		mail_index_sync_rollback(ctx.index_sync_ctx);
		return -1;
	}

	ctx.trans = mail_index_transaction_begin(ctx.sync_view, FALSE, TRUE);

	hdr = mail_index_get_header(ctx.sync_view);
	if ((uint32_t)mtime != hdr->sync_stamp) {
		/* indexes aren't synced. we'll do a full sync. */
		force = TRUE;
	}

	if (force)
		ret = dbox_sync_full(&ctx);
	else
		ret = dbox_sync_index(&ctx);

	if (ret < 0) {
		mail_index_sync_rollback(ctx.index_sync_ctx);
		dbox_uidlist_sync_rollback(ctx.uidlist_sync_ctx);
		return -1;
	}

	uid_validity = dbox_uidlist_sync_get_uid_validity(ctx.uidlist_sync_ctx);
	next_uid = dbox_uidlist_sync_get_next_uid(ctx.uidlist_sync_ctx);

	hdr = mail_index_get_header(ctx.sync_view);
	if (hdr->uid_validity != uid_validity) {
		mail_index_update_header(ctx.trans,
			offsetof(struct mail_index_header, uid_validity),
			&uid_validity, sizeof(uid_validity), TRUE);
	}
	if (hdr->next_uid != next_uid) {
		mail_index_update_header(ctx.trans,
			offsetof(struct mail_index_header, next_uid),
			&next_uid, sizeof(next_uid), FALSE);
	}

	if (dbox_uidlist_sync_commit(ctx.uidlist_sync_ctx, &mtime) < 0) {
		mail_index_sync_rollback(ctx.index_sync_ctx);
		return -1;
	}

	if ((uint32_t)mtime != hdr->sync_stamp) {
		uint32_t sync_stamp = mtime;

		mail_index_update_header(ctx.trans,
			offsetof(struct mail_index_header, sync_stamp),
			&sync_stamp, sizeof(sync_stamp), TRUE);
	}

	if (mail_index_transaction_commit(ctx.trans, &seq, &offset) < 0) {
		mail_storage_set_index_error(&mbox->ibox);
		mail_index_sync_rollback(ctx.index_sync_ctx);
		return -1;
	}

	if (force) {
		mail_index_sync_rollback(ctx.index_sync_ctx);
		/* now that indexes are ok, sync changes from the index */
		return dbox_sync(mbox, FALSE);
	} else {
		if (mail_index_sync_commit(ctx.index_sync_ctx) < 0) {
			mail_storage_set_index_error(&mbox->ibox);
			return -1;
		}
	}
	return 0;
}

struct mailbox_sync_context *
dbox_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
{
	struct dbox_mailbox *mbox = (struct dbox_mailbox *)box;
	int ret = 0;

	if ((flags & MAILBOX_SYNC_FLAG_FAST) == 0 ||
	    mbox->ibox.sync_last_check + MAILBOX_FULL_SYNC_INTERVAL <=
	    ioloop_time)
		ret = dbox_sync(mbox, FALSE);

	return index_mailbox_sync_init(box, flags, ret < 0);
}

int dbox_sync_if_changed(struct dbox_mailbox *mbox)
{
	const struct mail_index_header *hdr;
	time_t mtime;

	hdr = mail_index_get_header(mbox->ibox.view);
	if (hdr->sync_stamp == 0)
		return 1;

	if (dbox_uidlist_get_mtime(mbox->uidlist, &mtime) < 0)
		return -1;

	return (uint32_t)mtime == hdr->sync_stamp;
}