
/*
 *  GROUP.C
 *
 */

#include "defs.h"

Prototype void LoadGroups(char *);
Prototype void UnloadGroups(char *);
Prototype void CreateNewsrc(char *);
Prototype void RangeAdd(Range **, short, short);
Prototype void RangeDel(Range **, short, short);
Prototype int  NextInRange(Range *, short);
Prototype int  NextNotInRange(Range *, short);

Prototype void ClearGroupCache(NGroup *);
Prototype FNode **FindFNCache(FNode **, long);
Prototype void AddFNCache(FNode **, long, char *);

Prototype NGroup *PrevGroup(NGroup *);
Prototype NGroup *LastGroup(void);

Local void  cns_tag(char *, char *, FILE *);
Local char  *fgetstring(FILE *);
Local Range *ReadRange(FILE *);
Local void  WriteRange(FILE *, Range *);
Local void  FreeRange(Range *);
Local void  PrintRange(Range *);
Local long  GetMaxArtNo(char *);
Local void  ClearGroupChain(FNode *);
Local void  CreateFNCache(FNode ***);

static FNode	*FreeFNBase;

extern NGroup *GBase;

/*
 *  LoadGroups()
 *
 *  Load the newsrc file for the specified user and check for new groups
 */

void
LoadGroups(user)
char *user;
{
    FILE *fi;
    char buf[64];
    NGroup *ng;
    NGroup **png = &GBase;

    sprintf(buf, "%s.dnewsrc", user);
    if (fi = openlib(buf)) {
	short c;
	while ((c = fgetc(fi)) != EOF) {
	    switch(c) {
	    case '\n':
		continue;
	    case 'y':
	    case 'n':
		ng = malloc(sizeof(NGroup));
		setmem(ng, sizeof(NGroup), 0);
		if (c == 'y')
		    ng->Enabled = 1;
		ng->Name = fgetstring(fi);
		ng->RList= ReadRange(fi);
		ng->MList= ReadRange(fi);
		ng->KList= ReadRange(fi);
		CreateFNCache(&ng->IdBase);
		CreateFNCache(&ng->RefBase);
		CreateFNCache(&ng->SubBase);

		ng->CurNo = NextNotInRange(ng->RList, 0);
		ng->MaxNo = GetMaxArtNo(ng->Name);
		if (ng->CurNo > ng->MaxNo)
		    ng->CurNo = ng->MaxNo;

		while (ng->CurNo < ng->MaxNo && !FileExists(FileForArticle(ng, ng->CurNo))) {
		    RangeAdd(&ng->RList, ng->CurNo, ng->CurNo);
		    ng->CurNo = NextNotInRange(ng->RList, ng->CurNo);
		}

		*png = ng;
		png = &ng->Next;
		break;
	    default:
		puts("dnewsrc format error");
		break;
	    }

	    /*
	     *	skip to next newline.
	     */

	    while ((c = fgetc(fi)) != EOF && c != '\n');
	}
	*png = NULL;
	fclose(fi);
    }
}

void
UnloadGroups(user)
char *user;
{
    FILE *fi;
    char buf[64];
    NGroup *grp;
    NGroup *gnext;

    sprintf(buf, "%s.dnewsrc", user);
    fi = openlib_write(buf);
    if (fi == NULL)
	printf("Unable to open %s\n", buf);
    for (grp = GBase; grp; grp = gnext) {
	gnext = grp->Next;

	if (fi) {
	    if (grp->Enabled)
		fputc('y', fi);
	    else
		fputc('n', fi);
	    fprintf(fi, " %-20s ", grp->Name);
	    WriteRange(fi, grp->RList);
	    fputc(' ', fi);
	    WriteRange(fi, grp->MList);
	    fputc(' ', fi);
	    WriteRange(fi, grp->KList);
	    fputc('\n', fi);
	}
	free(grp->Name);
	FreeRange(grp->RList);
	FreeRange(grp->MList);
	FreeRange(grp->KList);
	free(grp);
    }
    if (fi)
	fclose(fi);
    GBase = NULL;
}

/*
 *  Create a new Newsrc file for a user
 */

void
CreateNewsrc(user)
char *user;
{
    char buf[64];
    FILE *fi;

    sprintf(buf, "%s.dnewsrc", user);
    if (fi = openlib_write(buf)) {
	ScanNewsDirs(cns_tag, fi);
	fclose(fi);
    }
}

static void
cns_tag(path, tail, fi)
char *path;
char *tail;
FILE *fi;
{
    FILE *fj;

    sprintf(TmpBuf, "%s/.next", path);
    if (fj = fopen(TmpBuf, "r")) {
	int artno;
	if (fscanf(fj, "%d", &artno) == 1) {
	    artno -= 50;
	    if (artno < 0)
		artno = 0;
	    fprintf(fi, "y %-20s 0-%d - -\n", tail, artno);
	    printf("Adding %s\n", tail);
	}
	fclose(fj);
    }
}

/*
 *  support
 */

static char *
fgetstring(fi)
FILE *fi;
{
    char buf[128];
    short i = 0;
    short c;

    while ((c = fgetc(fi)) == ' ' || c == 9);
    while (c != EOF && c != ' ' && c != 9 && i < sizeof(buf) - 1) {
	buf[i++] = c;
	c = fgetc(fi);
    }
    buf[i] = 0;
    if (c != EOF)
	ungetc(c, fi);
    return(strdup(buf));
}

static Range *
ReadRange(fi)
FILE *fi;
{
    Range *rb;
    Range **rp = &rb;
    short c;
    short is, ie;

    while ((c = fgetc(fi)) == ' ' || c == 9);
    if (c == '-')       /*  no range    */
	return(NULL);
    while (c != EOF) {
	is = 0;
	while (c >= '0' && c <= '9') {
	    is = is * 10 + c - '0';
	    c = getc(fi);
	}
	if (c == '-') {
	    ie = 0;
	    c = getc(fi);
	    while (c >= '0' && c <= '9') {
		ie = ie * 10 + c - '0';
		c = getc(fi);
	    }
	} else {
	    ie = is;
	}
	{
	    Range *r = malloc(sizeof(Range));
	    r->SNo = is;
	    r->ENo = ie;
	    *rp = r;
	    rp = &r->Next;
	}
	if (c != ',')
	    break;
	c = fgetc(fi);
    }
    if (c != EOF)
	ungetc(c, fi);
    *rp = NULL;
    return(rb);
}

static void
WriteRange(fi, r)
FILE *fi;
Range *r;
{
    short first = 1;

    if (r == NULL) {
	fputc('-', fi);
	return;
    }
    while (r) {
	if (!first)
	    putc(',', fi);
	if (r->SNo == r->ENo)
	    fprintf(fi, "%d", r->SNo);
	else
	    fprintf(fi, "%d-%d", r->SNo, r->ENo);
	first = 0;
	r = r->Next;
    }
}

void
RangeAdd(rp, sno, eno)
Range **rp;
short sno;
short eno;
{
    Range *r;
    Range *rn;

    for (r = *rp; r; r = *rp) {
	if (eno < r->SNo - 1) {     /*  insert before   */
	    rn = malloc(sizeof(Range));
	    *rp = rn;
	    rn->Next = r;
	    rn->SNo = sno;
	    rn->ENo = eno;
	    return;
	}
	if (sno <= r->ENo + 1) {     /*  extend this ran */
	    if (sno < r->SNo)
		r->SNo = sno;
	    if (eno > r->ENo)
		r->ENo = eno;

	    /*
	     *	delete/combine later rans that the new range overlaps
	     */
	    while ((rn = r->Next) && r->ENo >= rn->SNo - 1) {
		if (r->ENo < rn->ENo)
		    r->ENo = rn->ENo;
		r->Next = rn->Next;
		free(rn);
	    }
	    return;
	}
	rp = &r->Next;
    }
    /*
     *	Append to end
     */
    rn = malloc(sizeof(Range));
    rn->Next = NULL;
    rn->SNo = sno;
    rn->ENo = eno;
    *rp = rn;
}

void
RangeDel(rp, sno, eno)
Range **rp;
short sno;
short eno;
{
    Range *r;
    Range *rn;

    for (r = *rp; r; r = *rp) {
	if (sno <= r->ENo && eno >= r->SNo) {
	    if (sno <= r->SNo && eno >= r->ENo) {   /*  delete entirely */
		*rp = r->Next;
		free(r);
		continue;
	    } else if (sno > r->SNo && eno < r->ENo) {  /*  split   */
		rn = malloc(sizeof(Range));
		rn->Next = r->Next;
		r->Next = rn;
		rn->ENo = r->ENo;
		r->ENo = sno - 1;
		rn->SNo = eno + 1;
		break;
	    } else if (sno <= r->SNo) {              /*  else reduce an end */
		r->SNo = eno + 1;
	    } else if (eno >= r->ENo) {
		r->ENo = sno - 1;
	    }
	} else {
	    if (eno < r->SNo)   /*  done    */
		break;
	}
	rp = &r->Next;
    }
}

/*
 *  Returns index after idx that is in the range
 */

int
NextInRange(r, idx)
Range *r;
short idx;
{
    while (r && idx >= r->ENo)
	r = r->Next;
    if (r) {
	if (idx < r->SNo)
	    return(r->SNo);
	return(idx + 1);
    }
    return(-1);
}

/*
 *  Returns index after idx that is not in the range
 */

int
NextNotInRange(r, idx)
Range *r;
short idx;
{
    while (r && idx >= r->ENo)
	r = r->Next;
    if (r) {
	if (idx < r->SNo - 1)
	    return(idx + 1);
	return(r->ENo + 1);
    }
    return(idx + 1);
}

static void
FreeRange(r)
Range *r;
{
    Range *rn;

    while (r) {
	rn = r->Next;
	free(r);
	r = rn;
    }
}


/*
 *  debugging
 */

static void
PrintRange(r)
Range *r;
{
    WriteRange(stdout, r);
    puts("");
}

static long
GetMaxArtNo(group)
char *group;
{
    FILE *fi;
    int max = 0;

    sprintf(TmpBuf, "%s/.next", MakeConfigPath(UUNEWS, group));
    if (fi = fopen(TmpBuf, "r")) {
	fscanf(fi, "%d", &max);
	fclose(fi);
    }
    return(max);
}

/*
 *  References: and Message-ID: cache
 */

void
ClearGroupCache(grp)
NGroup *grp;
{
    FNode **fn;
    short i;

    if (grp->IdBase) {
	for (fn = grp->IdBase, i = 0; i < FNHASHSIZE; ++i, ++fn) {
	    ClearGroupChain(*fn);
	    *fn = NULL;
	}
    }

    if (grp->RefBase) {
	for (fn = grp->RefBase, i = 0; i < FNHASHSIZE; ++i, ++fn) {
	    ClearGroupChain(*fn);
	    *fn = NULL;
	}
    }

    if (grp->SubBase) {
	for (fn = grp->SubBase, i = 0; i < FNHASHSIZE; ++i, ++fn) {
	    ClearGroupChain(*fn);
	    *fn = NULL;
	}
    }
}

void
ClearGroupChain(fn)
FNode *fn;
{
    FNode *fnext;

    while (fn) {
	fnext = fn->Next;
	if (fn->Id)
	    free(fn->Id);
	free(fn);
	fn = fnext;
    }
}

void
CreateFNCache(pht)
FNode ***pht;
{
    if (*pht == NULL) {
	*pht = malloc(FNHASHSIZE * sizeof(FNode *));
	setmem(*pht, FNHASHSIZE * sizeof(FNode *), 0);
    }
}

FNode **
FindFNCache(htab, msgno)
FNode **htab;
{
    FNode **pfn = htab + (msgno & FNHASHMASK);
    FNode *fn;

    if (htab == NULL)
	return(NULL);
    while (fn = *pfn) {
	if (fn->MsgNo == msgno)
	    break;
	pfn = &fn->Next;
    }
    return(pfn);
}

void
AddFNCache(pfn, msgno, id)
FNode **pfn;
long msgno;
char *id;
{
    FNode *fn = malloc(sizeof(FNode));

    if (fn) {
	*pfn = fn;
	fn->MsgNo = msgno;
	fn->Id = id;
	fn->Next = NULL;
    }
}

NGroup *
PrevGroup(grp)
NGroup *grp;
{
    NGroup *gp2;

    for (gp2 = GBase; gp2 && gp2->Next != grp; gp2 = gp2->Next);
    return(gp2);
}

NGroup *
LastGroup()
{
    NGroup *grp;

    for (grp = GBase; grp && grp->Next; grp = grp->Next);
    return(grp);
}

