
// ########################################################################
/*
	FSearch v2.0									(Set TAB to 3)

	Descrip:	A fast pure CLI command for listing/searching files
	Author:	D. Keletsekis - dck@hol.gr - http://users.hol.gr/~dck

	This is a CLI command with a lot of options which greatly reduce
	the scope of the search. If a search string is given (with the TXT 
	option), it will search the files, otherwise it will just work 
	like a list command.

	FSearch will use the Boyer-Moore searching algorithm (which is very 
	fast), unless the LINE switch is used (meaning that you want the
	line numbers of the text matches displayed) wherein a sequential
	search will be used so we can count the linefeeds. This is slower	
	(although still very fast).
														Athens, October 1999
*/
// ########################################################################

#define __USE_SYSBASE

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <dos/rdargs.h>
// #include <utility/utility.h>

#include <string.h>
#include <ctype.h>

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

#define THISPROC	 ((struct Process *)(SysBase->ThisTask))
#define Result2(x) THISPROC->pr_Result2 = x

__inline int max(int a, int b) { return a > b ? a : b; }

static const char VERSION[] = "\0$VER: FSearch 2.0";

// Altered AnchorPath structure with enlarged ap_Buf for storing
// the full path & file name - set ap_Strlen to indicate it.
struct MyAnchorPath 
{	struct AChain *ap_Base, *ap_Last;
	LONG	 ap_BreakBits, ap_FoundBreak;
	BYTE	 ap_Flags, ap_Reserved;
	WORD	 ap_Strlen;                // buffer length
	struct FileInfoBlock ap_Info;
	UBYTE  ap_Buf[150];					// Buffer for path name 
};

// my protos
#include "fsearch_protos.h"

// ====================================================================
// The program's options
// ====================================================================

#define TEMPLATE	"ROOT/A/M,PAT/K,TXT/K,FROM/K,TO/K,MIN/N/K,MAX/N/K,HEADER/K,ALL/S,CS=CASESENSITIVE/S,V=VERBOSE/S,NOBIN/S,HL=HIGHLIGHT/S,NOPATH/S,INFO/S,LINE/S"
#define OPT_ROOT	 0    //A/M,  multiple, wildcards - dir(s) to search
#define OPT_PAT	 1    //K,	  file pattern to search - ex - PAT=#?.h
#define OPT_TXT	 2    //K,	  text to search for - ex - TXT="some string"
#define OPT_FROM	 3    //K,	  search files over this date - FROM=25/3/96
#define OPT_TO     4    //K,	  search files up to this date
#define OPT_MIN	 5    //N/K,  search files of this minimum size (in Kb)
#define OPT_MAX	 6    //N/K,  search files of maximum this size
#define OPT_HEADER 7    //K,	  search files starting with this header
#define OPT_ALL	 8    //S,	  recurse sub-directories
#define OPT_CS     9    //=CASESENSITIVE/S,		search case sensitive
#define OPT_V		 10	//=VERBOSE/S,				print text found
#define OPT_NOBIN  11	//S,	  do not search binary files
#define OPT_HL     12	//S,	  Highlight filename & linenumbers
#define OPT_NOPATH 13	//S,	  do not print the file's full path
#define OPT_INFO	 14	//S,	  print size/date etc next to file name
#define OPT_LINE	 15	//S,    Print line nums, do not use Boyer-Moore

#define OPT_COUNT  16	// the number of options

// ------- escape sequences for changing color of text output

#define SET_WHITE  PutStr("›32m")
#define SET_BLUE	 PutStr("›33m")
#define SET_BLACK  PutStr("›31m")
#define MAXLINELENGTH 60

// ====================================================================
// ------------------------ Structure definition ----------------------
// ====================================================================
// -------- The main hold-everything structure

#define MAXPAT 256		// maximum search string length

struct SearchInfo
{
	UBYTE *buff;			// buffer to search
	LONG	bufflen;			// characters in buffer

	UBYTE *str;				// search string - pointer to TXT argument
	UBYTE upstr[256];		// upper case buffer for str (same if CS)
	LONG	len;        	// the length of the search string
	LONG	strline;			// number of \n in search string

	LONG  bytesread;		// bytes read in previous buffers (for finding cur byte)
	UBYTE *curpos;			// current position in buffer (if match is found)
	LONG	line;				// current line number

	LONG	*opts;			// command line options
	UBYTE *filename;		// the name of the file we're searching
	struct FileInfoBlock *fib;	// for prepareinfo()
	BOOL	printfile;		// 1 = file name already printed
	UBYTE info[256];		// file information buffer (size, date & comment)

	BOOL  useBM;			// 1= use Boyer-Moore
	LONG	skip[256];		// look-up arrays for BM search..
	LONG	shift[MAXPAT]; // ..255 chars is the maximum length of str
};

#include "bm.h"			// this contains the BM functions

// ====================================================================
// ------------------------- Program start ----------------------------
// ====================================================================

LONG main(void)
{
	long temprc=0, rc, hdlength;
	long buffsize = 32760;			// 32k read buffer
	LONG actual, last;
	LONG opts[OPT_COUNT];
	struct RDArgs *rdargs;
	struct MyAnchorPath __aligned ua;
	struct FileInfoBlock *fib;
	BPTR oldlock, curlock, lk, fh=NULL;
	UBYTE *fname=NULL;
	UBYTE head[120];			 	// buffer to store header
	UBYTE patbuff[512];		 	// buffer for pattern matching
	UBYTE hdbuff[512];		 	// buffer for header matching
	UBYTE *p, *u;
	UBYTE *curarg, **argptr, *buff=NULL;
	UBYTE *pat, *hd;
	struct DateTime __aligned dt;  // for FORM/TO
	LONG	 c;
	BOOL	 flag, havepat=0, nomoredirs;
	struct SearchInfo *si=NULL;		// search info structure

	SysBase = (*((struct ExecBase **) 4));

	// the default return code
	rc = RETURN_FAIL;

	// clear the locks we'll use so we know if we used them
	oldlock = NULL;
	curlock = NULL;

	// setup the DateTime structure flag for dd-mm-yy
	memset(&dt, 0, sizeof(struct DateTime));
	dt.dat_Format = FORMAT_CDN;

	// ------------ clear options array & parse command line

	memset((UBYTE *)opts, 0, sizeof(opts));
	rdargs = ReadArgs(TEMPLATE, opts, NULL);

	// if there was a parser error - print slogan etc and skip to end
	if (rdargs == NULL)
	{	PutStr ("›32mby D.Keletsekis\n›33mdck@hol.gr›31m\n");
		PrintFault(IoErr(),	NULL);
		goto	endprog;
	}

	// -- get SearchInfo structure

	si = (struct SearchInfo *)AllocVec(sizeof(struct SearchInfo), MEMF_ANY|MEMF_CLEAR|MEMF_PUBLIC);
	if (!si)
	{	PutStr	("No memory!\n");
		goto endprog;
	}
	si->opts	= opts;

	// ------------ ok, now set up the CLI arguments

	// point to first (of possibly multiple) source args,
	// which are set up as an array of pointers to chars
	argptr = (UBYTE **)opts[OPT_ROOT];

	// if LINE switch is used and VERBOSE is on do not use BM
	if (opts[OPT_LINE] && opts[OPT_V])
			si->useBM = 0;
	else	si->useBM = 1;

	// setup ParsePattern for PAT and HEADER
	if (pat = (UBYTE *)opts[OPT_PAT])
	{	havepat = ParsePatternNoCase (pat, patbuff, 500);
		if (havepat < 0)	// if 0, use stricmp()
		{	PutStr ("Error parsing file pattern\n");
			goto endprog;
		}
	}

	if (hd = (UBYTE *)opts[OPT_HEADER])
	{	if ((ParsePatternNoCase (hd,	hdbuff, 500)) < 1)
		{	PutStr ("Error parsing header\n");
			goto endprog;
		}
	}

	// allow / and . as date separators
	if (opts[OPT_FROM])
	{	for (p = (UBYTE *)opts[OPT_FROM]; *p;	++p)
		{	if ((*p=='/')||(*p=='.')) 
					*p =	'-'; 
	}	}
	if (opts[OPT_TO])
	{	for (p = (UBYTE *)opts[OPT_TO];	*p; ++p)
		{	if ((*p=='/')||(*p=='.')) 
					*p =	'-'; 
	}	}

	// setup reader buffer for searching
	if (opts[OPT_TXT])
	{
		// get buffer	& searchinfo struct
		buff = (UBYTE *)AllocVec(buffsize+16, MEMF_ANY | MEMF_PUBLIC);
		if (!buff)
		{	PutStr ("No memory!\n");
			goto endprog;
		}

		si->buff = buff;
		si->str	= (UBYTE *)opts[OPT_TXT];
		stccpy (si->upstr, si->str, 254);
		si->len 	= strlen(si->str);

		// count new lines in search	string (if any)
		for (p = si->str, si->strline = 0; *p; ++p)
			if (*p == '\n') ++si->strline;

		// if not case sensitive make str/upstr oposite case
		if (opts[OPT_CS] == NULL)
		{	u = si->str;
			while (*u) { *u = ToLower(*u); ++u; }
			u = si->upstr;
			while (*u) { *u = ToUpper(*u); ++u; }
		}

		// setup BM..
		if (si->useBM)	init_bmsearch(si);
	}

	// -------------- LOOP FOR ALL "ROOT" ARGUMENTS ----------

	while (curarg = *argptr++)
	{
		// clear the anchorpath structure to 0's
		memset(&ua, 0, sizeof(struct MyAnchorPath));
		// indicate - put full path in ap_Buf which is 150 bytes
		ua.ap_Strlen = 150;
		// set up what to print in case of error
		strcpy(ua.ap_Buf, "FSearch failed :(\n");
		// break on control-C/D
		ua.ap_BreakBits = SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D;

		// call the matcher.. (0=success)
		temprc = MatchFirst(curarg, (struct AnchorPath *)&ua);

		nomoredirs = 0; // reset flag

		// ---------------- LOOP FOR ALL MATCHES --------------------

		while (temprc == 0)
		{

			// ------ if it's a directory...
			if (ua.ap_Info.fib_DirEntryType > 0)
			{	
				if (opts[OPT_ALL])	// if the ALL option is on..
				{	// if the backing-out-of-dir flag is not set...
					if (!(ua.ap_Flags & APF_DIDDIR))
					{	// tell matcher to enter this sub-dir.
						ua.ap_Flags |= APF_DODIR;
					}
					// always clear the flag
					ua.ap_Flags &= ~APF_DIDDIR;
				}

				else if (!nomoredirs) // if 1st dir, we must enter it..
				{	if (!(ua.ap_Flags & APF_DIDDIR))
					{	ua.ap_Flags |= APF_DODIR;	// enter it..
						nomoredirs = 1; // set flag for no more levels
					}
					ua.ap_Flags &= ~APF_DIDDIR;
				}
			}

			// ------ if dir changed - cd to it
			if (ua.ap_Flags & APF_DirChanged)
			{
				if (curlock) UnLock (curlock);
				curlock = DupLock(ua.ap_Current->an_Lock);
				lk = CurrentDir(curlock);
				// if this	is the first dir change, store the lock
				if (!oldlock) oldlock = lk;
			}

			// -------------- IF IT'S A FILE ------- do your stuff...

			if (ua.ap_Info.fib_DirEntryType < 0)
			{
				// point to our FileInfoBlock &	clear flags
				si->fib = fib = &ua.ap_Info;
				flag	= 0;
				// point to file	name
				if (opts[OPT_NOPATH]) fname = fib->fib_FileName;
				else fname = ua.ap_Buf;
				si->filename = fname;

				// --------- check file pattern
				if (opts[OPT_PAT])
				{	if (!havepat)		// if no wilds...
					{	if (stricmp(pat, fib->fib_FileName) != NULL)
							goto skipfile;
					}
					else					// have wilds..
					{	if(!(MatchPatternNoCase(patbuff, fib->fib_FileName)))
							goto skipfile;
				}	}

				// ----------	check file size (MIN/MAX)
				if (opts[OPT_MIN])
				{	if ((fib->fib_Size	/ 1024) < (*((LONG *)opts[OPT_MIN])))
						goto skipfile;
				}
				if (opts[OPT_MAX])
				{	if ((fib->fib_Size	/ 1024) > (*((LONG *)opts[OPT_MAX])))
						goto skipfile;
				}

				// --------- check dates
				if (opts[OPT_FROM])
				{	dt.dat_StrDate = (UBYTE *)opts[OPT_FROM];
					if (StrToDate(&dt))
					{	if ((CompareDates(&dt.dat_Stamp, &fib->fib_Date)) <= 0)
							goto	skipfile;
					}
					// error in date format..
					else
					{	PutStr ("FROM syntax error\n");
							goto enderror;
					}
				}
				if (opts[OPT_TO])
				{	dt.dat_StrDate = (UBYTE *)opts[OPT_TO];
					if (StrToDate(&dt))
					{	if ((CompareDates(&dt.dat_Stamp, &fib->fib_Date)) >= 0)
							goto	skipfile;
					}
					else
					{	PutStr ("TO syntax error\n"); 
							goto enderror;
					}
				}

				// ------------ HEADER	or NOBIN options
				if (opts[OPT_HEADER] || opts[OPT_NOBIN])
				{
					// open & read file
					hdlength = 0;
					if (fh = Open(fib->fib_FileName, MODE_OLDFILE))
					{	hdlength = Read(fh, head, 100); // up to 100 bytes..
						Close (fh);
					}
					if (hdlength == -1)
					{	temprc = IoErr();	goto enderror;
					}
					else if (!hdlength) goto skipfile;
					head[hdlength] = 0;

					if (opts[OPT_HEADER])
					{	// replace null bytes with full stops
						for (c=0; c<hdlength; ++c) if (!head[c]) head[c]='.';
						if(!(MatchPatternNoCase(hdbuff, head)))
							goto skipfile;
					}

					if (opts[OPT_NOBIN])
					{	for (p=head, c=0; c<hdlength; c++, p++)
						{	if ((!*p) ||
								((*p<32)&&((*p!='\n')&&(*p!='\r')&&(*p!='\t'))) ||
								((*p>=127)&&(*p<=159)))
								goto skipfile;
					}	}
				}

				// -------------	Search the file -----------------------

				if (opts[OPT_TXT])
				{
					// open the file
					if ((fh = Open(fib->fib_FileName, MODE_OLDFILE)) == NULL)
					{	temprc = IoErr();
						goto enderror;
					}

					// reset the line number counter & split line stuff  
					si->line    	= 1;
					si->printfile	= 0;
					si->buff    	= buff;
					si->bytesread	= 0;

					// start reading - flag will indicate if we're finished
					flag = 0;
					actual = Read (fh, buff, buffsize);
						
					while (!flag)
					{	
						if (actual == -1)                   // read error
						{	temprc = IoErr();
							Close (fh);
							goto enderror;
						}
	
						else if (actual == 0)					// empty file
						{	Close (fh);
							goto skipfile;
						}

						else if (actual < buffsize)			// finished reading
							flag = 1;

						si->bufflen	= actual;

						if (si->useBM)								// Search BM
						{	if ((bmsearch (si)) && (!OPT_V))
								++flag; // no verbose = just find 1st match
						}
						else											// Search sequentially
						{	if ((search (si)) && (!OPT_V))
								++flag; // no verbose = just find 1st match
						}

						if (!flag)									// read more..
						{	// the new buffer must contain the last strlen(str)
							// chars to account for str falling between buffers..
							last = si->len - 1;	// one less than strlen(str)
							strncpy (buff, &buff[actual-last], last);
							si->bytesread += actual - last;
							actual = Read (fh, &buff[last], buffsize - last);
							actual += last; // for correct buff length
						}

					}	// end of read loop

					Close (fh);

				}	// end of dealing with file for opts[OPT_TXT]

				// ------- if no txt option was declared, we list the file

				else printheader (si);

			}	// -------- end of IF IT'S A FILE

			// point at which we goto if file does not meet our requirement
			skipfile:

			// match the next file
			temprc = MatchNext((struct AnchorPath *)&ua);
		}

		// ------------------- END OF MATCH LOOP ---------------------

		// tell the matcher we've finished
		MatchEnd((struct AnchorPath *)&ua);
	}

	// ------------- END OF LOOP FOR ALL "ROOT" ARGS -------------

	// check if we had any errors
	enderror:

	if (temprc)
	{
		// if we ran out	of files, we set error code to OK - i.e. 0
		if (temprc == ERROR_NO_MORE_ENTRIES)
			rc = RETURN_OK;
		else
		{	
			// if the user hit control-C, set WARN return code (5)
			if (temprc == ERROR_BREAK)
				rc = RETURN_WARN;
			else
			{	PutStr ("FSearch Error");
				rc = RETURN_FAIL;
			}
			// there was a reportable error - so print it
			PrintFault(temprc, fname);
		}
	}

	// ------------------------- end - clean up --------------------

	endprog:

	// change back to our original dir..
	if (oldlock) CurrentDir(oldlock);
	// and unlock the current lock we have (if any)
	if (curlock) UnLock(curlock);

	// free memory
	if (rdargs) FreeArgs(rdargs);
	if (buff)	FreeVec(buff);
	if (si)		FreeVec(si);
	return(rc);
}

// =================================================================
// Sequential search
// - search si->buff -	return: matches found
// =================================================================
search (struct SearchInfo *si)
{
	UBYTE *p, *endbuff;
	UBYTE *t, *d, *u;
	int matches = 0;

	p = si->buff;
	endbuff = &si->buff[si->bufflen];

	while (p < endbuff)
	{
		// match first letter
		if ((*p == *si->str) || (*p == *si->upstr))
		{	t = p;
			d = si->str;
			u = si->upstr;

			// match rest of txt string
			while ((*d) && (*t) && ((*d == *t) || (*u == *t))) 
			{
				++t; ++d; ++u;

				// if we run out of buffer in mid-matching..
				if (t >= endbuff)
				{	// return.. new buffer sent will start from
					// str length before the next read block.
					return (matches);
				}
			}

			// ------ found match !
			if (*d == 0) 
			{	--t;
				p = t;
				++matches;

				// if not already printed - print file name
				if (!si->printfile)
				{	printheader (si);
					si->printfile	= 1;
				}

				// if verbose, print the line context
				if (si->opts[OPT_V])
				{	si->curpos = p;				// set match position
					printtext (si);
					si->line += si->strline;	// add any lines the string had in it
					p = &p[si->len - 1];    	// go to end of string	found (-1 since ++p)
				}

				// if no verbose we're finished with this file
				else return (matches);
			}

			else if ((!*p) || (*p == 10)) ++si->line;
		}

		else if ((!*p) || (*p == 10)) ++si->line;

		++p;
	}

	return (matches);
}

// =================================================================
// Print text found
//	- only called if the VERBOSE option in declared 
// - forces OUTPUT line to about 60 chars long
// - MUST set: si->curpos, si->line(if not BM), si->bytesread
// =================================================================

void printtext (struct SearchInfo *si)
{
	LONG  c;
	UBYTE *p, *start, numbuff[50], outbuff[80];

	// write line number (normal) or byte (BM)
	*numbuff = ' '; // start with a space
	if (si->useBM)
		stcl_d (&numbuff[1], si->bytesread + (LONG)(si->curpos - si->buff));
	else
		stcl_d (&numbuff[1], si->line);
	strcat (numbuff, ": ");

	if (si->opts[OPT_HL]) SET_BLUE;
	PutStr (numbuff);
	if (si->opts[OPT_HL]) SET_BLACK;

	// go back to line start or max 20 bytes
	for (	start = si->curpos, c = 0; 
			(c < 20) && (*start) && (start > si->buff) && 
			(*start != '\n') && (*start != '\r');
			start--, c++);

	++start; --c;	// skip \n

	// get matched string & rest of line (length = 60 chars)
	c += si->len;
	if (c < 60)
	{	for (	p = &start[c];
				(c < 60) && (*p) && (*p != 10) && (*p != 13);
				++p, ++c);
	}
	else c = 60;	// limit

	// copy to outbuff, replacing all tabs with spaces (neater)
	p = outbuff;
	while (c)
	{	*p = *start;
		if (*p == 9) *p = ' ';
		++p; ++start; --c;
	}

	*p = '\n'; p[1] = '\0';
	PutStr (outbuff);
}

// =================================================================
// Prepare the file info buffer
// =================================================================

void prepareinfo (struct SearchInfo *si)
{
	struct DateTime __aligned dd;
	UBYTE buff[30], buff2[30];
	LONG  c;

	memset (si->info, ' ', 50); // to right-align size
	memset ((UBYTE *)&dd, 0, sizeof(struct DateTime));

	c = stcl_d (buff, si->fib->fib_Size);
	strcpy (&si->info[12-c], buff); // right-align
	strcat (si->info, " ");

	dd.dat_Stamp	= si->fib->fib_Date;
	dd.dat_StrDate = buff;
	dd.dat_StrTime = buff2;
	DateToStr (&dd);
	strcat (si->info,	buff);
	strcat (si->info,	" ");
	strcat (si->info,	buff2);

	if (si->fib->fib_Comment)
	{	strcat (si->info, " ");
		strcat (si->info, si->fib->fib_Comment);
	}
}

// =================================================================
// Print file name & other info
// =================================================================

void printheader (struct SearchInfo *si)
{
	if (si->opts[OPT_HL]) SET_WHITE;
	PutStr (si->filename);

	if (si->opts[OPT_INFO])
	{	PutStr (" \t");
		prepareinfo (si);
		PutStr (si->info);
	}

	if (si->opts[OPT_HL]) SET_BLACK;
	PutStr ("\n");
}
