/*
** treefilter.c
**
** FileFinder Package Utilities: pattern driven tree replicator
**
** $Id: treefilter.c,v 1.0 94/08/09 17:06:35 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(trf)			((trf)->trf_Options & TREEFF_ALL)
#define OPT_PROT(trf)		((trf)->trf_Options & TREEFF_CLONEPROT)
#define OPT_COMM(trf)		((trf)->trf_Options & TREEFF_CLONECOMM)
#define OPT_DATE(trf)		((trf)->trf_Options & TREEFF_CLONEDATE)
#define OPT_FILES(trf)		((trf)->trf_Options & TREEFF_OPENFILES)

#define DO_DIR(trf)			((trf)->trf_Flags & TREEFF_DODIR)
#define IS_MULTI_FILE(trf) ((trf)->trf_Flags & TREEFF_MULTIPLEF)
#define TARGET_IS_DIR(trf) ((trf)->trf_Flags & TREEFF_TARGETDIR)
#define INIT_DEST(trf)		((trf)->trf_Flags & TREEFF_TARGETED)

#define NEWSTATE(trf,s)		((trf)->trf_State = (s))

#define DIR_ENTRY_TYPE(trf)	((trf)->trf_AP.ap_Info.fib_DirEntryType)
#define DIR_ENTRY_LOCK(trf)	((trf)->trf_AP.ap_Current->an_Lock)
#define DIR_ENTRY_NAME(trf)	((trf)->trf_AP.ap_Info.fib_FileName)

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

#define CHECKFLG(trf,mask)	((trf)->trf_AP.ap_Flags & (mask))

/* --- Global data (read only) ---------------------------------------- */

static struct TagItem trf_boolmap[] = {
	TREEFT_All,				TREEFF_ALL,
	TREEFT_CopyProts,		TREEFF_CLONEPROT,
	TREEFT_CopyComment,	TREEFF_CLONECOMM,
	TREEFT_CopyDate,		TREEFF_CLONEDATE,
	TREEFT_OpenFiles,		TREEFF_OPENFILES,
	TAG_END
};

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

/*
 * close_stfiles -- (safely) closes the source and target files
 *
 * INTERNAL
 */

static LONG __regargs close_stfiles(
	register struct TreeFilter *trf)
{
	register LONG errcode;

	errcode = 0;

	/* Close Old Source File */

	if (trf->trf_Source) {
		if (!Close(trf->trf_Source)) errcode = IoErr();
		trf->trf_Source = NULL;
	}

	/* Close Old Target File */

	if (trf->trf_Target) {
		if (!Close(trf->trf_Target)) errcode = IoErr();
		trf->trf_Target = NULL;
	}

	return errcode;
}

/*
 * open_stfiles -- (safely) opens the source and target files
 *
 * INTERNAL
 */

static LONG __regargs open_stfiles(
	register struct TreeFilter *trf)
{
	register LONG errcode;
	register UBYTE *fname;
	BPTR olddir;
	BOOL did_cd;

	errcode = 0;

	fname = DIR_ENTRY_NAME(trf);

	/*
	 * Open New Source File.
	 */

	olddir = CurrentDir(DIR_ENTRY_LOCK(trf));

	if (!(trf->trf_Source = Open(fname,MODE_OLDFILE)))
		errcode = IoErr();

	(VOID)CurrentDir(olddir);

	/*
	 * Open New Target File.
	 */

	if (!errcode) {

		if (!TARGET_IS_DIR(trf)) fname = trf->trf_FTarget;

		if (trf->trf_DestLock) {
			olddir = CurrentDir(trf->trf_DestLock);
			did_cd = TRUE;
		} else
			did_cd = FALSE;

		if (trf->trf_Target = Open(fname,MODE_NEWFILE)) {

			/*
			 * Call the user DisplayHook (if available).
			 */

			if (trf->trf_DisplayHook) {
				LONG msg;

				msg = TRFHOOK_OPENNEWFILE;
				errcode = CallHookPkt(
					trf->trf_DisplayHook,&trf->trf_AP.ap_Info,&msg);
			}

		} else errcode = IoErr();

		if (did_cd) (VOID)CurrentDir(olddir);
	}

	return errcode;
}

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

/*
 * preprocess_stfiles -- preprocessor for source and target files
 *
 * INTERNAL
 */

static LONG __regargs preprocess_stfiles(
	register struct TreeFilter *trf)
{
	LONG errcode;
	BOOL target_file_exists;

	if (trf->trf_Buf)	/* remove file node from target pathname */
		*PathPart(trf->trf_Buf) = '\0';

	target_file_exists = (trf->trf_Target ? TRUE : FALSE);

	errcode = close_stfiles(trf);

	if (target_file_exists) {
		register UBYTE *fname;
		BPTR olddir;
		BOOL did_cd;

		fname = (
			TARGET_IS_DIR(trf)
			? (UBYTE *)DIR_ENTRY_NAME(trf)
			: trf->trf_FTarget
		);

		if (trf->trf_DestLock) {
			olddir = CurrentDir(trf->trf_DestLock);
			did_cd = TRUE;
		} else
			did_cd = FALSE;

		if (OPT_PROT(trf))
			(VOID)SetProtection(fname,trf->trf_AP.ap_Info.fib_Protection);

		if (OPT_COMM(trf))
			(VOID)SetComment(fname,trf->trf_AP.ap_Info.fib_Comment);

		if (OPT_DATE(trf))
			(VOID)SetFileDate(fname,&trf->trf_AP.ap_Info.fib_Date);

		if (did_cd) (VOID)CurrentDir(olddir);
	}

	return errcode;
}

/*
 * postprocess_stfiles -- postprocessor for source and target files
 *
 * INTERNAL
 */

static LONG __regargs postprocess_stfiles(
	register struct TreeFilter *trf)
{
	if (trf->trf_Buf) {	/* append the filename node */
		register UBYTE *fname;

		fname = (
			TARGET_IS_DIR(trf)
			? (UBYTE *)DIR_ENTRY_NAME(trf)
			: trf->trf_FTarget
		);

		(VOID)AddPart(trf->trf_Buf,fname,trf->trf_BufLen);
	}

	if (OPT_FILES(trf))
		return open_stfiles(trf);

	return 0;	
}

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

/*
 * get_parent_dir -- sets target dir to parent of the current one
 *
 * INTERNAL
 */

static LONG __regargs get_parent_dir(
	register struct TreeFilter *trf)
{
	register LONG errcode;
	BPTR lk;

	errcode = 0;

	if (trf->trf_DestLock) {

		if (lk = ParentDir(trf->trf_DestLock)) {

			UnLock(trf->trf_DestLock);
			trf->trf_DestLock = lk;

			if (trf->trf_Buf)	/* remove one node from target pathname */
				*PathPart(trf->trf_Buf) = '\0';

			if (trf->trf_DisplayHook) {
				LONG msg;

				msg = TRFHOOK_LEAVEDIR;
				errcode = (
					Examine(lk,&trf->trf_FIB)
					? CallHookPkt(trf->trf_DisplayHook,&trf->trf_FIB,&msg)
					: IoErr()
				);
			}

		} else errcode = IoErr();

	} else errcode = ERROR_OBJECT_NOT_FOUND;

	return errcode;
}

/*
 * set_target_dir -- (initializes and) sets the target directory
 *
 * INTERNAL
 */

static LONG __regargs set_target_dir(
	register struct TreeFilter *trf,
	UBYTE *name)
{
	register LONG errcode;
	LONG msg;
	BPTR lk;

	BPTR olddir;
	BOOL did_cd;

	/*
	 * CD to current destination (if available).
	 */

	if (lk = trf->trf_DestLock) {
		olddir = CurrentDir(lk);
		did_cd = TRUE;
	} else
		did_cd = FALSE;

	/*
	 * Try to Lock() target or,
	 * if not found, create it.
	 */

	if (lk = Lock(name,ACCESS_READ)) {
		msg = TRFHOOK_ENTEROLDDIR;
		errcode = 0;
	} else {
		errcode = IoErr();

		if (errcode == ERROR_OBJECT_NOT_FOUND)
			if (IS_MULTI_FILE(trf))
				if (lk = CreateDir(name)) {
					msg = TRFHOOK_ENTERNEWDIR;
					errcode = 0;
				} else
					errcode = IoErr();
			else
				errcode = 0;
	}

	/*
	 * The SameLock() test is necessary to get rid of cases where
	 * the MatchFirst()/MatchNext() functions return in the AnchorPath
	 * FIB a NULL filename (they do it for directories with trailing
	 * slash or colon).
	 */

	if (lk
	&& (SameLock(lk,trf->trf_DestLock) != LOCK_SAME)) {

		/*
		 * Now, check that it's a directory,
		 * (a file will be accepted iff not in multi file mode).
		 */

		if (Examine(lk,&trf->trf_FIB)) {
			if (trf->trf_FIB.fib_DirEntryType > 0) {

				trf->trf_Flags |= TREEFF_TARGETDIR;

				trf->trf_DestLock = lk;
				lk = NULL;	/* lock is used */

				if (trf->trf_Buf)	/* build the target name */
					(VOID)AddPart(trf->trf_Buf,name,trf->trf_BufLen);

			} else {

				if (IS_MULTI_FILE(trf))
					errcode = ERROR_OBJECT_WRONG_TYPE;

			}
		} else errcode = IoErr();

		/*
		 * If it's a directory, invoke the
		 * user DisplayHook (if available).
		 */

		if (!errcode && TARGET_IS_DIR(trf) && trf->trf_DisplayHook)
			errcode = CallHookPkt(trf->trf_DisplayHook,&trf->trf_FIB,&msg);
	}

	/*
	 * Restore current directory and
	 * free unused locks (if any).
	 */

	if (lk) UnLock(lk);
	if (did_cd) {
		lk = CurrentDir(olddir);

		/*
		 * If destination lock has changed
		 * get rid of old one.
		 */

		if (trf->trf_DestLock != lk) UnLock(lk);
	}

	return errcode;
}

/*
 * init_destination -- [obvious]
 *
 * INTERNAL
 */

static LONG __regargs init_destination(
	register struct TreeFilter *trf)
{
	LONG argc,*argv;

	trf->trf_Flags |= TREEFF_TARGETED;	/* prevent other initializations */

	/*
	 * Check for multiple file mode
	 */

	argc = 0;
	argv = (LONG *)trf->trf_FList;
	while (*argv++) ++argc;

	if (argc > 1									/* IF (multiple arguments) */
	||  CHECKFLG(trf,APF_ITSWILD)				/* OR (single argument AND wildcards) */
	||  DIR_ENTRY_TYPE(trf) > 0)				/* OR (a complete directory) */
		trf->trf_Flags |= TREEFF_MULTIPLEF;	/* THEN (multiple file mode) */

	return set_target_dir(trf,trf->trf_FTarget);
}

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

/*
 * ride_tree -- runs the matcher and clones (if required) the source tree
 *
 * INTERNAL
 */

static LONG __regargs ride_tree(
	register struct TreeFilter *trf)
{
	LONG errcode;

	for (;;) {

		switch (trf->trf_State) {

			case FFSTATE_NEXT:
				(VOID)MatchNext(&trf->trf_AP);
				break;

			case FFSTATE_FIRST:
				NEWSTATE(trf,FFSTATE_NEXT);
				(VOID)MatchNext(&trf->trf_AP);

				if (!OPT_ALL(trf)) trf->trf_Flags &= ~TREEFF_DODIR;
				break;

			case FFSTATE_INIT:
			{
				UBYTE *fname;

				ClearStruct(&trf->trf_AP);
				trf->trf_AP.ap_BreakBits = trf->trf_BrkMsk;
				trf->trf_AP.ap_Strlen    = trf->trf_BufLen;

				if (fname = trf->trf_FList[trf->trf_FNum++]) {

					NEWSTATE(trf,FFSTATE_FIRST);
					(VOID)MatchFirst(fname,&trf->trf_AP);

					if (!INIT_DEST(trf)) {
						LONG olderr;

						olderr = IoErr();

						if (errcode = init_destination(trf))
							return errcode;

						SetIoErr(olderr);
					}

					if (OPT_ALL(trf) || !CHECKFLG(trf,APF_ITSWILD))
						trf->trf_Flags |= TREEFF_DODIR;

					break;
				}

				NEWSTATE(trf,FFSTATE_DONE);
				continue;
			}

			case FFSTATE_DONE:
				return 0;

			default:
				return -1;	/* unknown state */
		}

		switch (errcode = IoErr()) {

			case 0:
				if (DIR_ENTRY_TYPE(trf) < 0) return 0;
				if (CHECKFLG(trf,APF_DIDDIR)) {

					CLRFLAGS(trf,APF_DIDDIR);
					if (errcode = get_parent_dir(trf))
						return errcode;

				} else if (DO_DIR(trf)) {

					SETFLAGS(trf,APF_DODIR);
					if (errcode = set_target_dir(trf,DIR_ENTRY_NAME(trf)))
						return errcode;

				}
				break;

			case ERROR_NO_MORE_ENTRIES:
				NEWSTATE(trf,FFSTATE_INIT);
				MatchEnd(&trf->trf_AP);
				break;

			default:
				return errcode;

		}

	}	/* end forever loop */
}

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

/****** filefinder.lib/FilterTree ****************************************
*
*	NAME
*		FilterTree -- Open source and destination files for the filter.
*
*	SYNOPSIS
*		success = FilterTree(trf);
*		d0                   a0
*
*		BOOL FilterTree(struct TreeFilter *);
*
*	FUNCTION
*		It runs the matcher. At the function exit the caller can examine the
*		public part of the TreeFilter structure to use the source and target
*		file handles.
*
*	INPUTS
*		trf - a pointer to a valid TreeFilter structure.
*
*	RESULT
*		success - TRUE if there are files, 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.
*
*	BUGS
*		Maybe...
*
*	SEE ALSO
*		CreateTreeFilterA(), DeleteTreeFilter()
*
*************************************************************************/

BOOL ASM FilterTree(REG(a0) struct TreeFilter *trf)
{
	BOOL rc;
	register LONG errcode;

	rc = FALSE;
	errcode = preprocess_stfiles(trf);

	if (!errcode)
		errcode = ride_tree(trf);

	if (trf->trf_State != FFSTATE_DONE) {

		if (!errcode)
			errcode = postprocess_stfiles(trf);

		if (!errcode) rc = TRUE;

	}

	SetIoErr(errcode);
	return rc;
}

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

/****** filefinder.lib/CreateTreeFilterA *********************************
*
*	NAME
*		CreateTreeFilterA -- TreeFilter Constructor.
*		CreateTreeFilter  -- varargs stub for CreateTreeFilterA().
*
*	SYNOPSIS
*		tfilter = CreateTreeFilterA(taglist);
*		d0                          a0
*
*		struct TreeFilter *CreateTreeFilterA(struct TagItem *);
*
*		tfilter = CreateTreeFilter(tag1, ... );
*		d0
*
*		struct TreeFilter *CreateTreeFilter(Tag, ... );
*
*	FUNCTION
*		It creates and initializes an instance of the TreeFilter structure,
*		used to examine and process a list of files. The public part of the
*		structure contains two BCPL pointers to the source and target
*		(destination) file handles. The required directories are automatically
*		created. Support is provided for an array of pattern string, user
*		option ALL/S, callback Hook, and break signals.
*
*	INPUTS
*		taglist - a pointer to a standard tag list. The following tags are
*			supported:
*
*			TREEFT_From - (UBYTE *) a pointer to a null-terminated string. The
*				pattern to use to find files.
*			TREEFT_Fromv - (UBYTE **) like TREEFT_From, but this points to an
*				array of pointers to null-terminated strings. The array must be
*				terminated by a NULL entry.
*			TREEFT_To - (UBYTE *) a pointer to a null-terminated string. The
*				destination file or directory.
*			TREEFT_All - (BOOL) enables the user option ALL/S. It forces the
*				matcher to accept all files contained in subdirs of matching
*				directories.
*			TREEFT_MaxNameLen - (LONG) sets the pathname buffer length. Default
*				length is 0 (no pathnames).
*			TREEFT_BreakBits - (LONG) contains the mask of the signals where to
*				break on. It defaults to SIGBREAKF_CTRL_C.
*			TREEFT_CopyProts - (BOOL) TRUE to copy the source protection bits
*				to the target. Default is to get default protection bits.
*			TREEFT_CopyComment - (BOOL) TRUE to copy the source comment to the
*				target. Default is to leave the target comment blank.
*			TREEFT_CopyDate - (BOOL) TRUE to copy the source time stamp to the
*				target. Default is to leave the target time stamp untouched.
*			TREEFT_OpenFiles	- (BOOL) TRUE to automatically open the source
*				and target files. Default is FALSE (no files).
*			TREEFT_DisplayHook - (struct Hook *) a pointer to an Hook called on
*				every directory and file. The object parameter points to the
*				FileInfoBlock structure for this disk object. The message
*				parameter points to a LONG that can be one of:
*				0L							- no action scheduled,
*				TRFHOOK_ENTERNEWDIR	- entering a new directory,
*				TRFHOOK_ENTEROLDDIR	- entering an old directory,
*				TRFHOOK_OPENNEWFILE	- opening a new file,
*				TRFHOOK_LEAVEDIR		- leaving a directory.
*				The Hook may return 0 to continue, or a valid DOS error code to
*				abort the scan.
*
*	RESULT
*		tfilter - a pointer to an instance of TreeFilter 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
*		DeleteTreeFilter()
*
*************************************************************************/

struct TreeFilter * ASM CreateTreeFilterA(REG(a0) struct TagItem *tl)
{
	register struct TreeFilter *trf;
	LONG buflen;

	buflen = GetTagData(TREEFT_MaxNameLen,0,tl);

	if (trf = AllocVec(sizeof(struct TreeFilter) + buflen * 2,STDMEM)) {

		/*
		 * set source/target parameters
		 */

		if (trf->trf_SABuf[0] = GetTagData(TREEFT_From,NULL,tl))
			trf->trf_FList = (UBYTE **)trf->trf_SABuf;
		else
			trf->trf_FList = (UBYTE **)GetTagData(TREEFT_Fromv,NULL,tl);

		trf->trf_FTarget = (UBYTE *)GetTagData(TREEFT_To,NULL,tl);

		/*
		 * set options
		 */

		trf->trf_DisplayHook =
			(struct Hook *)GetTagData(TREEFT_DisplayHook,NULL,tl);

		trf->trf_Options = (UBYTE)PackBoolTags(0,tl,trf_boolmap);

		trf->trf_BrkMsk = GetTagData(TREEFT_BreakBits,SIGBREAKF_CTRL_C,tl);

		if (buflen) {
			trf->trf_BufLen = buflen;
			trf->trf_Buf = ((UBYTE *)trf) + sizeof(struct TreeFilter) + buflen;
			trf->trf_Buf[0] = '\0';
		}
	}

	return trf;
}

struct TreeFilter * __stdargs CreateTreeFilter(Tag tag1, ... )
{
	return CreateTreeFilterA((struct TagItem *)&tag1);
}

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

/****** filefinder.lib/DeleteTreeFilter **********************************
*
*	NAME
*		DeleteTreeFilter -- TreeFilter Distructor.
*
*	SYNOPSIS
*		DeleteTreeFilter(trf);
*		                 a0
*
*		VOID DeleteTreeFilter(struct TreeFilter *);
*
*	FUNCTION
*		It frees all the resources allocated by CreateTreeFilterA().
*
*	INPUTS
*		trf - a pointer to a valid TreeFilter structure.
*
*	SEE ALSO
*		CreateTreeFilter()
*
*************************************************************************/

VOID ASM DeleteTreeFilter(REG(a0) struct TreeFilter *trf)
{
	if (trf) {
		(VOID)preprocess_stfiles(trf);

		if (trf->trf_DestLock) UnLock(trf->trf_DestLock);

		switch (trf->trf_State) {
			case FFSTATE_NEXT:
			case FFSTATE_FIRST:
				MatchEnd(&trf->trf_AP);
		}

		FreeVec(trf);
	}
}
