/*  $Revision: 1.1 $
**
**  History file routines.
*/
#include "innbbsconf.h"
#include "bbslib.h"
#include "his.h"

#define STATIC static
/*STATIC char	HIShistpath[] = _PATH_HISTORY;*/
STATIC FILE *HISwritefp;
STATIC int HISreadfd;
STATIC int HISdirty;
STATIC int HISincore = XINDEX_DBZINCORE;
STATIC char *LogName = "xindexchan";
#ifndef EXPIREDAYS
#define EXPIREDAYS 4
#endif

#ifndef DEFAULT_HIST_SIZE
#define DEFAULT_HIST_SIZE 100000
#endif

hisincore(flag)
int     flag;
{
	HISincore = flag;
}
makedbz(histpath, entry)
char   *histpath;
long    entry;
{
	long    size;
	size = dbzsize(entry);
	dbzfresh(histpath, size, '\t', 0, 1);
	dbmclose();
}

void    HISsetup();
void    HISclose();

void
mkhistory(srchist)
char   *srchist;
{
	FILE   *hismaint;
	time_t  lasthist, now;
	char    maintbuff[256];
	char   *ptr;
	hismaint = fopen(srchist, "r");
	if (hismaint == NULL) {
		return;
	} {
		char    newhistpath[1024];
		char    newhistdirpath[1024];
		char    newhistpagpath[1024];
		sprintf(newhistpath, "%s.n", srchist);
		sprintf(newhistdirpath, "%s.n.dir", srchist);
		sprintf(newhistpagpath, "%s.n.pag", srchist);
		if (!isfile(newhistdirpath) || !isfile(newhistpagpath)) {
			makedbz(newhistpath, DEFAULT_HIST_SIZE);
		}
		myHISsetup(newhistpath);
		while (fgets(maintbuff, sizeof(maintbuff), hismaint) != NULL) {
			datum   key;
			ptr = (char *) strchr(maintbuff, '\t');
			if (ptr != NULL) {
				*ptr = '\0';
				ptr++;
			}
			key.dptr = maintbuff;
			key.dsize = strlen(maintbuff);
			myHISwrite(&key, ptr);
		}
		(void) HISclose();
		/*
		 * rename(newhistpath, srchist);
		 * rename(newhistdirpath, fileglue("%s.dir", srchist));
		 * rename(newhistpagpath, fileglue("%s.pag", srchist));
		 */
	}
	fclose(hismaint);
}
time_t
gethisinfo()
{
	FILE   *hismaint;
	time_t  lasthist, now;
	char    maintbuff[4096];
	char   *ptr;
	hismaint = fopen(HISTORY, "r");
	if (hismaint == NULL) {
		return 0;
	}
	fgets(maintbuff, sizeof(maintbuff), hismaint);
	fclose(hismaint);
	ptr = (char *) strchr(maintbuff, '\t');
	if (ptr != NULL) {
		ptr++;
		lasthist = atol(ptr);
		return lasthist;
	}
	return 0;
}

void
HISmaint()
{
	FILE   *hismaint;
	time_t  lasthist, now;
	char    maintbuff[4096];
	char   *ptr;
	if (!isfile(HISTORY)) {
		makedbz(HISTORY, DEFAULT_HIST_SIZE);
	}
	hismaint = fopen(HISTORY, "r");
	if (hismaint == NULL) {
		return;
	}
	fgets(maintbuff, sizeof(maintbuff), hismaint);
	ptr = (char *) strchr(maintbuff, '\t');
	if (ptr != NULL) {
		ptr++;
		lasthist = atol(ptr);
		time(&now);
		if (lasthist + 86400 * Expiredays * 2 < now) {
			char    newhistpath[1024];
			char    newhistdirpath[1024];
			char    newhistpagpath[1024];
			(void) HISclose();
			sprintf(newhistpath, "%s.n", HISTORY);
			sprintf(newhistdirpath, "%s.n.dir", HISTORY);
			sprintf(newhistpagpath, "%s.n.pag", HISTORY);
			if (!isfile(newhistdirpath)) {
				makedbz(newhistpath, DEFAULT_HIST_SIZE);
			}
			myHISsetup(newhistpath);
			while (fgets(maintbuff, sizeof(maintbuff), hismaint) != NULL) {
				datum   key;
				ptr = (char *) strchr(maintbuff, '\t');
				if (ptr != NULL) {
					*ptr = '\0';
					ptr++;
					lasthist = atol(ptr);
				} else {
					continue;
				}
				if (lasthist + 99600 * Expiredays < now)
					continue;
				key.dptr = maintbuff;
				key.dsize = strlen(maintbuff);
				myHISwrite(&key, ptr);
			}
			(void) HISclose();
			rename(HISTORY, (char *) fileglue("%s.o", HISTORY));
			rename(newhistpath, HISTORY);
			rename(newhistdirpath, (char *) fileglue("%s.dir", HISTORY));
			rename(newhistpagpath, (char *) fileglue("%s.pag", HISTORY));
			(void) HISsetup();
		}
	}
	fclose(hismaint);
}


/*
**  Set up the history files.
*/
void
HISsetup()
{
	myHISsetup(HISTORY);
}

int
myHISsetup(histpath)
char   *histpath;
{
	if (HISwritefp == NULL) {
		/* Open the history file for appending formatted I/O. */
		if ((HISwritefp = fopen(histpath, "a")) == NULL) {
			syslog(LOG_CRIT, "%s cant fopen %s %m", LogName, histpath);
			exit(1);
		}
		CloseOnExec((int) fileno(HISwritefp), TRUE);

		/* Open the history file for reading. */
		if ((HISreadfd = open(histpath, O_RDONLY)) < 0) {
			syslog(LOG_CRIT, "%s cant open %s %m", LogName, histpath);
			exit(1);
		}
		CloseOnExec(HISreadfd, TRUE);

		/* Open the DBZ file. */
		/* (void)dbzincore(HISincore); */
		(void) dbzincore(HISincore);
		(void) dbzwritethrough(1);
		if (dbminit(histpath) < 0) {
			syslog(LOG_CRIT, "%s cant dbminit %s %m", histpath, LogName);
			exit(1);
		}
	}
}


/*
**  Synchronize the in-core history file (flush it).
*/
void
HISsync()
{
	if (HISdirty) {
		if (dbzsync()) {
			syslog(LOG_CRIT, "%s cant dbzsync %m", LogName);
			exit(1);
		}
		HISdirty = 0;
	}
}


/*
**  Close the history files.
*/
void
HISclose()
{
	if (HISwritefp != NULL) {
		/*
		 * Since dbmclose calls dbzsync we could replace this line
		 * with "HISdirty = 0;".  Oh well, it keeps the abstraction
		 * clean.
		 */
		HISsync();
		if (dbmclose() < 0)
			syslog(LOG_ERR, "%s cant dbmclose %m", LogName);
		if (fclose(HISwritefp) == EOF)
			syslog(LOG_ERR, "%s cant fclose history %m", LogName);
		HISwritefp = NULL;
		if (close(HISreadfd) < 0)
			syslog(LOG_ERR, "%s cant close history %m", LogName);
		HISreadfd = -1;
	}
}


#ifdef HISset
/*
**  File in the DBZ datum for a Message-ID, making sure not to copy any
**  illegal characters.
*/
STATIC void
HISsetkey(p, keyp)
register char *p;
datum  *keyp;
{
	static BUFFER MessageID;
	register char *dest;
	register int i;
	/* Get space to hold the ID. */
	i = strlen(p);
	if (MessageID.Data == NULL) {
		MessageID.Data = NEW(char, i + 1);
		MessageID.Size = i;
	} else if (MessageID.Size < i) {
		RENEW(MessageID.Data, char, i + 1);
		MessageID.Size = i;
	}
	for (keyp->dptr = dest = MessageID.Data; *p; p++)
		if (*p == HIS_FIELDSEP || *p == '\n')
			*dest++ = HIS_BADCHAR;
		else
			*dest++ = *p;
	*dest = '\0';

	keyp->dsize = dest - MessageID.Data + 1;
}
#endif
/*
**  Get the list of files under which a Message-ID is stored.
*/
char   *
HISfilesfor(key, output)
datum  *key;
datum  *output;
{
	char   *dest;
	datum   val;
	off_t   offset;
	register char *p;
	register int i;
	int     Used;
	/* Get the seek value into the history file. */
	val = dbzfetch(*key);
	if (val.dptr == NULL || val.dsize != sizeof offset) {
		/* printf("fail here val.dptr %d\n",val.dptr); */
		return NULL;
	}
	/* Get space. */
	if (output->dptr == NULL) {
		printf("fail here output->dptr null\n");
		return NULL;
	}
	/* Copy the value to an aligned spot. */
	for (p = val.dptr, dest = (char *) &offset, i = sizeof offset; --i >= 0;)
		*dest++ = *p++;
	if (lseek(HISreadfd, offset, SEEK_SET) == -1) {
		printf("fail here lseek %d\n", offset);
		return NULL;
	}
	/* Read the text until \n or EOF. */
	for (output->dsize = 0, Used = 0;;) {
		i = read(HISreadfd,
			&output->dptr[output->dsize], LEN - 1);
		if (i <= 0) {
			printf("fail here i %d\n", i);
			return NULL;
		}
		Used += i;
		output->dptr[Used] = '\0';
		if ((p = (char *) strchr(output->dptr, '\n')) != NULL) {
			*p = '\0';
			break;
		}
	}

	/* Move past the first two fields -- Message-ID and date info. */
	if ((p = (char *) strchr(output->dptr, HIS_FIELDSEP)) == NULL) {
		printf("fail here no HIS_FILE\n");
		return NULL;
	}
	return p + 1;
	/*
	 * if ((p = (char*)strchr(p + 1, HIS_FIELDSEP)) == NULL) return NULL;
	 */

	/* Translate newsgroup separators to slashes, return the fieldstart. */
}
/*
**  Have we already seen an article?
*/
#ifdef HISh
BOOL
HIShavearticle(MessageID)
char   *MessageID;
{
	datum   key;
	datum   val;
	HISsetkey(MessageID, &key);
	val = dbzfetch(key);
	return val.dptr != NULL;
}
#endif


/*
**  Turn a history filename entry from slashes to dots.  It's a pity
**  we have to do this.
*/
STATIC void
HISslashify(p)
register char *p;
{
	register char *last;
	for (last = NULL; *p; p++) {
		if (*p == '/') {
			*p = '.';
			last = p;
		} else if (*p == ' ' && last != NULL)
			*last = '/';
	}
	if (last)
		*last = '/';
}


IOError(error)
char   *error;
{
	fprintf(stderr, "%s\n", error);
}
/*BOOL*/
myHISwrite(key, remain)
datum  *key;
char   *remain;
{
	static char NOPATHS[] = "";
	long    offset;
	datum   val;
	int     i;
	val = dbzfetch(*key);
	if (val.dptr != NULL) {
		return FALSE;
	}
	flock(fileno(HISwritefp), LOCK_EX);
	offset = ftell(HISwritefp);
	i = fprintf(HISwritefp, "%s%c%s",
		key->dptr, HIS_FIELDSEP, remain);
	if (i == EOF || fflush(HISwritefp) == EOF) {
		/* The history line is now an orphan... */
		IOError("history");
		syslog(LOG_ERR, "%s cant write history %m", LogName);
		flock(fileno(HISwritefp), LOCK_UN);
		return FALSE;
	}
	/* Set up the database values and write them. */
	val.dptr = (char *) &offset;
	val.dsize = sizeof offset;
	if (dbzstore(*key, val) < 0) {
		IOError("my history database");
		syslog(LOG_ERR, "%s cant dbzstore %m", LogName);
		flock(fileno(HISwritefp), LOCK_UN);
		return FALSE;
	}
	if (++HISdirty >= ICD_SYNC_COUNT)
		HISsync();
	flock(fileno(HISwritefp), LOCK_UN);
	return TRUE;
}


/*
**  Write a history entry.
*/
BOOL
HISwrite(key, date, paths)
datum  *key;
char   *paths;
long    date;
{
	static char NOPATHS[] = "";
	long    offset;
	datum   val;
	int     i;
	flock(fileno(HISwritefp), LOCK_EX);
	offset = ftell(HISwritefp);
	i = fprintf(HISwritefp, "%s%c%ld%c%s\n",
		key->dptr, HIS_FIELDSEP, (long) date, HIS_FIELDSEP,
		paths);
	if (i == EOF || fflush(HISwritefp) == EOF) {
		/* The history line is now an orphan... */
		IOError("history");
		syslog(LOG_ERR, "%s cant write history %m", LogName);
		flock(fileno(HISwritefp), LOCK_UN);
		return FALSE;
	}
	/* Set up the database values and write them. */
	val.dptr = (char *) &offset;
	val.dsize = sizeof offset;
	if (dbzstore(*key, val) < 0) {
		IOError("history database");
		syslog(LOG_ERR, "%s cant dbzstore %m", LogName);
		flock(fileno(HISwritefp), LOCK_UN);
		return FALSE;
	}
	if (++HISdirty >= ICD_SYNC_COUNT)
		HISsync();
	flock(fileno(HISwritefp), LOCK_UN);
	return TRUE;
}
