/* This file is part of Mailfromd.
   Copyright (C) 2005-2025 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   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 General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

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

#include <mailutils/mailutils.h>
#include "libmf.h"
#include "mfdb.h"
#include "filenames.h"

time_t negative_expire_interval;//FIXME
time_t tempfail_expire_interval = 37*60;/* FIXME: Equals to the sum of the
					   ``hard'' timeouts */
	
static void cache_print_item(struct mu_dbm_datum const *key,
			     struct mu_dbm_datum const *val);
static int cache_expire_item(struct mu_dbm_datum const *content);

static struct db_format cache_format_struct = {
	"cache",
	DEFAULT_DATABASE,
	1,
	0,
	DEFAULT_EXPIRE_INTERVAL,
	cache_print_item,
	cache_expire_item
};

struct db_format *cache_format = &cache_format_struct;


struct cache_result {
	time_t timestamp;
	mf_status status;
};

#define EXPIRE_INTERVAL(s)					\
	((s) == mf_success ? cache_format->expire_interval :	\
	 (((s) == mf_temp_failure || (s) == mf_failure)		\
	  ? tempfail_expire_interval : negative_expire_interval))

static char *
format_timestr(time_t timestamp, char *timebuf, size_t bufsize)
{
	struct tm tm;
	gmtime_r(&timestamp, &tm);
	strftime(timebuf, bufsize, "%c", &tm);
	return timebuf;
}

mf_status
cache_get(const char *email)
{
	mf_status rc;
	mu_dbm_file_t db;
	struct mu_dbm_datum key;
	struct mu_dbm_datum contents;
	int result;
	int readonly = 0;
	
	if (!cache_format->enabled)
		return mf_failure;

	mu_debug(cache_format->debug_handle, MU_DEBUG_TRACE5,
		 ("getting cache info for %s", email));
	db = mf_dbm_open(cache_format->dbname, MU_STREAM_RDWR);
	if (!db) {
		db = mf_dbm_open(cache_format->dbname, MU_STREAM_READ);
		if (!db)
			return mf_failure;
		readonly = 1;
	}
	
	memset (&key, 0, sizeof key);
	memset (&contents, 0, sizeof contents);
	key.mu_dptr = (void*) email;
	key.mu_dsize = strlen (email) + 1;
	if ((result = mu_dbm_fetch(db, &key, &contents)) == 0) {
		char timebuf[80];
		struct cache_result *res = (struct cache_result *)
			                     contents.mu_dptr;
		time_t t = time(NULL);
		mu_debug(cache_format->debug_handle, MU_DEBUG_TRACE5, 
			 ("found status: %s (%d), time: %s",
			  mf_status_str(res->status), res->status,
			  format_timestr(res->timestamp,
					 timebuf, sizeof timebuf)));
		
		if (t - res->timestamp > EXPIRE_INTERVAL(res->status)) {
			if (!readonly) {
				mu_debug(cache_format->debug_handle,
					 MU_DEBUG_TRACE5,
					 ("removing expired entry for %s",
					  email));
				result = mu_dbm_delete(db, &key);
				if (result)
					mu_error(_("cannot remove record `%s' from `%s': %s"),
						 email, cache_format->dbname,
						 result == MU_ERR_FAILURE ?
						   mu_dbm_strerror(db) :
						   mu_strerror(result));
				
			}
			rc = mf_failure;
		} else {
			rc = res->status;
		}
	} else {
		if (result == MU_ERR_FAILURE)
			mu_error(_("cannot fetch record `%s' from `%s': %s"),
				 email, cache_format->dbname,
				 mu_dbm_strerror(db));
		rc = mf_failure;
	}
	mu_dbm_datum_free(&contents);
	mu_dbm_destroy(&db);
	return rc;
}

void
cache_insert(const char *email, mf_status rc)
{
	mu_dbm_file_t db;
	struct mu_dbm_datum key;
	struct mu_dbm_datum contents;
	struct cache_result res;
	char timebuf[80];
	int result;
	
	if (!cache_format->enabled)
		return;

	time(&res.timestamp);
	res.status = rc;
	mu_debug(cache_format->debug_handle, MU_DEBUG_TRACE5,
		 ("inserting cache info for %s. status=%s (%d), time=%s",
		  email, mf_status_str(rc), rc,
		  format_timestr(res.timestamp, timebuf, sizeof timebuf)));
	db = mf_dbm_open(cache_format->dbname, MU_STREAM_RDWR);
	if (!db)
		return;
	
	memset(&key, 0, sizeof key);
	memset(&contents, 0, sizeof contents);
	key.mu_dptr = (void*) email;
	key.mu_dsize = strlen (email) + 1;
	contents.mu_dptr = (void*)&res;
	contents.mu_dsize = sizeof(res);
	result = mu_dbm_store(db, &key, &contents, 1);
	if (result)
		mu_error(_("cannot store datum `%s' into `%s': %s"), 
                         email, cache_format->dbname,
			 result == MU_ERR_FAILURE ?
			  mu_dbm_strerror(db) : mu_strerror(result));
	
	mu_dbm_destroy(&db);
}

static void
cache_print_item(struct mu_dbm_datum const *key,
		 struct mu_dbm_datum const *val)
{
	const struct cache_result *res = (const struct cache_result *)
		                          val->mu_dptr;
	int size = key->mu_dsize - 1; /* Size includes the trailing nul */
	printf("%*.*s", size, size, key->mu_dptr);
	printf(" %10.10s ", mf_status_str(res->status));
	format_time_str(stdout, res->timestamp);
	putchar('\n');
}

static int
cache_expire_item(struct mu_dbm_datum const *content)
{
	struct cache_result const *res =
		(struct cache_result const *)content->mu_dptr;
	return !res ||
		time(NULL) - res->timestamp > EXPIRE_INTERVAL(res->status);
}

mf_status
cache_get2(const char *email, const char *client_addr)
{
	mf_status rc = mf_failure;
	size_t size;
	char *key;
	
	if (!cache_format->enabled)
		return mf_failure;
	size = strlen(email) + 1 + strlen(client_addr) + 1;
	key = malloc(size);
	if (key) {
		strcat(strcat(strcpy(key, email), ":"), client_addr);
		rc = cache_get(key);
		free(key);
	}
	return rc;
}

void
cache_insert2(const char *email, const char *client_addr, mf_status rc)
{
	if (!cache_format->enabled)
		return;
	else { 
		size_t size = strlen(email) + 1 + strlen(client_addr) + 1;
		char *key = malloc(size);
		if (key) {
			strcat(strcat(strcpy(key, email), ":"), client_addr);
			cache_insert(key, rc);
			free(key);
		}
	}
}


