/* $Id: cache.cpp,v 1.16 2004/01/02 03:54:01 fesnel Exp $ */
/*******************************************************************************
 *   This program is part of a library used by the Archimedes email client     * 
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2004 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU Library General Public License as published *
 *   by the Free Software Foundation; either version 2 of the License, 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 Library General Public License for more details.                      *
 *                                                                             *
 *   You should have received a copy of the GNU Library General Public License *
 *   along with this program; if not, write to the Free Software               *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA.  *
 *                                                                             *
 ******************************************************************************/


#include <fmail.h>
#include <umail.h>
#include <cfgfile.h>

#ifdef USE_DB1_NDBM
	#include <db1/ndbm.h>
#endif

#ifdef USE_NDBM
	#include <ndbm.h>
	#ifdef DBMOPEN_MODE_T
		extern "C" DBM * dbm_open(const char *f, int flags, mode_t mode);
	#else
		#if defined(HPUX10)
			extern "C" DBM * dbm_open(const char *f, int flags, short unsigned int mode);
		#else
			extern "C" DBM * dbm_open(const char *f, int flags, int mode);
		#endif
	#endif
#endif

#if defined(USE_GDBM) || defined(USE_GDBM_NDBM)
	#ifdef USE_GDBM
	   	#include <gdbm/ndbm.h>
	#endif
	#ifdef USE_GDBM_NDBM
	   	#include <gdbm-ndbm.h>
	#endif

	#ifdef DBMOPEN_MODE_T
		extern "C" DBM * dbm_open(char *f, int flags, mode_t mode);
	#else
		extern "C" DBM * dbm_open(char *f, int flags, int mode);
	#endif
#endif

#if !defined(USE_GDBM) && !defined(USE_NDBM) && !defined(USE_GDBM_NDBM) && !defined(USE_DB1_NDBM)
	#error No recognized DBM package
#endif


#define CACHE_STRTERMCHAR   '\n'
#define CACHE_ADRTERMCHAR   '\0'
#define CACHE_FLDTERMCHAR   '\0'

#define CACHE_MAXSIZE   1024    /* don't store more then 1k/message */

extern cfgfile Config;

extern "C" void dbm_close(DBM * db);
extern "C" datum dbm_fetch(DBM * db, datum key);
extern "C" datum dbm_firstkey(DBM * db);
extern "C" datum dbm_nextkey(DBM * db);
extern "C" int dbm_delete(DBM * db, datum key);
extern "C" int dbm_store(DBM * db, datum key, datum data, int flags);

#ifndef dbm_error
int dbm_error(DBM * db) {
	return 0;
}
#endif

char *get_cache_file(struct _mail_folder *folder, int type);

int exists_cache(struct _mail_folder *folder) {
	DBM *db;
	datum msg, msgdata;
	int i;

	if(!(folder->flags & CACHED))
		return 0;

	if(folder->cache)
		return 1;

	if((db = dbm_open(get_cache_file(folder, 0), O_RDONLY, 0600)) == NULL)
		return 0;

	msg.dsize = 0;
	msg.dptr = NULL;
	msg = dbm_firstkey(db);
	if((msg.dptr == NULL) || (msg.dsize == 0)) {
		dbm_close(db);
		return 0;
	}

	msgdata = dbm_fetch(db, msg);
	if((msgdata.dptr == NULL) || (msgdata.dsize == 0)) {
		dbm_close(db);
		return 0;
	}

	memcpy(&i, msgdata.dptr, sizeof(i));
	if(i != CACHE_MAGIC) {
		dbm_close(db);
		delete_cache(folder);
#ifdef  DEBUG
		fprintf(stderr,
				"Wrong magic in cache database: %d , expected %d Folder: %s\n",
				i, CACHE_MAGIC, folder->sname);
#endif
		return 0;
	}

	dbm_close(db);
	return 1;
}

void close_cache(struct _mail_folder *folder) {
	DBM *db;

	if(!(folder->flags & CACHED))
		return;

	db = (DBM *) folder->cache;
	if(db)
		dbm_close(db);
	folder->cache = NULL;
}

void delete_cache(struct _mail_folder *folder) {
	char *p;

	if(!(folder->flags & CACHED))
		return;

	close_cache(folder);
	p = get_cache_file(folder, 1);
	unlink(p);
	p = get_cache_file(folder, 2);
	unlink(p);
	p = get_cache_file(folder, 3);
	unlink(p);

	return;
}

void rename_cache(struct _mail_folder *folder, char *oldsname) {
	char newcname[255], *p, *oldcname;

	if(!oldsname || !(folder->flags & CACHED))
		return;
	close_cache(folder);
	delete_cache(folder);

	snprintf(newcname, sizeof(newcname), "%s", get_cache_file(folder, 1));
	p = folder->sname;
	folder->sname = oldsname;
	oldcname = get_cache_file(folder, 1);
	folder->sname = p;
#ifdef __EMX__          /* Under OS/2 the file will not be deleted during rename() */
	if(access(newcname, 0) == 0) {
		if(unlink(newcname) != 0) {
			display_msg(MSG_WARN, "unlink", "delete %s before moving",
						newcname);
		}
	}
#endif
	rename(oldcname, newcname);

	snprintf(newcname, sizeof(newcname), "%s", get_cache_file(folder, 2));
	p = folder->sname;
	folder->sname = oldsname;
	oldcname = get_cache_file(folder, 2);
	folder->sname = p;
#ifdef __EMX__          /* Under OS/2 the file will not be deleted during rename() */
	if(access(newcname, 0) == 0) {
		if(unlink(newcname) != 0) {
			display_msg(MSG_WARN, "unlink", "delete %s before moving",
						newcname);
		}
	}
#endif
	rename(oldcname, newcname);

	snprintf(newcname, sizeof(newcname), "%s", get_cache_file(folder, 3));
	p = folder->sname;
	folder->sname = oldsname;
	oldcname = get_cache_file(folder, 3);
	folder->sname = p;
#ifdef __EMX__          /* Under OS/2 the file will not be deleted during rename() */
	if(access(newcname, 0) == 0) {
		if(unlink(newcname) != 0) {
			display_msg(MSG_WARN, "unlink", "delete %s before moving",
						newcname);
		}
	}
#endif
	rename(oldcname, newcname);

	return;
}

int open_cache(struct _mail_folder *folder) {
	DBM *db;
	char *p;

	if(!(folder->flags & CACHED))
		return -1;

	if(folder->cache)
		return 0;

	p = get_cache_file(folder, 0);
	if((db = dbm_open(p, O_RDWR | O_CREAT, 0600)) == NULL) {
		display_msg(MSG_WARN, "Failed to open cache database", "%s", p);
		return -1;
	}

	folder->cache = db;
	return 0;
}

char *get_cache_file(struct _mail_folder *folder, int type) {
	static char cname[300 + MAX_FOLD_NAME_LEN];
	char buf[MAX_FOLD_NAME_LEN];
	char *p;
    string sCacheDir;
	int ctype = (folder->type & 0x0f) | (folder->level << 4);

	if(Config.exist("cachedir"))
		sCacheDir = Config.get("cachedir", configdir);
	else
		sCacheDir = configdir;

	snprintf(buf, sizeof(buf), "%s", folder->name(folder));
	if(folder->pfold && (folder->hdelim == '/')) {
		while((p = strchr(buf, '/')) != NULL)
			*p = '#';
	}

	switch(type) {
		case 0:
			snprintf(cname, sizeof(cname), "%s/%s/%02x%s", sCacheDir.c_str(), CACHE_DIR,
					 ctype, buf);
			break;

		case 1:
			snprintf(cname, sizeof(cname), "%s/%s/%02x%s.db", sCacheDir.c_str(), CACHE_DIR,
					 ctype, buf);
			break;

		case 2:
			snprintf(cname, sizeof(cname), "%s/%s/%02x%s.dir", sCacheDir.c_str(), CACHE_DIR,
					 ctype, buf);
			break;

		case 3:
			snprintf(cname, sizeof(cname), "%s/%s/%02x%s.pag", sCacheDir.c_str(), CACHE_DIR,
					 ctype, buf);
			break;

	}

	return cname;
}

int init_cache() {
	static char cname[255];
	struct stat sb;

	if(Config.exist("cachedir"))
		snprintf(cname, sizeof(cname), "%s/%s",
				 Config.get("cachedir", configdir).c_str(),
				 CACHE_DIR);
	else
		snprintf(cname, sizeof(cname), "%s/%s", configdir, CACHE_DIR);

	if((stat(cname, &sb) == 0) && (sb.st_mode & S_IFDIR))
		return 0;

	unlink(cname);
	if(mkdir(cname, 00700) == -1) {
		display_msg(MSG_WARN, "cache", "Can not create\n%s", cname);
		return -1;
	}
	display_msg(MSG_MSG, "init", "Created %s", cname);

	return 0;
}

int cache_str(char *str, char *buf, int *len) {
	int slen;

	if(str) {
		slen = strlen(str) + 1;
		if((*len + slen + 2) >= CACHE_MAXSIZE)
			return -1;
		memcpy(buf + *len, str, slen);
		*len += slen;
	}
	buf[(*len)++] = CACHE_STRTERMCHAR;
	return 0;
}

char *str_cache(char *buf, int *len) {
	char *p = buf + *len;

	if(*p == CACHE_STRTERMCHAR) {
		(*len)++;
		return NULL;
	}

	*len += (strlen(p) + 2);
	return p;
}

int cache_addr(struct _mail_addr *addr, char *buf, int *len) {
	if(addr) {
		if(cache_str(addr->addr, buf, len) < 0)
			return -1;
		if(cache_str(addr->name, buf, len) < 0)
			return -1;
		if(cache_str(addr->comment, buf, len) < 0)
			return -1;
	}
	buf[(*len)++] = CACHE_ADRTERMCHAR;
	return 0;
}

struct _mail_addr *addr_cache(char *buf, int *len) {
	struct _mail_addr *addr;
	char *p;

	if(buf[*len] == CACHE_ADRTERMCHAR) {
		(*len)++;
		return NULL;
	}

	addr = (struct _mail_addr *) malloc(sizeof(struct _mail_addr));
	addr->num = 0;
	addr->next_addr = NULL;
	addr->pgpid = NULL;

	p = str_cache(buf, len);
	addr->addr = strdup(p ? p : "");

	p = str_cache(buf, len);
	addr->name = p ? strdup(p) : NULL;

	p = str_cache(buf, len);
	addr->comment = p ? strdup(p) : NULL;

	(*len)++;
	return addr;
}

int cache_field(struct _head_field *fld, char *buf, int *len) {
	if(fld) {
		if(cache_str(fld->f_name, buf, len) < 0)
			return -1;
		if(cache_str(fld->f_line, buf, len) < 0)
			return -1;
	}
	buf[(*len)++] = CACHE_FLDTERMCHAR;
	return 0;
}

struct _head_field *field_cache(char *buf, int *len) {
	struct _head_field *fld;
	char *p;

	if(buf[*len] == CACHE_FLDTERMCHAR) {
		(*len)++;
		return NULL;
	}

	fld = (struct _head_field *) malloc(sizeof(struct _head_field));
	p = str_cache(buf, len);
	snprintf(fld->f_name, sizeof(fld->f_name), "%s", p ? p : "");

	p = str_cache(buf, len);
	fld->num_fields = 0;
	fld->f_line = p ? strdup(p) : NULL;
	fld->next_head_field = NULL;

	(*len)++;
	return fld;
}

int cache_msg(struct _mail_msg *msg) {
	char msgbuf[CACHE_MAXSIZE];
	DBM *db;
	datum msgid, msgdata;
	char *p;
	int msgbuflen = 0, i = CACHE_MAGIC;
	unsigned long validity;
	struct _head_field *hf;

	if(!msg || !msg->folder || (msg->uid < 0))
		return -1;

	if(open_cache(msg->folder) == -1)
		return -1;
	db = (DBM *) msg->folder->cache;

	msgid.dptr = (char *) &msg->uid;
	msgid.dsize = sizeof(msg->uid);

	memcpy(msgbuf + msgbuflen, &i, sizeof(i));
	msgbuflen += sizeof(i);

	validity = msg->validity(msg);
	memcpy(msgbuf + msgbuflen, &validity, sizeof(validity));
	msgbuflen += sizeof(validity);

	memcpy(msgbuf + msgbuflen, msg, sizeof(struct _mail_msg));
	msgbuflen += sizeof(struct _mail_msg);

	memcpy(msgbuf + msgbuflen, msg->header, sizeof(struct _msg_header));
	msgbuflen += sizeof(struct _msg_header);

	cache_str(msg->header->Subject, msgbuf, &msgbuflen);
	cache_addr(msg->header->From, msgbuf, &msgbuflen);
	cache_addr(msg->header->To, msgbuf, &msgbuflen);

	hf = msg->header->other_fields;
	while(hf) {
		i = 0;
		while((p = shorthfields[i++]) != NULL) {
			if(!strcasecmp(hf->f_name, p))
				break;
		}

		if(p) {
			if(cache_field(hf, msgbuf, &msgbuflen) < 0)
				break;
		}

		hf = hf->next_head_field;
	}
	cache_field(NULL, msgbuf, &msgbuflen);

	msgdata.dptr = msgbuf;
	msgdata.dsize = msgbuflen;

	if(dbm_store(db, msgid, msgdata, DBM_REPLACE) != 0) {
		display_msg(MSG_WARN, "cache", "Failed to store message");
		close_cache(msg->folder);
		return -1;
	}

	return 0;
}

struct _mail_msg *msg_cache(struct _mail_folder *folder, long uid) {
	DBM *db;
	datum msgid, msgdata;
	char *msgbuf, *p;
	int msgbuflen, i;
	unsigned long validity;
	struct _mail_msg *msg;
	struct _head_field *hf;

	if(open_cache(folder) == -1)
		return NULL;
	db = (DBM *) folder->cache;

	msgid.dptr = (char *) &uid;
	msgid.dsize = sizeof(uid);

	msgdata.dptr = NULL;
	msgdata.dsize = 0;

	msgdata = dbm_fetch(db, msgid);
	if(dbm_error(db) || (msgdata.dptr == NULL) || (msgdata.dsize == 0))
		return NULL;

	msgbuf = (char *) msgdata.dptr;
	msgbuflen = 0;

	memcpy(&i, msgbuf + msgbuflen, sizeof(i));
	msgbuflen += sizeof(i);

	if(i != CACHE_MAGIC) {
		dbm_delete(db, msgid);
		return NULL;
	}

	memcpy(&validity, msgbuf + msgbuflen, sizeof(validity));
	msgbuflen += sizeof(validity);

	if((msg = alloc_message()) == NULL) {
		display_msg(MSG_WARN, "cache", "malloc failed");
		return NULL;
	}

	memcpy(msg, msgbuf + msgbuflen, sizeof(struct _mail_msg));
	msgbuflen += sizeof(struct _mail_msg);

	if(
	  (msg->header =
	   (struct _msg_header *) malloc(sizeof(struct _msg_header))) ==
	  NULL) {
		display_msg(MSG_WARN, "cache", "malloc failed");
		free(msg);
		return NULL;
	}

	memcpy(msg->header, msgbuf + msgbuflen, sizeof(struct _msg_header));
	msgbuflen += sizeof(struct _msg_header);

	p = str_cache(msgbuf, &msgbuflen);
	msg->header->Subject = p ? strdup(p) : NULL;

	msg->header->From = addr_cache(msgbuf, &msgbuflen);
	msg->header->To = addr_cache(msgbuf, &msgbuflen);
	msg->header->other_fields = NULL;
	msg->header->Cc = NULL;
	msg->header->Bcc = NULL;
	msg->header->Sender = NULL;
	msg->header->News = NULL;
	msg->header->Fcc = NULL;

	while(((hf = field_cache(msgbuf, &msgbuflen)) != NULL) &&
		  (msgbuflen < msgdata.dsize)) {
		hf->next_head_field = msg->header->other_fields;
		msg->header->other_fields = hf;
	}

	msg->folder = folder;
	msg->status = H_SHORT;
	msg->pdata = NULL;
	msg->ref = NULL;
	msg->refs = 0;
	msg->msg_body = NULL;
	msg->msg_body_len = 0;
	msg->next = NULL;
	msg->mime = NULL;

	if(folder->type & F_MH)
		local_message(msg);
	else if(folder->type & F_IMAP)
		imap_message((struct _imap_src *) folder->spec, msg);
	else if(folder->type & F_MBOX)
		mbox_message(msg);
	else {
		discard_message(msg);
		dbm_delete(db, msgid);
		return NULL;
	}

	if(!(folder->flags & FNVALD) && (validity != msg->validity(msg))) {
#ifdef  DEBUG
		fprintf(stderr,
				"Invalid message. Folder: %s UID: %ld Cached: %ld Real: %ld\n",
				folder->sname, uid, validity, msg->validity(msg));
#endif
		discard_message(msg);
		dbm_delete(db, msgid);
		return NULL;
	}

	msg->uid = uid;
	msg->real_uid = uid;
	return msg;
}

void msg_cache_deluid(struct _mail_folder *folder, long uid) {
	DBM *db;
	datum msgid;

	if(!folder || !(folder->flags & CACHED) || (uid < 0))
		return;

	if(open_cache(folder) == -1)
		return;
	db = (DBM *) folder->cache;

	msgid.dptr = (char *) &uid;
	msgid.dsize = sizeof(uid);

	dbm_delete(db, msgid);

	return;
}

void msg_cache_del(struct _mail_msg *msg) {
	DBM *db;
	datum msgid;

	if(!msg || !msg->folder ||
	   !(msg->folder->flags & CACHED) || (msg->uid < 0))
		return;

	if(open_cache(msg->folder) == -1)
		return;
	db = (DBM *) msg->folder->cache;

	msgid.dptr = (char *) &msg->uid;
	msgid.dsize = sizeof(msg->uid);

	dbm_delete(db, msgid);

	return;
}

void cache_countmsg(struct _mail_folder *folder) {
	DBM *db;
	datum msgid, msgdata;
	struct _mail_msg *msg;

	if(!folder || !(folder->flags & CACHED))
		return;

	if(open_cache(folder) == -1)
		return;
	db = (DBM *) folder->cache;
	folder->num_msg = folder->unread_num = 0;

	for(msgid = dbm_firstkey(db); msgid.dptr != NULL;
	   msgid = dbm_nextkey(db)) {
		msgdata = dbm_fetch(db, msgid);
		if(dbm_error(db) ||
		   (msgdata.dptr == NULL) || (msgdata.dsize == 0)) return;

		msg =
		(struct _mail_msg *) ((char *) msgdata.dptr + sizeof(int) +
							  sizeof(unsigned long));
		folder->num_msg++;
		if(msg->flags & UNREAD)
			folder->unread_num++;
	}

	return;
}
