/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "dict.h"
#include "index-storage.h"
#include "index-sync-private.h"
#include "index-pop3-uidl.h"
#include "index-mail.h"

static void index_transaction_free(struct mailbox_transaction_context *t)
{
	if (t->view_pvt != NULL)
		mail_index_view_close(&t->view_pvt);
	mail_cache_view_close(&t->cache_view);
	mail_index_view_close(&t->view);
	if (array_is_created(&t->pvt_saves))
		array_free(&t->pvt_saves);
	array_free(&t->module_contexts);
	i_free(t->reason);
	i_free(t);
}

static int
index_transaction_index_commit(struct mail_index_transaction *index_trans,
			       struct mail_index_transaction_commit_result *result_r)
{
	struct mailbox_transaction_context *t =
		MAIL_STORAGE_CONTEXT_REQUIRE(index_trans);
	struct index_mailbox_sync_pvt_context *pvt_sync_ctx = NULL;
	const char *error;
	int ret = 0;

	index_pop3_uidl_update_exists_finish(t);

	if (t->attr_pvt_trans != NULL) {
		if (dict_transaction_commit(&t->attr_pvt_trans, &error) < 0) {
			mailbox_set_critical(t->box,
				"Dict private transaction commit failed: %s", error);
			ret = -1;
		}
	}
	if (t->attr_shared_trans != NULL) {
		if (dict_transaction_commit(&t->attr_shared_trans, &error) < 0) {
			mailbox_set_critical(t->box,
				"Dict shared transaction commit failed: %s", error);
			ret = -1;
		}
	}

	if (t->save_ctx != NULL) {
		mailbox_save_context_deinit(t->save_ctx);
		if (ret < 0) {
			t->box->v.transaction_save_rollback(t->save_ctx);
			t->save_ctx = NULL;
		} else if (t->box->v.transaction_save_commit_pre(t->save_ctx) < 0) {
			t->save_ctx = NULL;
			ret = -1;
		}
	}

	if (array_is_created(&t->pvt_saves)) {
		if (index_mailbox_sync_pvt_init(t->box, TRUE, 0, &pvt_sync_ctx) < 0)
			ret = -1;
	}

	i_assert(t->mail_ref_count == 0);
	if (ret < 0)
		t->super.rollback(index_trans);
	else {
		if (t->super.commit(index_trans, result_r) < 0) {
			mailbox_set_index_error(t->box);
			ret = -1;
		} else {
			t->changes->changes_mask = result_r->changes_mask;
		}
	}

	if (t->save_ctx == NULL) {
	} else if (ret >= 0) {
		i_assert(t->save_ctx->dest_mail == NULL);
		t->box->v.transaction_save_commit_post(t->save_ctx, result_r);
	} else {
		t->box->v.transaction_save_rollback(t->save_ctx);
	}

	if (pvt_sync_ctx != NULL) {
		if (index_mailbox_sync_pvt_newmails(pvt_sync_ctx, t) < 0) {
			/* failed to add private flags. a bit too late to
			   return failure though, so just ignore silently */
		}
		index_mailbox_sync_pvt_deinit(&pvt_sync_ctx);
	}

	if (ret < 0)
		mail_index_set_error_nolog(t->box->index, mailbox_get_last_error(t->box, NULL));
	index_transaction_free(t);
	return ret;
}

static void
index_transaction_index_rollback(struct mail_index_transaction *index_trans)
{
	struct mailbox_transaction_context *t =
		MAIL_STORAGE_CONTEXT_REQUIRE(index_trans);

	dict_transaction_rollback(&t->attr_pvt_trans);
	dict_transaction_rollback(&t->attr_shared_trans);

	if (t->save_ctx != NULL) {
		mailbox_save_context_deinit(t->save_ctx);
		t->box->v.transaction_save_rollback(t->save_ctx);
	}

	i_assert(t->mail_ref_count == 0);
	t->super.rollback(index_trans);
	index_transaction_free(t);
}

static enum mail_index_transaction_flags
index_transaction_flags_get(enum mailbox_transaction_flags flags)
{
	enum mail_index_transaction_flags itrans_flags;

	itrans_flags = MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES;
	if ((flags & MAILBOX_TRANSACTION_FLAG_HIDE) != 0)
		itrans_flags |= MAIL_INDEX_TRANSACTION_FLAG_HIDE;
	if ((flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0)
		itrans_flags |= MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL;
	if ((flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0)
		itrans_flags |= MAIL_INDEX_TRANSACTION_FLAG_SYNC;
	return itrans_flags;
}

void index_transaction_init_pvt(struct mailbox_transaction_context *t)
{
	enum mail_index_transaction_flags itrans_flags;

	if (t->box->view_pvt == NULL || t->itrans_pvt != NULL)
		return;

	itrans_flags = index_transaction_flags_get(t->flags);
	t->itrans_pvt = mail_index_transaction_begin(t->box->view_pvt,
						     itrans_flags);
	t->view_pvt = mail_index_transaction_open_updated_view(t->itrans_pvt);
}

void index_transaction_init(struct mailbox_transaction_context *t,
			    struct mailbox *box,
			    enum mailbox_transaction_flags flags,
			    const char *reason)
{
	enum mail_index_transaction_flags itrans_flags;

	i_assert(box->opened);

	itrans_flags = index_transaction_flags_get(flags);
	if ((flags & MAILBOX_TRANSACTION_FLAG_REFRESH) != 0)
		mail_index_refresh(box->index);

	t->flags = flags;
	t->box = box;
	t->reason = i_strdup(reason);
	t->itrans = mail_index_transaction_begin(box->view, itrans_flags);
	t->view = mail_index_transaction_open_updated_view(t->itrans);

	array_create(&t->module_contexts, default_pool,
		     sizeof(void *), 5);

	t->cache_view = mail_cache_view_open(box->cache, t->view);
	t->cache_trans = mail_cache_get_transaction(t->cache_view, t->itrans);

	if ((flags & MAILBOX_TRANSACTION_FLAG_NO_CACHE_DEC) != 0)
		mail_cache_view_update_cache_decisions(t->cache_view, FALSE);

	/* set up after mail_cache_get_transaction(), so that we'll still
	   have the cache_trans available in _index_commit() */
	t->super = t->itrans->v;
	t->itrans->v.commit = index_transaction_index_commit;
	t->itrans->v.rollback = index_transaction_index_rollback;
	MODULE_CONTEXT_SET(t->itrans, mail_storage_mail_index_module, t);
}

struct mailbox_transaction_context *
index_transaction_begin(struct mailbox *box,
			enum mailbox_transaction_flags flags,
			const char *reason)
{
	struct mailbox_transaction_context *t;

	t = i_new(struct mailbox_transaction_context, 1);
	index_transaction_init(t, box, flags, reason);
	return t;
}

int index_transaction_commit(struct mailbox_transaction_context *t,
			     struct mail_transaction_commit_changes *changes_r)
{
	struct mailbox *box = t->box;
	struct mail_index_transaction *itrans = t->itrans;
	struct mail_index_transaction_commit_result result;
	int ret = 0;

	i_zero(changes_r);
	changes_r->pool = pool_alloconly_create(MEMPOOL_GROWING
						"transaction changes", 512);
	p_array_init(&changes_r->saved_uids, changes_r->pool, 32);
	t->changes = changes_r;

	if (t->itrans_pvt != NULL)
		ret = mail_index_transaction_commit(&t->itrans_pvt);
	if (mail_index_transaction_commit_full(&itrans, &result) < 0)
		ret = -1;
	t = NULL;

	if (ret < 0 && mail_index_is_deleted(box->index))
		mailbox_set_deleted(box);

	changes_r->ignored_modseq_changes = result.ignored_modseq_changes;
	return ret;
}

void index_transaction_rollback(struct mailbox_transaction_context *t)
{
	struct mail_index_transaction *itrans = t->itrans;

	if (t->itrans_pvt != NULL)
		mail_index_transaction_rollback(&t->itrans_pvt);
	mail_index_transaction_rollback(&itrans);
}
