/*
 *	Notify -- Start programs when detect file creation.
 *
 *		The source are not very clean because there so many
 *		#ifdef everywhere.
 *
 *		There's two level of debug info.  The first, invoked when
 *		DEBUG is defined, prints out file that are matched and the
 *		option used.
 *
 *		The second level prints all file it can see, with the symbol
 *		DEBUGALL.
 *
 *		This file should only compile under SAS 5.10, it uses alot
 *		of compiler stuff.
 */


#include "arpbase.h"
#include "arp_proto.h"


#ifdef DEBUGALL		/* display ALL opens, closes and renames*/
#ifndef DEBUG
#define DEBUG
#endif
#endif


/*
 *	A little typedef for clearer declarations...
 */

typedef LONG (*__asm DosClose)(register __d1 LONG, register __a6 struct Library *);
typedef LONG (*__asm DosOpen)(register __d1 char *, register __d2 LONG, register __a6 struct Library *);
typedef LONG (*__asm DosRename)(register __d1 char *, register __d2 char *, register __a6 struct Library *);

struct Match
{
	struct Match *Next;
	struct Match *Prev;
	LONG File;
	char Name[256];
};


/*
 *	Ok, there are a lot of weird declarations, but it works !
 */

void	main		(UWORD argc, char **argv);
void	StartPrg	(char *);
UWORD	Match		(char *);
BPTR	GetFile		(char *);
void	AddArg		(char *Buff, char *arg);

__fptr __stdargs DosWedge (struct DosBase *, LONG Offset, WORD *BackUp, __fptr NewFunc);
void __stdargs UnWedge (struct DosBase *, LONG Offset, WORD *Backup);
LONG __asm __saveds NewClose (register __d1 LONG, register __a6 struct Library *);
LONG __asm __saveds NewOpen (register __d1 char *, register __d2 LONG, register __a6 struct Library *);
LONG __asm __saveds NewRename (register __d1 char *, register __d2 char *, register __a6 struct Library *);

#ifdef DEBUG
void WriteDB (char *);
#endif

/* The old function of DOS */

DosClose				CallClose;
DosOpen					CallOpen;
DosRename				CallRename;


/* Library I like to have around... */

extern struct DosBase	*DOSBase;
struct Library			*ArpBase;


/*	Template for the command */

char					*Template = "RENAME/s,NEW/s,APPEND/s,Pattern/a,Command/a,Arg/...,</k,>/k";
char					*Help = "Usage: Notify [RENAME] [NEW] [APPEND] wildcards cmd <infile >outfile [arguments] [FILENAME] [arguments]";


/* command and arguments when file match */

#define RENAME			0
#define NEW				1
#define APPEND			2
#define PATTERN			3
#define COMMAND			4
#define ARG				5
#define INFILE			6
#define OUTFILE			7

void					*Arg[8];
char 					Pattern[256];

#ifdef DEBUG
BPTR					out;
#endif

/* Match linked list */

struct Match			*Matches;
struct SignalSemaphore	Semaphore;


void _main(char *line)
{
	struct Match 	*m, *next;
	WORD			OBackup[10],
					CBackup[10],
					RBackup[10];
	__fptr			NewFuncPtr;

#ifdef DEBUG
	char			t[256];

	out = Output();
	if (out == NULL)
		exit (5);
#endif

	if ( ! (ArpBase = OpenLibrary ("arp.library", 33L)) )
		exit (10);


	/*
	 *	Initialize semaphore guarding our list of matched filenames and
	 *	the output file handle.
	 */

	InitSemaphore (&Semaphore);


	/*
	 *	Get command line arguments.
	 */

#ifdef DEBUG
	sprintf (t, "%s\n", line);
	WriteDB (t);
#endif

	if ( GADS (line, strlen (line), Help, (char **)Arg, Template) < 0 )
	{
		Puts (Arg[0]);
		CloseLibrary (ArpBase);
		exit (5);
	}

#ifdef DEBUG
	sprintf (t, "  RENAME = %lx\n  NEW = %lx\n  APPEND = %lx\n  Pattern = %s\n  Command = %s\n",
			Arg[RENAME], Arg[NEW], Arg[APPEND], Arg[PATTERN], Arg[COMMAND]);
	WriteDB (t);
#endif


	/*
	 *	Get the filename pattern ready for inspection...
	 */

	PreParse ( strlwr(Arg[PATTERN]), Pattern );


	/*
	 *	We replace the functions that the user wants.  We used a
	 *	function pointer because the compiler needs a modifiable
	 *	value for function call (C problem).
	 */

	if ( Arg[NEW] || Arg[APPEND] )
	{
		NewFuncPtr = (__fptr) NewClose;
		CallClose = (DosClose) DosWedge ( DOSBase, -36L, CBackup, NewFuncPtr );
		NewFuncPtr = (__fptr) NewOpen;
		CallOpen = (DosOpen) DosWedge ( DOSBase, -30L, OBackup, NewFuncPtr );
	}

	if ( Arg[RENAME] )
	{
		NewFuncPtr = (__fptr) NewRename;
		CallRename = (DosRename) DosWedge ( DOSBase, -78L, RBackup, NewFuncPtr );
	}


	/*
	 *	Wait for control-C
	 */

	Wait (SIGBREAKF_CTRL_C);


	/*
	 *	Remove ourselves safely.  Be sure nobody is using the semaphore.
	 */

	Forbid ();
	ObtainSemaphore (&Semaphore);

	if ( Arg[NEW] || Arg[APPEND] )
	{
		UnWedge (DOSBase, -30L, OBackup);
		UnWedge (DOSBase, -36L, CBackup);
	}

	if ( Arg[RENAME] )
	{
		UnWedge (DOSBase, -78L, RBackup);
	}

	Permit ();
	ReleaseSemaphore (&Semaphore);

	/*
	 *	Free all matches that have not been closed yet.
	 */

	for (m = Matches; m != NULL; m = next)
	{
		next = m->Next;
		FreeMem (m, sizeof (struct Match));
	}

	CloseLibrary (ArpBase);
}


/*
 *	The function that will be placed for DOS Close.
 */

LONG __asm __saveds NewClose (register __d1 LONG File, register __a6 struct Library *Dos)
{
	struct Match	*m;
	LONG			Result;

#ifdef DEBUG
	char			t[256];
#endif


	/*
	 *	Call real DOS Close...
	 */

	Result = CallClose (File, Dos);

#ifdef DEBUGALL
	sprintf (t, "\n[42m [33m%lx[31m closed. [0m\n", File);
	WriteDB (t);
#endif


	/*
	 *	Check if file was matched earlier.
	 */

	ObtainSemaphore (&Semaphore);
	for (m = Matches; m != NULL; m = m->Next)
	{
		if (File == m->File)
		{
			if (m->Prev)
				m->Prev->Next = m->Next;
			else
				Matches = m->Next;
			if (m->Next)
				m->Next->Prev = m->Prev;
			break;
		}
	}
	ReleaseSemaphore (&Semaphore);


	/*
	 *	If the file was matched, start the program the user specified.
	 */

	if (m)
	{
#ifdef DEBUG
		sprintf (t, "\n[42m [33m%s (%lx)[31m matched in Close(). [0m\n", m->Name, File);
		WriteDB (t);
#endif
		StartPrg (m->Name);
		FreeMem (m, sizeof (struct Match));
	}

	return Result;
}


/*
 *	The function that will be placed for DOS Open.
 */

LONG __asm __saveds NewOpen (register __d1 char *Name, register __d2 LONG Mode, register __a6 struct Library *Dos)
{
	struct Match	*m;
	LONG			File;

#ifdef DEBUG
	char			t[256];
#endif


	/*
	 *	Call real DOS Open...
	 */

	File = CallOpen (Name, Mode, Dos);

#ifdef DEBUGALL
	sprintf (t, "\n[42m [33m%s (%lx)[31m opened. [0m\n", Name, File);
	WriteDB (t);
#endif


	/*
	 *	If the mode matches and is enabled and the filename matches
	 *	then create a match structure.
	 */

	if ( ( (MODE_NEWFILE == Mode && Arg[NEW]) || (MODE_READWRITE == Mode && Arg[APPEND]) ) && Match (Name) )
	{
		m = AllocMem (sizeof (struct Match), MEMF_PUBLIC);
		if (m)
		{
			m->File = File;
			strcpy (m->Name, Name);
			m->Prev = NULL;
			ObtainSemaphore (&Semaphore);
			m->Next = Matches;
			Matches = m;
			ReleaseSemaphore (&Semaphore);
		}

#ifdef DEBUG
		sprintf (t, "\n[42m [33m%s (%lx)[31m matched in Open(). [0m\n", Name, File);
		WriteDB (t);
#endif
	}
	return File;
}


/*
 *	The function that will be placed for DOS Rename.
 */

LONG __asm __saveds NewRename (register __d1 char *Old, register __d2 char *New, register __a6 struct Library *Dos)
{
	LONG	Result;

#ifdef DEBUG
	char	t[256];
#endif


	/*
	 *	Call real DOS Rename...
	 */

	Result = CallRename (Old, New, Dos);

#ifdef DEBUGALL
	sprintf (t, "\n[42m [33m%s[31m renamed to [33m%s[31m. [0m\n", Old, New);
	WriteDB (t);
#endif


	/*
	 *	Check if file matches pattern.
	 */

	if (Match (New))
	{
#ifdef DEBUG
		sprintf (t, "\n[42m [33m%s[31m matched in Rename(). [0m\n", New);
		WriteDB (t);
#endif
		StartPrg (New);
	}

	return Result;
}


/*
 *	Check if a filename matches a pattern, not taking care of case.
 */

UWORD Match (char *Name)
{
	char n[256];

	strcpy (n, Name);
	return (UWORD)PatternMatch (Pattern, strlwr(n));
}


/*
 *	This start a program, inserting the proper arguments.
 */

void StartPrg (char *Name)
{
	char	**argv;
	char	arg[256] = { '\0' };
	struct ProcessControlBlock
			PCB = { 6072L, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

#ifdef DEBUG
	char	t[256];
#endif


	/*
	 *	Add the arguments and the filename together to form the
	 *	command line of the program to be started.
	 */

	if (Arg[ARG] != NULL)
		for (argv = Arg[ARG]; *argv != NULL; argv++)
			if (stricmp (*argv, "filename") == 0)
				AddArg (arg, Name);
			else
				AddArg (arg, *argv);

	/*
	 *	Set the input and output handles.
	 */

		PCB.pcb_Input = GetFile (Arg[INFILE]);
		PCB.pcb_Output = GetFile (Arg[OUTFILE]);


	/*
	 *	Start the program.
	 */

#ifdef DEBUG
	if (ASyncRun(Arg[COMMAND],arg,&PCB) < 0)
	{
		sprintf (t, "\n[42m [33m%s %s[31m impossible to start. [0m\n", Arg[COMMAND], arg);
		WriteDB (t);
	}
	else
	{
		sprintf (t, "\n[42m [33m%s %s[31m started. [0m\n", Arg[COMMAND], arg);
		WriteDB (t);
	}
#else
	ASyncRun(Arg[COMMAND],arg,&PCB);
#endif
}


/*
 *	Check if filename is valid and try to open the file.
 */

BPTR GetFile (char *Name)
{
	if (Name != NULL && *Name != '\0')
		return Open (Name, MODE_NEWFILE);
	else
		return NULL;
}


/*
 *	This is an elegant (somewhat) way to add an argument and quoting
 *	it if it contains a space.
 */

void AddArg (char *to, char *from)
{
	UWORD	HasSpace = 0;
	char	*downer,
			*begin = to + strlen (to);

	/*
	 *	if the destination contained something, add a space and put the
	 *	destination pointer at the next free byte.
	 */

	if (to != begin)
	{
		*begin++ = ' ';
		to = begin;
	}


	/*
	 *	While there's a destination byte to transfer, copy it to the
	 *	destination.
	 *
	 *	But before that, check if it's a space.  If it is scroll back
	 *	to the beginning of the text we just added, shifting bytes to
	 *	make place for an opening quote.  We do this only for the first
	 *	space of this argument, of course...
	 */
	 

	do
		if (*from == ' ' && !HasSpace)
			for (HasSpace = 1, downer = to++; downer != begin; *downer = *(downer-- -1));
	while ( (*to++ = *from++) != '\0' );


	/*
	 *	If the source text had space, put it under quote.
	 */

	if (HasSpace)
	{
		*begin = '"';
		*(to-1) = '"';
		*to = '\0';
	}
}


#ifdef DEBUG
void WriteDB (char *t)
{
	ObtainSemaphore (&Semaphore);
	Write (out, t, strlen (t));
	ReleaseSemaphore (&Semaphore);
}
#endif
