/*
** filefinder.c
**
** Pattern driven file finder
**
** $Id: filefinder.c,v 1.0 94/08/09 17:02:55 GF Exp Locker: GF $
**
**************************************************************************
**
** Copyright © 1994 by Gabriele Falcioni. All Rights Reserved.
**
** 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, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** For inquiries, comments, donations, bug reports, write to:
**
** Gabriele Falcioni
** via E. Cialdini, 50
** I-60122 Ancona
** ITALY
**
*/

#include <string.h>

#include <exec/types.h>
#include <exec/memory.h>

#include <dos/dos.h>
#include <dos/stdio.h>
#include <dos/dosasl.h>
#include <dos/rdargs.h>

#include <proto/exec.h>
#include <proto/utility.h>
#include <proto/dos.h>

#include "filefinder.h"

/* --- My favourite memory macros ------------------------------------- */

#define STDMEM (MEMF_PUBLIC|MEMF_CLEAR)

#define ConStruct(type)		((type *)AllocMem(sizeof(type),STDMEM))
#define DiStruct(ptr)		FreeMem((ptr),sizeof(*(ptr)))
#define ClearStruct(ptr)	memset((ptr),0,sizeof(*(ptr)))

/* --- Some speed-up macros ------------------------------------------- */

#define OPT_ALL(ff)			((ff)->ff_Flags & FILEFF_ALL)
#define DO_DIR(ff)			((ff)->ff_Flags & FILEFF_DODIR)

#define NEWSTATE(ff,s)		((ff)->ff_State = (s))

#define DIR_ENTRY_TYPE(ff)	((ff)->ff_AP.ap_Info.fib_DirEntryType)
#define DIR_ENTRY_LOCK(ff)	((ff)->ff_AP.ap_Current->an_Lock)
#define DIR_ENTRY_NAME(ff)	((ff)->ff_AP.ap_Info.fib_FileName)

#define SETFLAGS(ff,mask)	((ff)->ff_AP.ap_Flags |=  (mask))
#define CLRFLAGS(ff,mask)	((ff)->ff_AP.ap_Flags &= ~(mask))
#define INVFLAGS(ff,mask)	((ff)->ff_AP.ap_Flags ^=  (mask))

#define CHECKFLG(ff,mask)	((ff)->ff_AP.ap_Flags & (mask))

/* -------------------------------------------------------------------- */

/****** filefinder.lib/CreateFileFinderA *********************************
*
*	NAME
*		CreateFileFinderA -- FileFinder Constructor.
*		CreateFileFinder  -- varargs stub for CreateFileFinderA().
*
*	SYNOPSIS
*		ff = CreateFileFinderA(taglist);
*		d0                     a0
*
*		struct FileFinder *CreateFileFinderA(struct TagItem *);
*
*		ff = CreateFileFinder(tag1, ... );
*		d0
*
*		struct FileFinder *CreateFileFinder(Tag, ... );
*
*	FUNCTION
*		It creates and initializes an instance of the FileFinder structure,
*		used to examine a list of files. The public part of the structure
*		contains an instance of the AnchorPath structure. Here the caller can
*		find all the relevant informations relative to examined files and
*		directories. This function is principally a friendly interface to
*		MatchFirst()/MatchNext() calls. Support is provided for array of
*		pattern string, user option ALL/S, callback Hooks, and break signals.
*
*	INPUTS
*		taglist - a pointer to a standard tag list. The following tags are
*			supported:
*
*			FILEFT_Arg - (UBYTE *) a pointer to a null-terminated string. The
*				pattern to use to find files.
*			FILEFT_Argv - (UBYTE **) like FILEFT_Arg, but this points to an
*				array of pointers to null-terminated strings. The array must be
*				terminated by a NULL entry.
*			FILEFT_All - (BOOL) enables the user option ALL/S. It forces the
*				matcher to accept all files contained in subdirs of matching
*				directories.
*			FILEFT_MaxNameLen - (LONG) sets the pathname buffer length. Default
*				length is 256.
*			FILEFT_BreakBits - (LONG) contains the mask of the signals where
*				to break on. It defaults to SIGBREAKF_CTRL_C.
*			FILEFT_DirHook - (struct Hook *) a pointer to an Hook called on
*				every directory. The object parameter points to the AnchorPath
*				structure for this directory. The message parameter points to a
*				LONG that can be one of:
*				FFHOOK_ENTERDIR	- about to enter this directory,
*				FFHOOK_LEAVEDIR	- about to leave this directory,
*				FFHOOK_IGNORED	- no action scheduled.
*				The Hook can set or clear either the APF_DODIR or APF_DIDDIR
*				flags for this directory at will. The Hook may return 0 to
*				continue, or a valid DOS error code to abort the scan.
*
*	RESULT
*		ff - a pointer to an instance of FileFinder structure or NULL if not
*			enough memory.
*
*	NOTES
*		A directory name without wildcards is taken as short for:
*		"every file of [dir name]".
*
*	SEE ALSO
*		DeleteFileFinder()
*
*************************************************************************/

struct FileFinder * ASM CreateFileFinderA(REG(a0) struct TagItem *tl)
{
	register struct FileFinder *ff;
	LONG buflen;

	buflen = GetTagData(FILEFT_MaxNameLen,256,tl);

	if (ff = AllocVec(sizeof(struct FileFinder) + buflen,STDMEM)) {

		ff->ff_DirHook = (struct Hook *)GetTagData(FILEFT_DirHook,NULL,tl);

		if (ff->ff_SABuf[0] = GetTagData(FILEFT_Arg,NULL,tl))
			ff->ff_FList = (UBYTE **)ff->ff_SABuf;
		else
			ff->ff_FList = (UBYTE **)GetTagData(FILEFT_Argv,NULL,tl);

		if (GetTagData(FILEFT_All,FALSE,tl))
			ff->ff_Flags |= FILEFF_ALL;

		ff->ff_BrkMsk = GetTagData(FILEFT_BreakBits,SIGBREAKF_CTRL_C,tl);
		ff->ff_BufLen = buflen;
	}

	return ff;
}

struct FileFinder * __stdargs CreateFileFinder(Tag tag1, ... )
{
	return CreateFileFinderA((struct TagItem *)&tag1);
}

/* -------------------------------------------------------------------- */

/****** filefinder.lib/DeleteFileFinder **********************************
*
*	NAME
*		DeleteFileFinder -- FileFinder Distructor.
*
*	SYNOPSIS
*		DeleteFileFinder(ff);
*		                 a0
*
*		VOID DeleteFileFinder(struct FileFinder *);
*
*	FUNCTION
*		It frees all the resources allocated by CreateFileFinderA().
*
*	INPUTS
*	ff - a pointer to a valid FileFinder structure. A NULL pointer is
*		tolerated and ignored.
*
*	SEE ALSO
*		CreateFileFinderA()
*
*************************************************************************/

VOID ASM DeleteFileFinder(REG(a0) struct FileFinder *ff)
{
	if (ff) {
		switch (ff->ff_State) {
			case FFSTATE_NEXT:
			case FFSTATE_FIRST:
				MatchEnd(&ff->ff_AP);
		}

		FreeVec(ff);
	}
}

/* -------------------------------------------------------------------- */

/****** filefinder.lib/FindFiles *****************************************
*
*	NAME
*		FindFiles -- Find the next matching file.
*
*	SYNOPSIS
*		success = FindFiles(ff);
*		d0                  a0
*
*		BOOL FindFiles(struct FileFinder *);
*
*	FUNCTION
*		It runs the matcher. At the function exit the caller can examine the
*		public part of the FindFile structure to discover relevant informations
*		about the file found.
*
*	INPUTS
*		ff - a pointer to a valid FileFinder structure.
*
*	RESULT
*		success - TRUE if there is a matching file, FALSE if no more files or
*			DOS error. The latter cases can be discriminated examining IoErr()
*			return code - 0 (no errors) for no more files.
*
*	SEE ALSO
*		CreateFileFinderA(), DeleteFileFinder()
*
*************************************************************************/

BOOL ASM FindFiles(REG(a0) struct FileFinder *ff)
{
	LONG errcode;

	for (;;) {

		switch (ff->ff_State) {

			case FFSTATE_NEXT:
				(VOID)MatchNext(&ff->ff_AP);
				break;

			case FFSTATE_FIRST:
				NEWSTATE(ff,FFSTATE_NEXT);
				(VOID)MatchNext(&ff->ff_AP);

				if (!OPT_ALL(ff)) ff->ff_Flags &= ~FILEFF_DODIR;
				break;

			case FFSTATE_INIT:
			{
				UBYTE *fname;

				ClearStruct(&ff->ff_AP);
				ff->ff_AP.ap_BreakBits = ff->ff_BrkMsk;
				ff->ff_AP.ap_Strlen    = ff->ff_BufLen;

				if (fname = ff->ff_FList[ff->ff_FNum++]) {

					NEWSTATE(ff,FFSTATE_FIRST);
					(VOID)MatchFirst(fname,&ff->ff_AP);

					if (OPT_ALL(ff) || !CHECKFLG(ff,APF_ITSWILD))
						ff->ff_Flags |= FILEFF_DODIR;

					break;
				}

				NEWSTATE(ff,FFSTATE_DONE);
				continue;
			}

			case FFSTATE_DONE:
				SetIoErr(0);
				return FALSE;

			default:
				SetIoErr(-1);	/* unknown state */
				return FALSE;
		}

		switch (errcode = IoErr()) {

			case 0:
			{
				LONG msg;

				if (DIR_ENTRY_TYPE(ff) < 0) return TRUE;

				if (CHECKFLG(ff,APF_DIDDIR)) {

					CLRFLAGS(ff,APF_DIDDIR);
					msg = FFHOOK_LEAVEDIR;

				} else if (DO_DIR(ff)) {

					SETFLAGS(ff,APF_DODIR);
					msg = FFHOOK_ENTERDIR;

				} else msg = FFHOOK_IGNORED;

				if (ff->ff_DirHook) {
					errcode = CallHookPkt(ff->ff_DirHook,&ff->ff_AP,&msg);
					if (errcode) {
						SetIoErr(errcode);
						return FALSE;
					}
				}

				break;
			}
			case ERROR_NO_MORE_ENTRIES:
				NEWSTATE(ff,FFSTATE_INIT);
				MatchEnd(&ff->ff_AP);
				SetIoErr(0);
				break;

			default:
				return FALSE;

		}

	}	/* end forever loop */
}
