
/*
 *  REFS.C
 *
 *  Handles the References: line
 */

#include "defs.h"

/*
 *  hash table for killed references
 */

#define HSIZE	256
#define HMASK	(HSIZE-1)

Prototype void AddKillFile(char *, int);
Prototype void RemKillFile(char *);
Prototype void LoadKillFile(char *);
Prototype void SaveKillFile(char *);

Prototype char *ReferenceLineOf(NGroup *, int);
Prototype char *NewsMessageIdOf(NGroup *, int);
Prototype int FindArticleReferencing(NGroup *, char *, int);
Prototype int ArticleRefNotKilled(NGroup *, char *, int);
Prototype char *NextRef(char **);

/*
 *  Hash table for references
 */

typedef struct Hash {
    struct  Hash *Next;
    time_t  TTerm;	/*  terminate entry after time tterm	*/
    char    Id[4];
} Hash;

Local Hash **FindRefHash(char *);
Local void AddRefHash(Hash **, char *, time_t);
Local int hash(char *);

static Hash *Table[HSIZE];

/*
 *  Add References: field to kill file.  Kill file entry will exist for 30
 *  days.
 */

void
AddKillFile(refs, days)
char *refs;
int days;
{
    char *id;
    Hash **ph;
    Hash *h;

    if (days)
	days = time(0) + days * 1440 * 60;  /*  seconds  */

    while (id = NextRef(&refs)) {
	ph = FindRefHash(id);
	if (h = *ph) {
	    h->TTerm = days;	    /*	now seconds */
	} else {
	    AddRefHash(ph, id, days);
	}
    }
}

void
RemKillFile(refs)
char *refs;
{
    char *id;
    Hash **ph;
    Hash *h;

    while (id = NextRef(&refs)) {
	ph = FindRefHash(id);
	if (h = *ph) {
	    *ph = h->Next;
	    free(h);
	}
    }
}

void
LoadKillFile(user)
char *user;
{
    char id[128];
    char buf[64];
    long tterm;
    FILE *fi;

    sprintf(buf, "%s.dnewskl", user);
    if (fi = openlib(buf)) {
	char *ptr = TmpBuf;

	while (fgets(ptr, sizeof(TmpBuf), fi)) {
	    Hash **ph;

	    if (sscanf(ptr, "%s %ld", id, &tterm) == 2) {
		if (*(ph = FindRefHash(id)) == NULL)
		    AddRefHash(ph, id, tterm);
	    }
	}
	fclose(fi);
    }
}

void
SaveKillFile(user)
char *user;
{
    char buf[64];
    FILE *fo;
    time_t curtime = time(NULL);

    sprintf(buf, "%s.dnewskl", user);
    if (fo = openlib_write(buf)) {
	Hash **ph;
	short i;

	for (ph = Table, i = 0; i < HSIZE; ++i, ++ph) {
	    Hash *h;
	    for (h = *ph; h; h = h->Next) {
		if (h->TTerm && h->TTerm > curtime)
		    fprintf(fo, "%s %d\n", h->Id, h->TTerm);
	    }
	}
	fclose(fo);
    }
}

char *
ReferenceLineOf(grp, artno)
NGroup *grp;
int artno;
{
    FILE *fi;
    FNode **pfn = FindFNCache(grp->RefBase, artno);

    {
	FNode *fn;
	if (fn = *pfn)
	    return(fn->Id);
    }
    {
	if (fi = fopen(FileForArticle(grp, artno), "r")) {
	    char *field = FindField(fi, "References:");
	    fclose(fi);
	    if (field) {
		AddFNCache(pfn, artno, field);
		return(field);
	    }
	}
    }
    AddFNCache(pfn, artno, NULL);
    return(NULL);
}

char *
NewsMessageIdOf(grp, artno)
NGroup *grp;
int artno;
{
    FILE *fi;
    FNode **pfn = FindFNCache(grp->IdBase, artno);

    {
	FNode *fn;
	if (fn = *pfn)
	    return(fn->Id);
    }
    {
	if (fi = fopen(FileForArticle(grp, artno), "r")) {
	    char *field = FindField(fi, "Message-ID:");
	    fclose(fi);
	    if (field) {
		AddFNCache(pfn, artno, field);
		return(field);
	    }
	}
    }
    AddFNCache(pfn, artno, NULL);
    return(NULL);
}


/*
 *  Given the msg-id of an article (msgid), find it amoung the References:
 *  line of later articles.
 */

int
FindArticleReferencing(grp, msgid, artno)
NGroup *grp;
char *msgid;
int artno;
{
    char *field = ReferenceLineOf(grp, artno);
    char *id;

    if (field == NULL)
	return(0);
    while (id = NextRef(&field)) {
	/*printf("COMPARE: %s vs %s artno %d\n", msgid, id, artno);*/
	if (strcmp(msgid, id) == 0)
	    break;
    }
    if (id)
	return(1);
    return(0);
}

/*
 *  Is the specified article (grp, artno) in the kill list ?  That is, if
 *  any of the fields in the article's References: line are on the kill list
 *  we return TRUE.
 */

int
ArticleRefKilled(grp, artno)
NGroup *grp;
int artno;
{
    char *refs = ReferenceLineOf(grp, artno);
    char *id;
    Hash **ph;

    if (refs == NULL)
	return(0);

    while (id = NextRef(&refs)) {
	if (*(ph = FindRefHash(id)))
	    break;
    }
    if (id == NULL)
	return(0);
    return(1);
}


char *
NextRef(ppt)
char **ppt;
{
    char *ptr = *ppt;
    char *base;
    static char buf[256];

    while (*ptr == ' ' || *ptr == 9 || *ptr == '\n')
	++ptr;
    base = buf;
    while (*ptr && *ptr != ' ' && *ptr != 9 && *ptr != '\n')
	*base++ = *ptr++;
    *base = 0;
    *ppt = ptr;
    if (base == buf)
	return(NULL);
    return(buf);
}

/*
 *				HASH TABLE ROUTINES
 */

Hash **
FindRefHash(id)
char *id;
{
    Hash **ph = Table + hash(id);
    Hash *h;

    while (h = *ph) {
	if (strcmp(id, h->Id) == 0)
	    break;
	ph = &h->Next;
    }
    return(ph);
}

Local
void
AddRefHash(ph, id, ti)
Hash **ph;
char *id;
time_t ti;
{
    Hash *h = malloc(sizeof(Hash) - sizeof(h->Id) + strlen(id) + 1);

    strcpy(h->Id, id);
    h->TTerm = ti;
    h->Next = *ph;
    *ph = h;
}

int
hash(ptr)
char *ptr;
{
    long hv = 0x1234ADC3;

    while (*ptr) {
	hv = (hv >> 23) ^ (hv << 5) ^ *ptr;
	++ptr;
    }
    return(hv & HMASK);
}

