/*  $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;
    long		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;
}