view src/plugins/expire/expire-plugin.c @ 5185:24f4a959a24c HEAD

Added <plugin_name>_version string.
author Timo Sirainen <tss@iki.fi>
date Thu, 22 Feb 2007 16:27:25 +0200
parents 99699cf9df43
children 40bd57129083
line wrap: on
line source

/* Copyright (C) 2006 PT.COM / SAPO. Code by Tianyan Liu */

#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "dict.h"
#include "index-mail.h"
#include "index-storage.h"
#include "expire-env.h"
#include "expire-plugin.h"

#include <stdlib.h>

#define EXPIRE_CONTEXT(obj) \
	*((void **)array_idx_modifiable(&(obj)->module_contexts, \
					expire.storage_module_id))

struct expire {
	struct dict *db;
	struct expire_env *env;
	const char *username;

	unsigned int storage_module_id;
	bool storage_module_id_set;

	void (*next_hook_mail_storage_created)(struct mail_storage *storage);
};

struct expire_mail_storage {
	struct mail_storage_vfuncs super;
};

struct expire_mailbox {
	struct mailbox_vfuncs super;
	time_t expire_secs;
};

struct expire_mail {
	struct mail_vfuncs super;
};

struct expire_transaction_context {
	struct mail *mail;
	time_t first_save_time;

	unsigned int first_expunged:1;
};

/* defined by imap, pop3, lda */
extern void (*hook_mail_storage_created)(struct mail_storage *storage);

const char *expire_plugin_version = PACKAGE_VERSION;

static struct expire expire;

static struct mailbox_transaction_context *
expire_mailbox_transaction_begin(struct mailbox *box,
				 enum mailbox_transaction_flags flags)
{
	struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(box);
	struct mailbox_transaction_context *t;
	struct expire_transaction_context *xt;

	t = xpr_box->super.transaction_begin(box, flags);
	xt = i_new(struct expire_transaction_context, 1);
	xt->mail = mail_alloc(t, 0, NULL);

	array_idx_set(&t->module_contexts, expire.storage_module_id, &xt);
	return t;
}

static int first_nonexpunged_timestamp(struct mailbox_transaction_context *_t,
				       time_t *stamp_r)
{
	struct index_transaction_context *t =
		(struct index_transaction_context *)_t;
	struct expire_transaction_context *xt = EXPIRE_CONTEXT(_t);
	struct mail_index_view *view = t->trans_view;
	const struct mail_index_header *hdr;
	const struct mail_index_record *rec;
	uint32_t seq;
	int ret = 0;

	/* find the first non-expunged mail. we're here because the first
	   mail was expunged, so don't bother checking it. */
	hdr = mail_index_get_header(view);
	for (seq = 2; seq <= hdr->messages_count; seq++) {
		ret = mail_index_lookup(view, seq, &rec);
		if (ret != 0)
			break;
	}
	if (ret < 0) {
		*stamp_r = 0;
		return -1;
	}

	if (ret > 0) {
		mail_set_seq(xt->mail, seq);
		*stamp_r = mail_get_save_date(xt->mail);
		if (*stamp_r == (time_t)-1)
			return -1;
	} else {
		/* everything expunged */
		*stamp_r = 0;
	}
	return 0;
}

static int
expire_mailbox_transaction_commit(struct mailbox_transaction_context *t,
				  enum mailbox_sync_flags flags)
{
	struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box);
	struct expire_transaction_context *xt = EXPIRE_CONTEXT(t);
	const char *key, *value;
	time_t new_stamp;
	bool update_dict;
	int ret;

	t_push();
	key = t_strconcat(DICT_PATH_SHARED, expire.username, "/",
			  t->box->name, NULL);

	if (xt->first_expunged) {
		/* first mail expunged. dict needs updating. */
		update_dict = first_nonexpunged_timestamp(t, &new_stamp) == 0;
	} else {
		/* saved new mails. dict needs to be updated only if this is
		   the first mail in the database */
		ret = dict_lookup(expire.db, pool_datastack_create(),
				  key, &value);
		update_dict = ret == 0 || strtoul(value, NULL, 10) == 0;
		new_stamp = xt->first_save_time;
	}

	mail_free(&xt->mail);
	i_free(xt);

	if (xpr_box->super.transaction_commit(t, flags) < 0) {
		t_pop();
		return -1;
	}

	if (update_dict) {
		struct dict_transaction_context *dctx;

		new_stamp += xpr_box->expire_secs;

		dctx = dict_transaction_begin(expire.db);
		dict_set(dctx, key, dec2str(new_stamp));
		dict_transaction_commit(dctx);
	}
	t_pop();
	return 0;
}

static void
expire_mailbox_transaction_rollback(struct mailbox_transaction_context *t)
{
	struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box);
	struct expire_transaction_context *xt = EXPIRE_CONTEXT(t);

	mail_free(&xt->mail);

	xpr_box->super.transaction_rollback(t);
	i_free(xt);
}

static int expire_mail_expunge(struct mail *_mail)
{
	struct mail_private *mail = (struct mail_private *)_mail;
	struct expire_mail *xpr_mail = EXPIRE_CONTEXT(mail);
	struct expire_transaction_context *xt =
		EXPIRE_CONTEXT(_mail->transaction);

	if (xpr_mail->super.expunge(_mail) < 0)
		return -1;

	if (_mail->seq == 1) {
		/* first mail expunged, database needs to be updated */
		xt->first_expunged = TRUE;
	}
	return 0;
}

static struct mail *
expire_mail_alloc(struct mailbox_transaction_context *t,
		  enum mail_fetch_field wanted_fields,
		  struct mailbox_header_lookup_ctx *wanted_headers)
{
	struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box);
	struct expire_mail *xpr_mail;
	struct mail *_mail;
	struct mail_private *mail;

	_mail = xpr_box->super.mail_alloc(t, wanted_fields, wanted_headers);
	mail = (struct mail_private *)_mail;

	xpr_mail = p_new(mail->pool, struct expire_mail, 1);
	xpr_mail->super = mail->v;

	mail->v.expunge = expire_mail_expunge;
	array_idx_set(&mail->module_contexts, expire.storage_module_id,
		      &xpr_mail);
	return _mail;
}

static void
mail_set_save_time(struct mailbox_transaction_context *t, uint32_t seq)
{
	struct expire_transaction_context *xt = EXPIRE_CONTEXT(t);
	struct index_transaction_context *it =
		(struct index_transaction_context *)t;

	if (xt->first_save_time == 0)
		xt->first_save_time = ioloop_time;

	mail_cache_add(it->cache_trans, seq, MAIL_CACHE_SAVE_DATE,
		       &ioloop_time, sizeof(ioloop_time));
}

static int
expire_save_init(struct mailbox_transaction_context *t,
		 enum mail_flags flags, struct mail_keywords *keywords,
		 time_t received_date, int timezone_offset,
		 const char *from_envelope, struct istream *input,
		 struct mail *dest_mail, struct mail_save_context **ctx_r)
{       
	struct expire_transaction_context *xt = EXPIRE_CONTEXT(t);
	struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box);
	int ret;

	if (dest_mail == NULL)
		dest_mail = xt->mail;

	ret = xpr_box->super.save_init(t, flags, keywords, received_date,
				       timezone_offset, from_envelope, input,
				       dest_mail, ctx_r);
	if (ret >= 0)
		mail_set_save_time(t, dest_mail->seq);
	return ret;
}

static int
expire_copy(struct mailbox_transaction_context *t, struct mail *mail,
	    enum mail_flags flags, struct mail_keywords *keywords,
	    struct mail *dest_mail)
{
	struct expire_transaction_context *xt = EXPIRE_CONTEXT(t);
	struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box);
	int ret;

	if (dest_mail == NULL)
		dest_mail = xt->mail;

	ret = xpr_box->super.copy(t, mail, flags, keywords, dest_mail);
	if (ret >= 0)
		mail_set_save_time(t, dest_mail->seq);
	return ret;
}

static void mailbox_expire_hook(struct mailbox *box, time_t expire_secs)
{
	struct expire_mailbox *xpr_box;

	xpr_box = p_new(box->pool, struct expire_mailbox, 1);
	xpr_box->super = box->v;

	box->v.transaction_begin = expire_mailbox_transaction_begin;
	box->v.transaction_commit = expire_mailbox_transaction_commit;
	box->v.transaction_rollback = expire_mailbox_transaction_rollback;
	box->v.mail_alloc = expire_mail_alloc;
	box->v.save_init = expire_save_init;
	box->v.copy = expire_copy;

	xpr_box->expire_secs = expire_secs;

	array_idx_set(&box->module_contexts,
		      expire.storage_module_id, &xpr_box);
}

static struct mailbox *
expire_mailbox_open(struct mail_storage *storage, const char *name,
		    struct istream *input, enum mailbox_open_flags flags)
{
	struct expire_mail_storage *xpr_storage = EXPIRE_CONTEXT(storage);
	struct mailbox *box;
	const struct expire_box *expire_box;

	box = xpr_storage->super.mailbox_open(storage, name, input, flags);
	if (box != NULL) {
		expire_box = expire_box_find(expire.env, name);
		if (expire_box != NULL)
			mailbox_expire_hook(box, expire_box->expire_secs);
	}
	return box;
}

static void expire_mail_storage_created(struct mail_storage *storage)
{
	struct expire_mail_storage *xpr_storage;

	if (expire.next_hook_mail_storage_created != NULL)
		expire.next_hook_mail_storage_created(storage);

	xpr_storage = p_new(storage->pool, struct expire_mail_storage, 1);
	xpr_storage->super = storage->v;
	storage->v.mailbox_open = expire_mailbox_open;

	if (!expire.storage_module_id_set) {
		expire.storage_module_id = mail_storage_module_id++;
		expire.storage_module_id_set = TRUE;
	}

	array_idx_set(&storage->module_contexts,
		      expire.storage_module_id, &xpr_storage);
}

void expire_plugin_init(void)
{
	const char *env, *dict_uri;

	env = getenv("EXPIRE");
	if (env != NULL) {
		dict_uri = getenv("EXPIRE_DICT");
		if (dict_uri == NULL)
			i_fatal("expire plugin: expire_dict setting missing");

		expire.env = expire_env_init(env);
		expire.db = dict_init(dict_uri, DICT_DATA_TYPE_UINT32, NULL);
		expire.username = getenv("USER");

		expire.next_hook_mail_storage_created =
			hook_mail_storage_created;
		hook_mail_storage_created = expire_mail_storage_created;
	}
}

void expire_plugin_deinit(void)
{
	if (expire.db != NULL) {
		hook_mail_storage_created =
			expire.next_hook_mail_storage_created;

		dict_deinit(&expire.db);
		expire_env_deinit(expire.env);
	}
}