
/*
 *  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, int);
Prototype void RemKillFile(char *);
Prototype void LoadKillFile(char *);
Prototype void SaveKillFile(char *);

Prototype char *ReferenceLineOf(NGroup *, int);
Prototype char *NewsMessageIdOf(NGroup *, int);
Prototype char *SubjectOf(NGroup *, int, int);
Prototype char *StripRE(char *);
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, noBrkUp)
char *refs;
int days;
int noBrkUp;
{
    char *id;
    Hash **ph;
    Hash *h;

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

    if (noBrkUp) {
	ph = FindRefHash(refs);
	if (h = *ph) {
	    h->TTerm = days;
	} else {
	    AddRefHash(ph, refs, days);
	}
    } else {
	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)) {
	while (fgets(TmpBuf, sizeof(TmpBuf), fi)) {
	    Hash **ph;
	    char *ptr;
	    short len;

	    tterm = strtol(TmpBuf, &ptr, 0);
	    while (*ptr == ' ' || *ptr == '\t')
		++ptr;
	    strncpy(id, ptr, sizeof(id) - 1);
	    id[sizeof(id)-1] = 0;
	    len = strlen(id);
	    if (len && id[len-1] == '\n')
		id[--len] = 0;

	    if (tterm) {
		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 == -1 || (h->TTerm && h->TTerm > curtime))
		    fprintf(fo, "%d %s\n", h->TTerm, h->Id);
	    }
	}
	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 *
SubjectOf(grp, artno, stripre)
NGroup *grp;
int artno;
{
    FILE *fi;
    FNode **pfn = FindFNCache(grp->SubBase, artno);

    {
	FNode *fn;
	if (fn = *pfn) {
	    if (stripre)
		return(StripRE(fn->Id));
	    return(fn->Id);
	}
    }
    {
	if (fi = fopen(FileForArticle(grp, artno), "r")) {
	    char *field = FindField(fi, "Subject:");
	    fclose(fi);
	    if (field) {
		AddFNCache(pfn, artno, field);
		if (stripre)
		    field = StripRE(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;
    char *id;
    Hash **ph;

    if (refs = ReferenceLineOf(grp, artno)) {
	while (id = NextRef(&refs)) {
	    if (*(ph = FindRefHash(id))) {
		return(1);
	    }
	}
    }
    if (refs = SubjectOf(grp, artno, 1)) {
	if (*(ph = FindRefHash(refs))) {
	    return(1);
	}
    }
    return(0);
}


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);
}

char *
StripRE(str)
char *str;
{
    for (;;) {
	while (*str == ' ' || *str == '\t')
	    ++str;
	if (strnicmp(str, "re:", 3) == 0) {
	    str += 3;
	    continue;
	}
	break;
    }
    return(str);
}

