/*
 *	Name:				file.c
 *
 * Description:	File related functions for xferq.library
 *
 * Copyright:		1992-1993 by David Jones.
 *
 * Distribution:
 *		This program is free software; you can redistribute it and/or modify
 *		it under the terms of the GNU General Public License as published by
 *		the Free Software Foundation; either version 2 of the License, or
 *		(at your option) any later version.
 *
 *		This program is distributed in the hope that it will be useful,
 *		but WITHOUT ANY WARRANTY; without even the implied warranty of
 *		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *		GNU General Public License for more details.
 *
 *		You should have received a copy of the GNU General Public License
 *		along with this program; if not, write to:
 *
 *				The Free Software Foundation		David Jones
 *				675 Mass Ave							6730 Tooney Drive
 *				Cambridge, MA							Orleans, Ontario
 *				02139										K1C 6R4
 *				USA										Canada
 *
 *	Usenet:	gnu@prep.ai.mit.edu					aa457@freenet.carleton.ca
 *	Fidonet:												1:163/109.8
 *
 *		$Log: $
 *
 */

#include <exec/types.h>
#include <exec/semaphores.h>
#include <execlists.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <dos/exall.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/utility.h>
#include <utility/hooks.h>
#include <utility/tagitem.h>
#include <string.h>
#include "xferq.h"
#include "xferqint.h"
#include "xferq_pragmas.h"

struct ReadRec {
	BPTR normFH;	/* File handle for name */
	BPTR scoreFH;	/* File handle for _name */
	int flags;		/* See below */
};

#define	FOUND_NONE	0	/* Found no files */
#define	FOUND_NORM	1	/* Found name */
#define	FOUND_SCORE	2	/* Found _name */
#define	FOUND_BOTH	3	/* Found both */

struct TypeAddr {
	char *str;		/* Address string */
	LONG type;		/* Network type */
	char null;		/* Null byte */
};

struct ScanDir {
	BPTR lock;					/* Lock to scan */
	char **parts;				/* Current template part */
	char **namep;				/* Pointer to path name requested by caller */
	void *object;				/* Object template */
	struct VarList *vl;		/* Varlist of partial scan */
	struct Hook *hook;		/* Hook to call */
	char *path;					/* Current path string */
	long flags;					/* Flags requested by caller */
	struct TagItem *tags;	/* Tags sent by caller */
	void *globals;				/* Globals of caller */
};


#define	SEQUENCE_QUANTUM	100	/* Distance between sequence syncs */

extern BOOL QueueScanned;
extern struct SignalSemaphore QueueLock;

extern struct NetAddress *DefaultAddr;
extern struct Session *QueueList;

char *QFileName = "_$(SEQ:8X).queue";

BOOL ConfigRead;

ULONG Seq, High;
struct SignalSemaphore SeqLock;


void OpenReadRec(char *name, struct ReadRec *rr)
/*
 *	In:	name				Name of underscore file
 *			rr					Pointer to read-recovery structure
 *
 * Does:	This function opens the files name and _name, and stores the
 *			file handles in the corresponding slots of the ReadRec
 *			structure.  rr->flags tells you what was found.
 */
{

	rr->flags = FOUND_NONE;
	if (*name == '_') {
		rr->normFH = Open(&name[1], MODE_OLDFILE);
		if (rr->normFH) {
			rr->flags |= FOUND_NORM;
		}
		rr->scoreFH = Open(name, MODE_OLDFILE);
		if (rr->scoreFH) {
			rr->flags |= FOUND_SCORE;
		}
	}
	else {
		rr->normFH = Open(name, MODE_OLDFILE);
		if (rr->normFH) {
			rr->flags |= FOUND_NORM;
		}
		rr->scoreFH = NULL;
	}
}


void InitSeq(void)
/*
 *	Does:	Initializes the sequence number subsystem.
 */
{

	InitSemaphore(&SeqLock);
	Seq = High = 0;
}


BOOL WriteSeq(void)
/*
 * Does:	Updates XFERQ:seq with the given number.
 */
{
BPTR fh;

	High = Seq + SEQUENCE_QUANTUM;
	fh = Open("XFERQ:_seq", MODE_NEWFILE);
	if (fh) {
		FPrintf(fh, "%08lx\n", High);
		Close(fh);
		DeleteFile("XFERQ:seq");
		Rename("XFERQ:_seq", "XFERQ:seq");
		return TRUE;
	}
	else {
		XfqSetLocalError(XQERROR_DOS);
		return FALSE;
	}
}


ULONG ReadSeqEntry(BPTR fh)
/*
 *	In:	fh					File handle to read from
 *
 * Does:	Reads a sequence file and updates Seq.
 */
{
char buf[12];
ULONG num;
char *result;

	result = FGets(fh, buf, sizeof(buf));
	Close(fh);
	if (result && GetNumber(buf, 16, 0, &num)) {
		return num;
	}
	return 0;
}


ULONG ReadSeq(void)
/*
 *	Does:	Reads the next sequence number from disk.
 */
{
struct ReadRec rr;
BPTR oldLock, cdLock;
ULONG seq1, seq2;
LONG seqdiff;

	cdLock = Lock("XFERQ:", SHARED_LOCK);
	if (!cdLock) {
		XfqSetLocalError(XQERROR_DOS);
		return 0;
	}
	oldLock = CurrentDir(cdLock);
	OpenReadRec("_seq", &rr);
	switch (rr.flags) {
	case FOUND_NONE:
		/*
		 *		Assume no previous sequence file.
		 */
		Seq = 1;
		break;
	case FOUND_NORM:
		Seq = ReadSeqEntry(rr.normFH);
		break;
	case FOUND_SCORE:
		Seq = ReadSeqEntry(rr.scoreFH);
		break;
	case FOUND_BOTH:
		seq1 = ReadSeqEntry(rr.normFH);
		seq2 = ReadSeqEntry(rr.scoreFH);
		/*
		 *		If both sequence numbers are readable, then take the
		 *		maximum, considering wraparound.
		 *
		 *		If only one is readable, then add the sequence quantum to
		 *		it, and handle wraparound.
		 */
		if (seq1 && seq2) {
			seqdiff = seq1 - seq2;
			Seq = (seqdiff > 0) ? seq1 : seq2;
		}
		else {
			Seq = seq1 ? seq1 : seq2;
			Seq += SEQUENCE_QUANTUM;
			if (Seq <= SEQUENCE_QUANTUM) {
				Seq = 1;
			}
		}
		break;
	}
	CurrentDir(oldLock);
	UnLock(cdLock);
	return Seq;
}


ULONG LibNextSeq(ULONG num)
/*
 *	In:	num		D0		Number of numbers to allocate
 *
 *	Does:	Returns a unique 32-bit sequence number each time the function
 *			is called.  The numbers will repeat after 2^32 calls.
 */
{
ULONG value;

	ObtainSemaphore(&SeqLock);
	if (!Seq) {
		if (!ReadSeq() || !WriteSeq()) {
			ReleaseSemaphore(&SeqLock);
			return 0;
		}
	}
	if (Seq + num < Seq) {
		Seq = 1;
		if (!WriteSeq()) {
			ReleaseSemaphore(&SeqLock);
			return 0;
		}
	}
	value = Seq;
	Seq += num;
	if (Seq >= High) {
		if (!WriteSeq()) {
			ReleaseSemaphore(&SeqLock);
			return 0;
		}
	}
	ReleaseSemaphore(&SeqLock);
	return value;
}


ULONG __saveds __asm LIBNextSeq(register __d0 ULONG num)
{
ULONG seq;

	seq = LibNextSeq(num);
	SetErrorTags(NULL);
	return seq;
}


ULONG TruncateFile(char *name)
/*
 *	In:	name				Name of file to truncate
 *
 * Does:	The file is given zero length, although it still exists.
 *			The function returns the appropriate XfqRemoveWork() code.
 */
{
BPTR fh;

	fh = Open(name, MODE_OLDFILE);
	if (fh) {
		SetFileSize(fh, 0, OFFSET_BEGINNING);
		Close(fh);
		return XQRM_TRUNCATED;
	}
	else {
		XfqSetLocalError(XQERROR_DOS);
		return XQRM_ERROR;
	}
}


char *FullPath(char *name)
/*
 *	In:	name				Name to take full path of
 *
 * Does:	Returns a string object which is the fully-qualified path name
 *			of the input.
 */
{
char buf[258];
int i, len;
BPTR lock;
BOOL rc;
long pathit;

	pathit = TRUE;
	XfqGetAppConfigTags(
		XQ_CfgFullPath, &pathit,
		TAG_DONE);
	if (!pathit) {
		return CopyString(name);
	}
	len = strlen(name);
	for (i = 0; i < len; i++) {
		if (name[i] == ':') {
			return CopyString(name);
		}
	}
	lock = Lock(name, ACCESS_READ);
	if (lock) {
		rc = NameFromLock(lock, buf, sizeof(buf));
		UnLock(lock);
		if (rc) {
			return CopyString(buf);
		}
		else {
			XfqSetLocalError(XQERROR_DOS);
			return NULL;
		}
	}
	XfqSetLocalError(XQERROR_DOS);
	return NULL;
}


ULONG ReadQueueContents(BPTR file, struct SiteQueue *sq)
/*
*	In:	file			File containing contents of queue.
*			sq				Site queue to read into.
*
*	Does:	Reads the contents of the file, forming the entries for the
*			site queue.  The function returns TRUE on success, FALSE
*			otherwise.
*/
{
char line[260];
struct WorkNode *wn, *twn, *fwn, *tfwn;
struct List qlist;
long n, len;
char *cp;

	/*
	 *		We first build the queue on qlist.  If all goes well, then
	 *		entries in qlist that are not already in the queue (look at
	 *		sequence numbers) are transferred to the queue.
	 *
	 *		In case of error, qlist is burned, leaving the real queue
	 *		untouched.
	 */
	NEWLIST(&qlist);
	while (FGets(file, line, sizeof(line))) {
		wn = LibCreateObject(XQO_WORKNODE, NULL);
		if (!wn) {
			goto fail;
		}
		while (line[0] != '#') {
			switch (line[0]) {
			case 'Q':
				/*
				 *		Queue file address, can be ignored.  It is used
				 *		to build site index.
				 */
				break;
			case 'F':
				len = strlen(line) - 2;	/* Drop header and newline */
				wn->node.ln_Name = CopyStringN(&line[1], len);
				if (!wn->node.ln_Name) {
					goto fail;
				}
				break;
			case 'A':
				len = strlen(line) - 2;	/* Drop header and newline */
				wn->asname = CopyStringN(&line[1], len);
				if (!wn->asname) {
					goto fail;
				}
				break;
			case 'P':
				cp = &line[1];
				GetNumber(cp, 10, 0, &n);
				wn->node.ln_Pri = n;
				break;
			case 'C':
				cp = &line[1];
				GetNumber(cp, 10, 0, &n);
				wn->userFlags = n;
				break;
			case 'S':
				cp = &line[1];
				GetNumber(cp, 10, 0, &n);
				wn->status = n;
				break;
			case 'I':
				cp = &line[1];
				GetNumber(cp, 10, 0, &n);
				wn->seq = n;
				break;
			}
			if (!FGets(file, line, sizeof(line)) && IoErr()) {
				XfqSetLocalError(XQERROR_DOS);
				goto fail;
			}
		} /* while != '#' */
		ADDTAIL(&qlist, wn);
	} /* while not EOF or error*/
	if (IoErr()) {
		XfqSetLocalError(XQERROR_DOS);
		goto fail;
	}

	Close(file);
	for (wn = FIRST(&qlist); twn = NEXT(wn); wn = twn) {
		if (wn->seq) {
			for (fwn = FIRST(&sq->workList); tfwn = NEXT(fwn); fwn = tfwn) {
				if (wn->seq == fwn->seq) {
					break;
				}
			}
		}
		if (!wn->seq || !tfwn) {
			SimpleAddWork(sq, wn);
		}
		else {
			LibDropObject(wn);
		}
	}
	return TRUE;

fail:
	Close(file);
	LibDropObject(wn);
	for (wn = FIRST(&qlist); twn = NEXT(wn); wn = twn) {
		LibDropObject(wn);
	}
	return FALSE;
}


ULONG ReadQueue(char *name, struct SiteQueue *sq, ULONG flags)
/*
 *	In:	name				Name of file to read (with underscore)
 *			sq					Site queue to put data to
 *			flags				Flags
 *
 * Does:	Reads a queue into the given site queue.
 */
{
struct ReadRec rr;
ULONG result;

#ifdef DEBUG
	kprintf("ReadQueue(name='%s', xq=%08lx)\n", name, sq);
#endif
	OpenReadRec(name, &rr);
	switch (rr.flags) {
	case FOUND_NONE:
		return FALSE;
	case FOUND_NORM:
		return ReadQueueContents(rr.normFH, sq);
	case FOUND_SCORE:
		result = ReadQueueContents(rr.scoreFH, sq);
		Rename(name, &name[1]);
		return result;
	case FOUND_BOTH:
		/* _name is probably corrupt */
		ReadQueueContents(rr.scoreFH, sq);
		return ReadQueueContents(rr.normFH, sq);
	}
}


ULONG LibReadQueue(char *name,
	struct NetAddress *addr,
	ULONG flags)
/*
 *	In:	name		A0		Name of file to read (with underscore)
 *			addr		A1		Address to queue to read into
 *			flags		D0		Flags
 *
 * Does:	Reads the queue file into the given queue structure.
 */
{
struct SiteQueue *sq;
ULONG result;

	ObtainSemaphore(&QueueLock);
	sq = FindSiteQueue(addr, FSQ_CREATE);
	
	result = ReadQueue(name, sq, flags);
	sq->flags |= XQSITE_DIRTY;
		
	ReleaseSemaphore(&QueueLock);
	return result;
}


ULONG __saveds __asm LIBReadQueue(register __a0 char *name,
	register __a1 struct NetAddress *na, register __d0 ULONG flags)
{
ULONG result;

	result = LibReadQueue(name, na, flags);
	SetErrorTags(NULL);
	return result;
}


void ReadSiteQueue(struct SiteQueue *sq)
/*
 *	In:	sq					Site node to read queue for
 *
 * Does:	Reads the queue file from disk.
 *
 */
{
char *name;
BPTR cdLock, oldLock;
ULONG *seq;

	if (!sq->seq) {
		/*
		 *		Site was not found in scan, ergo nothing to read.
		 */
		sq->flags &= ~XQSITE_UNREAD;
		return;
	}
	cdLock = Lock("XFERQ:", SHARED_LOCK);
	if (!cdLock) {
		XfqSetLocalError(XQERROR_DOS);
		return;
	}
	oldLock = CurrentDir(cdLock);
	seq = LibCreateObjectTags(XQO_SEQ,
		XQ_Sequence, sq->seq,
		TAG_DONE);
	if (!seq) {
		goto fail;
	}
	name = LibBuildString(QFileName, seq, NULL);
	LibDropObject(seq);
	if (!name) {
		goto fail;
	}
	if (ReadQueue(name, sq, 0)) {
		sq->flags &= ~XQSITE_UNREAD;
	}
	LibDropObject(name);
fail:
	CurrentDir(oldLock);
	UnLock(cdLock);
}


int PreFlushQueue(struct SiteQueue *sq)
/*
 *	In:	sq					Site queue to prepare for writing
 *
 *	Does:	Determines if the queue file will be empty or not, then uses
 *			this information to determine if the site index needs to be
 *			written.
 *
 *			- Determines which queues need to be written/deleted
 *			- Determines if site index needs updating
 */
{
struct WorkNode *wn, *twn;

	wn = FIRST(&sq->workList);
	while ((twn = NEXT(wn)) && (wn->userFlags & XQ_IMMEDIATE)) {
		wn = twn;
	}
	if (!twn && !(sq->flags & XQSITE_UNREAD)) {
		sq->flags |= XQSITE_WREMPTY;
		return (sq->flags & XQSITE_INDEXED);
	}
	else {
		sq->flags &= ~XQSITE_WREMPTY;
		return !(sq->flags & XQSITE_INDEXED);
	}
}


void WriteAddr(struct NetAddress *na, struct TypeAddr *ta)
/*
 *	In:	na					Address to write
 *			ta					Pointer to TypeAddr structure
 *
 * Does:	Writes the address string and network type to the TypeAddr
 *			structure.
 */
{

	ta->str = LibPutAddressTags(na,
		XQ_Mandatory, XQADDR_ANYTHING,
		XQ_Optional, XQADDR_ANYTHING,
		TAG_DONE);
	LibExamObjectTags(na,
		XQ_Network, &ta->type,
		TAG_DONE);
	ta->null = '\0';
}


ULONG WriteQueue(char *name, struct SiteQueue *sq, ULONG flags)
/*
 *	In:	fh					File handle to write to
 *			sq					Site Queue to write
 *			flags				Flags
 *
 * Does:	Writes the site queue out to the file.
 */
{
BPTR fh;
struct WorkNode *wn, *twn;
struct TypeAddr ta;
ULONG result;

	result = TRUE;
	if ((sq->flags & XQSITE_WREMPTY) && (flags & XQIO_DELEMPTY)) {
		DeleteFile(&name[1]);
	}
	else {
		fh = Open(name, MODE_NEWFILE);
		if (!(flags & XQIO_OLDSTYLE)) {
			WriteAddr(sq->sn.addr, &ta);
			FPrintf(fh, "Q%4s %s\n", &ta.type, ta.str);
			LibDropObject(ta.str);
		}
		if (fh) {
			wn = FIRST(&sq->workList);
			while (twn = NEXT(wn)) {
				/*
				 *		If IMMEDIATE is in effect, then anything that can cause
				 *		the queue to be read in also nukes any sessions that
				 *		were up so don't even bother to write these out.
				 *
				 *		Similar logic goes for SENDLATER, but this time we
				 *		must clear the SENDLATER bit.
				 */
				if (!(wn->userFlags & XQ_IMMEDIATE)) {
					FPrintf(fh, "F%s\n", wn->node.ln_Name);
					if (wn->asname) {
						FPrintf(fh, "A%s\n", wn->asname);
					}
					FPrintf(fh, "P%ld\n", wn->node.ln_Pri);
					FPrintf(fh, "C%ld\n", wn->userFlags & ~XQ_SENDLATER);
					FPrintf(fh, "S%ld\n", wn->status);
					if (!(flags & XQIO_OLDSTYLE)) {
						FPrintf(fh, "I%ld\n", wn->seq);
					}
					FPrintf(fh, "#\n", NULL);
				}
				wn = twn;
			}
			Close(fh);
			DeleteFile(&name[1]);
			Rename(name, &name[1]);
		}
		else {
			XfqSetLocalError(XQERROR_DOS);
			result = FALSE;
		}
	}
	return result;
}


ULONG LibWriteQueue(char *name,
	struct NetAddress *addr,
	ULONG flags)
/*
 *	In:	name		A0		Name of file to write to, with underscore
 *			addr		A1		Address of queue to write
 *			flags		D0		Flags
 *
 * Does:	Writes the given queue file out to disk.
 */
{
struct SiteQueue *sq;
ULONG result;

	ObtainSemaphore(&QueueLock);
	sq = FindSiteQueue(addr, FSQ_CREATE);
	if (!sq) {
		ReleaseSemaphore(&QueueLock);
		return FALSE;
	}
	
	PreFlushQueue(sq);
	result = WriteQueue(name, sq, flags);
	ReleaseSemaphore(&QueueLock);
	return result;
}

	
ULONG __saveds __asm LIBWriteQueue(register __a0 char *name,
	register __a1 struct NetAddress *na, register __d0 ULONG flags)
{
ULONG result;

	result = LibWriteQueue(name, na, flags);
	SetErrorTags(NULL);
	return result;
}


ULONG WriteSiteIndex(void)
/*
 *	Does:	Writes the site index file out to disk.
 *
 */
{
BPTR fh;
struct SiteQueue *sq, *tsq;
struct TypeAddr ta;

	fh = Open("XFERQ:_site.index", MODE_NEWFILE);
	if (!fh) {
		XfqSetLocalError(XQERROR_DOS);
		return FALSE;
	}
	
	sq = FIRST(&QueueList->sessList);
	while (tsq = NEXT(sq)) {
		if (!(sq->flags & XQSITE_WREMPTY)) {
			WriteAddr(sq->sn.addr, &ta);
			FPrintf(fh, "%08lx %4s %s\n", sq->seq, &ta.type, ta.str);
			LibDropObject(ta.str);
		}
		sq = tsq;
	}
	Close(fh);
	DeleteFile("XFERQ:site.index");
	Rename("XFERQ:_site.index", "XFERQ:site.index");
	/*
	 *		If the index was written OK, then mark sites as indexed.
	 */
	sq = FIRST(&QueueList->sessList);
	while (tsq = NEXT(sq)) {
		if (sq->flags & XQSITE_WREMPTY) {
			sq->flags &= ~XQSITE_INDEXED;
		}
		else {
			sq->flags |= XQSITE_INDEXED;
		}
		sq = tsq;
	}
	return TRUE;
}


ULONG LibFlushQueue(void *object)
/*
 *	In:	object	A0		Pointer to object to flush
 *
 *	Does:	Writes one or more site queues out to disk.  If object is an
 *			address, then only that site queue will be flushed.  If object
 *			is a session, then all addresses in the session will be flushed.
 *			If object is NULL, then all queues are flushed.
 */
{
struct QueueWalk qw;
struct SiteQueue *sq;
BOOL indexDirty, result;
char *name;
BPTR cdLock, oldLock;
ULONG *seq;

	cdLock = Lock("XFERQ:", SHARED_LOCK);
	if (!cdLock) {
		XfqSetLocalError(XQERROR_DOS);
		return FALSE;
	}
	oldLock = CurrentDir(cdLock);
	
	ObtainSemaphore(&QueueLock);
	qw.actions = FSQ_NOP;
	
	/*
	 *		Since the site index affects all sites, it must be updated
	 *		even if we are flushing only a single queue.
	 */
	sq = FirstSiteQueue(NULL, &qw);
	indexDirty = FALSE;
	while (sq) {
		indexDirty |= PreFlushQueue(sq);
		sq = NextSiteQueue(&qw);
	}
	
	if (indexDirty) {
		DeleteFile("XFERQ:site.index");
	}
	
	result = TRUE;
	sq = FirstSiteQueue(object, &qw);
	while (sq) {
		if (sq->flags & XQSITE_DIRTY) {
			if (!sq->seq) {
				sq->seq = LibNextSeq(1);
				if (!sq->seq) {
					result = FALSE;
					goto fail;
				}
			}
			seq = LibCreateObjectTags(XQO_SEQ,
				XQ_Sequence, sq->seq,
				TAG_DONE);
			if (!seq) {
				result = FALSE;
				goto fail;
			}
			name = LibBuildString(QFileName, seq, NULL);
			LibDropObject(seq);
			if (!name) {
				result = FALSE;
				goto fail;
			}
	
			result &= WriteQueue(name, sq, XQIO_DELEMPTY);
			LibDropObject(name);
			sq->flags &= ~XQSITE_DIRTY;
		}
		sq = NextSiteQueue(&qw);
	}
	
	if (indexDirty) {
		result &= WriteSiteIndex();
	}
	
fail:
	CurrentDir(oldLock);
	UnLock(cdLock);
	ReleaseSemaphore(&QueueLock);
	return result;
}


ULONG __saveds __asm LIBFlushQueue(register __a0 void *object)
{
ULONG result;

	result = LibFlushQueue(object);
	SetErrorTags(NULL);
	return result;
}


void RecurseScan(struct ScanDir *sd)
/*
 *	In:	sd					Pointer to control info
 *
 *	Does:	This function performs the main directory scanning.  If it
 *			encounters a directory, it may call itself recursively.
 */
{
struct ExAllControl *eac;
struct ExAllData *ead;
struct ScanDir subSD;
char buffer[256];
int nameLen, error, more, ioErr, active;
void *newObj, *object;
char *name;
BPTR oldLock;

	eac = AllocDosObject(DOS_EXALLCONTROL, NULL);
	if (!eac) {
		XfqSetLocalError(XQERROR_NOMEM);
		return;
	}
	eac->eac_LastKey = NULL;
	eac->eac_MatchString = NULL;
	eac->eac_MatchFunc = NULL;
	if (sd->namep) {
		if (sd->path) {
			nameLen = strlen(sd->path);
		}
		else {
			nameLen = 0;
		}
	}
#ifdef DEBUG
	kprintf("RecurseScan: namep = %08lx, path = '%s', nameLen = %ld\n",
		sd->namep, sd->path, nameLen);
#endif
	active = TRUE;
	do {
		more = ExAll(sd->lock, (struct ExAllData *)buffer, sizeof(buffer),
			ED_TYPE, eac);
		ioErr = IoErr();
		if ((!more) && (ioErr != ERROR_NO_MORE_ENTRIES)) {
			break;
		}
		if (!active) {
			continue;	/* handles FALSE return from call-back */
		}
		if (eac->eac_Entries) {
			for (ead = (struct ExAllData *)buffer; ead; ead = ead->ed_Next) {
#ifdef DEBUG
				kprintf("name/type: %s %ld\n", ead->ed_Name, ead->ed_Type);
#endif
				XfqSetLocalError(XQERROR_OK);
				error = XQERROR_OK;
				name = ead->ed_Name;
				if (name[0] == '_' && (sd->flags & XQSCAN_UNDERSCORE)) {
					name++;
				}
				if (ead->ed_Type < 0 && !sd->parts[1]) {
					/* have a file */
					ParseVarList(name, sd->parts[0], sd->vl);
					error = XfqGetLocalError();
					if (!error) {
						if (sd->namep) {
							sd->path = NeedString(sd->path, strlen(ead->ed_Name));
							if (!sd->path) {
								sd->namep = NULL;
							}
							else {
								strcat(sd->path, ead->ed_Name);
								*sd->namep = sd->path;
							}
						}
						object = LibCopyObject(sd->object);
						newObj = ModifyObjectVarList(object, sd->vl);
						if (newObj) {
							if (!CallHookRes(sd->hook, newObj, sd->tags,
								sd->globals)) {
								active = FALSE;
								continue;
							}
						}
						else {
							LibDropObject(object);
						}
					}
				}
				else if (ead->ed_Type >= 0 && sd->parts[1]) {
					/* have a dir */
					ParseVarList(name, sd->parts[0], sd->vl);
					error = XfqGetLocalError();
					if (!error) {
						if (sd->namep) {
							sd->path = NeedString(sd->path,
								strlen(ead->ed_Name) + 1);
							if (!sd->path) {
								sd->namep = NULL;
							}
							else {
								strcat(sd->path, ead->ed_Name);
								strcat(sd->path, "/");
							}
						}
						subSD = *sd;
						oldLock = CurrentDir(sd->lock);
						subSD.lock = Lock(ead->ed_Name, SHARED_LOCK);
						CurrentDir(oldLock);
						if (!subSD.lock) {
							XfqSetLocalError(XQERROR_DOS);
							active = FALSE;
							continue;
						}
						subSD.parts++;
						RecurseScan(&subSD);
						error = XfqGetLocalError();
						UnLock(subSD.lock);
					}
				}
				if (sd->namep && sd->path) {
#ifdef DEBUG
					kprintf("Trimming path to %ld\n", nameLen);
#endif
					sd->path[nameLen] = '\0';
				}
				if (error != XQERROR_OK && error != XQERROR_BADPARSE) {
					active = FALSE;
					continue;
				}
			}
		}
	} while (more);
	if (ioErr && ioErr != ERROR_NO_MORE_ENTRIES) {
		XfqSetLocalError(XQERROR_DOS);
	}
	FreeDosObject(DOS_EXALLCONTROL, eac);
}

					
void LibScanDirCallBack(BPTR lock,
	char *template, void *object,
	struct TagItem *tags, void *globals)
/*
 *	In:	lock		D0		BPTR to lock to start from
 *			template	A0		Template to use for file scan
 *			object	A1		Object template to base objects on
 *			tags		A2		Tag list controlling operation
 *
 * Does:	Scans the directory given by lock looking for files that match
 *			the given template.  If a matching file is found, an object
 *			is created based on the object template and the arguments from
 *			the file name.  A call-back function is called.  Valid tags are:
 *
 *			XQ_CallBack		-	specifies the function to call
 *			XQ_FileName		-	data is pointer to string object for file name
 *			XQ_ScanFlags	-	flags controlling operation
 */
{
int numParts;
char *p, **parts, *partStr;
struct ScanDir sd;
BPTR curLock;

	sd.hook = (struct Hook *)GetTagData(XQ_CallBack, NULL, tags);
	if (!sd.hook) {
		XfqSetLocalError(XQERROR_NOTAG);
		SetErrorTags(tags);
		return;
	}
	sd.namep = (char **)GetTagData(XQ_FileName, NULL, tags);
	sd.flags = GetTagData(XQ_ScanFlags, 0, tags);
	
	/*
	 *		The directory scanning will not pick up fully-pathed names.
	 *		We must do this ourself.
	 */
	if (lock) {
		curLock = DupLock(lock);
	}
	else {
		curLock = Lock("", SHARED_LOCK);
	}
#ifdef DEBUG
	kprintf("lock = %08lx, curLock = %08lx\n", lock, curLock);
#endif
	if (!curLock) {
		XfqSetLocalError(XQERROR_DOS);
		SetErrorTags(tags);
		return;
	}
	
	/*
	 *		Part 1: look for foo:blah.  If found, lock foo: and parse
	 *		from blah.
	 */
	p = template;
	while (*p && *p != ':' && *p != '$') {
		p++;
	}
	if (*p == ':') {
		UnLock(curLock);
		
		p++;
		sd.path = CopyStringN(template, p - template);
		if (!sd.path) {
			SetErrorTags(tags);
			return;
		}
		curLock = Lock(sd.path, SHARED_LOCK);
		template = p;
		if (!curLock) {
			XfqSetLocalError(XQERROR_DOS);
			SetErrorTags(tags);
			return;
		}
	}
	else {
		sd.path = NULL;
	}
#ifdef DEBUG
		kprintf("Starting with path: %s\n", sd.path);
#endif
	/*
	 *		Part 2: for each '/', back up one dir.
	 */
	while (*template == '/') {
		template++;
		curLock = ParentDir(curLock);
	}
	
	/*
	 *		Break up the template into pieces that the scanner can handle.
	 */
	numParts = 2;
	p = template;
	while (*p) {
		if (*p++ == '/') {
			numParts++;
		}
	}
	partStr = CopyString(template);
	if (!partStr) {
		UnLock(curLock);
		SetErrorTags(tags);
		return;
	}
	parts = AllocObject(numParts * 4, XQO_STRING);
	if (!parts) {
		UnLock(curLock);
		FreeObject(partStr);
		SetErrorTags(tags);
		return;
	}
	p = partStr;
	numParts = 1;
	parts[0] = partStr;
	while (*p) {
		if (*p++ == '/') {
			parts[numParts++] = p;
			p[-1] = '\0';
		}
	}
	parts[numParts] = NULL;
	
	sd.lock = curLock;
	sd.parts = parts;
	sd.object = object;
	sd.globals = globals;
	sd.tags = tags;
	sd.vl = NewVarList();
	if (sd.vl) {
		RecurseScan(&sd);
		DropVarList(sd.vl);
	}
	UnLock(curLock);
	FreeObject(parts);
	FreeObject(partStr);
	SetErrorTags(tags);
}


void __saveds __asm LIBScanDirCallBack(register __d0 BPTR lock,
	register __a0 char *tpl, register __a1 void *obj,
	register __a2 struct TagItem *tags, register __a4 void *globals)
{

	LibScanDirCallBack(lock, tpl, obj, tags, globals);
	SetErrorTags(tags);
}


void LibScanDirCallBackTags(BPTR lock, char *tpl, void *obj, Tag tag, ...)
{

	LibScanDirCallBack(lock, tpl, obj, (struct TagItem *)&tag, Geta4());
}


void ReadSiteIndexFile(BPTR fh)
/*
 *	In:	fh					File handle to read from
 *
 * Does:	Reads the site index from the given file handle.
 */
{
char buffer[260];
struct SiteQueue *sq;
struct NetAddress *na;
long seq;
struct TypeAddr ta;

	while (FGets(fh, buffer, sizeof(buffer))) {
		GetNumber(buffer, 16, 0, &seq);
		memcpy(&ta.type, &buffer[9], 4);
		na = LibCreateObjectTags(XQO_ADDRESS,
			XQ_Network, ta.type,
			TAG_DONE);
		na = LibGetAddressTags(&buffer[14], na,
			XQ_Mandatory, XQADDR_ALLBUTUSER,
			XQ_Optional, XQADDR_ALLBUTUSER,
			TAG_DONE);
		sq = FindSiteQueue(na, FSQ_CREATE);
		LibDropObject(na);
		if (!sq) {
			Close(fh);
			return;
		}
		else {
			sq->flags = XQSITE_UNREAD | XQSITE_INDEXED;
			sq->seq = seq;
		}
	}
	Close(fh);
	QueueScanned = TRUE;
}


void ReadSiteIndex(void)
/*
 *	Does:	Reads the site index file.
 *
 *			If it read OK, then it clears QueueScanned.  Otherwise, it
 *			nukes the queue, and ScanQueue() does a rebuild.
 */
{
struct ReadRec rr;
BPTR cdLock, oldLock;

	cdLock = Lock("XFERQ:", SHARED_LOCK);
	if (!cdLock) {
		return;
	}
	oldLock = CurrentDir(cdLock);
	OpenReadRec("_site.index", &rr);
	switch (rr.flags) {
	case FOUND_BOTH:
		Close(rr.normFH);
		Close(rr.scoreFH);
		/* Fall-through is intentional */
	case FOUND_NONE:
#ifdef DEBUG
		kprintf("Found both/no site index\n");
#endif
		/*
		 *		This could be fishy.  Re-build.
		 */
		break;
	case FOUND_NORM:
#ifdef DEBUG
		kprintf("Found normal index\n");
#endif
		ReadSiteIndexFile(rr.normFH);
		break;
	case FOUND_SCORE:
#ifdef DEBUG
		kprintf("Found score index\n");
#endif
		ReadSiteIndexFile(rr.scoreFH);
		break;
	}
	CurrentDir(oldLock);
	UnLock(cdLock);
}


ULONG __asm NewScanQCB(register __a0 struct Hook *hook,
	register __a2 ULONG *seq,
	register __a1 struct TagItem *tags)
/*
 *	In:	hook		A0		Pointer to hook (not used)
 *			seq		A2		Sequence number parsed from template
 *			tags		A1		Pointer to tag list
 *
 * Does:	This function is called when a queue file is found.  It
 *			reads the address from the file and creates a SiteNode.
 */
{
struct SiteQueue *sq;
struct NetAddress *na, *newna;
struct ReadRec rr;
struct TypeAddr ta;
char **name;
char line[260];
ULONG seqNum;

	seqNum = *seq;
	LibDropObject(seq);
	name = (char **)GetTagData(XQ_FileName, NULL, tags);
#ifdef DEBUG
	kprintf("QueueScan: found %s (%ld)\n", *name, strlen(*name));
#endif
	OpenReadRec(*name, &rr);
	if (rr.normFH) {
		FGets(rr.normFH, line, sizeof(line));
		Close(rr.normFH);
		if (rr.scoreFH) {
			Close(rr.scoreFH);
		}
	}
	else {
		FGets(rr.scoreFH, line, sizeof(line));
		Close(rr.scoreFH);
	}
	if (line[0] != 'Q') {
#ifdef DEBUG
		kprintf("No Q line - aborting\n");
#endif
		return FALSE;
	}
	
#ifdef DEBUG
	kprintf("Line: %s\n", line);
#endif
	memcpy(&ta.type, &line[1], 4);
	na = LibCreateObjectTags(XQO_ADDRESS,
		XQ_Network, ta.type,
		TAG_DONE);
	newna = LibGetAddressTags(&line[6], na,
		XQ_Mandatory, XQADDR_ALLBUTUSER,
		XQ_Optional, XQADDR_ALLBUTUSER,
		TAG_DONE);
	LibDropObject(na);
#ifdef DEBUG
	kprintf("na: %08lx\n", newna);
#endif
	sq = FindSiteQueue(newna, FSQ_CREATE);
	LibDropObject(newna);
	if (!sq) {
		return FALSE;
	}
	else {
		sq->flags = XQSITE_UNREAD;
		sq->seq = seqNum;
		return TRUE;
	}
}


void ScanQueue(void)
/*
 *	Does:	Scans the queue directory for queue files.  For each queue file
 *			found, it creates a site node and marks it as unread.
 */
{
BPTR oldLock, cdLock;
struct Hook hook;
char *name;
ULONG *seq;

#ifdef DEBUG
	kprintf("Reading site index...\n");
#endif
	ReadSiteIndex();
#ifdef DEBUG
	kprintf("ReadSiteIndex result %d\n", QueueScanned);
#endif
	if (QueueScanned) {
		return;
	}
	cdLock = Lock("XFERQ:", SHARED_LOCK);
	if (!cdLock) {
		XfqSetLocalError(XQERROR_DOS);
		return;
	}
	oldLock = CurrentDir(cdLock);
	hook.h_Entry = (HOOK_FUNC)NewScanQCB;
	
	seq = LibCreateObject(XQO_SEQ, NULL);
	LibScanDirCallBackTags(cdLock, &QFileName[1], seq,
		XQ_CallBack, &hook,
		XQ_ScanFlags, XQSCAN_UNDERSCORE,
		XQ_FileName, &name,
		TAG_DONE);
	LibDropObject(seq);
	if (!XfqGetLocalError()) {
#ifdef DEBUG
	kprintf("Scanned OK\n");
#endif
		QueueScanned = TRUE;
	}
	CurrentDir(oldLock);
	UnLock(cdLock);
}


void ReadConfig(void)
/*
 *	Does:	Reads the configuration file.  At the moment, the file name
 *			is XFERQ:hostaddr and contains the default domain and zone in
 *			the form domain#zone.
 */
{
BPTR fh;
char line[40];
struct NetAddress *na;

	fh = Open("XFERQ:hostaddr", MODE_OLDFILE);
	if (fh) {
		if (FGets(fh, line, sizeof(line))) {
			line[strlen(line) - 1] = '\0';
			na = LibGetAddressTags(line, NULL,
				XQ_Mandatory, XQADDR_2D,
				XQ_Optional, XQADDR_FIDO,
				TAG_DONE);
			if (na) {
				LibDropObject(DefaultAddr);
				DefaultAddr = na;
			}
		}
		Close(fh);
	}
	ConfigRead = TRUE;
}


