/*
**	termFileBuffer.c
**
**	Double-buffered file I/O routines
**
**	Copyright © 1990-1993 by Olaf `Olsen' Barthel & MXM
**		All Rights Reserved
*/

#include "termGlobal.h"

	/* Size of a file buffer. */

#define BUFFER_SIZE	32768

	/* Argument types. */

enum	{	ARG_NAME,ARG_MODE };
enum	{	ARG_OFFSET,ARG_ORIGIN };

	/* Seek offsets. */

enum	{	SEEK_SET,SEEK_CURR,SEEK_END };

	/* Command codes. */

enum	{	BUF_CLOSE,BUF_SEEK,BUF_FILL,BUF_FLUSH };

	/* FileBufferServer():
	 *
	 *	Background process to handle the buffering
	 *	of a filehandle, automatically gets invoked
	 *	when a file is opened.
	 */

STATIC VOID __saveds
FileBufferServer(VOID)
{
	struct MsgPort	*Port;
	struct Buffer	*Buffer;
	BYTE		 Terminated = FALSE,
			 Done,
			 WasFull;
	UBYTE		*String;
	APTR		 Data;
	LONG		 Length;
	BPTR		 SomeLock;

		/* Wait for startup message (-> Buffer). */

	Port = &((struct Process *)SysBase -> ThisTask) -> pr_MsgPort;

	WaitPort(Port);

	Buffer = (struct Buffer *)GetMsg(Port);

		/* Open the file and obtain a filehandle. */

	String = (STRPTR)Buffer -> ActionData[ARG_MODE];

	Buffer -> WriteAccess = TRUE;

		/* Put the message into the list. */

	ObtainSemaphore(&DoubleBufferSemaphore);

	AddTail(&DoubleBufferList,(struct Node *)Buffer);

	ReleaseSemaphore(&DoubleBufferSemaphore);

		/* Remember the opening date. */

	DateStamp(&Buffer -> OpenDate);

		/* Check for the open type. */

	switch(String[0])
	{
		case 'r':

			if(String[1] == '+')
				Buffer -> FileHandle = Open((STRPTR)Buffer -> ActionData[ARG_NAME],MODE_READWRITE);
			else
			{
				Buffer -> WriteAccess = FALSE;

				Buffer -> FileHandle = Open((STRPTR)Buffer -> ActionData[ARG_NAME],MODE_OLDFILE);
			}

			break;

		case 'w':

			if(String[1] == '+')
			{
				if(SomeLock = Lock((STRPTR)Buffer -> ActionData[ARG_NAME],ACCESS_WRITE))
				{
					UnLock(SomeLock);

					DeleteFile((STRPTR)Buffer -> ActionData[ARG_NAME]);
				}

				Buffer -> FileHandle = Open((STRPTR)Buffer -> ActionData[ARG_NAME],MODE_READWRITE);
			}
			else
				Buffer -> FileHandle = Open((STRPTR)Buffer -> ActionData[ARG_NAME],MODE_NEWFILE);

			break;

		case 'a':

			if(SomeLock = Lock((STRPTR)Buffer -> ActionData[ARG_NAME],ACCESS_WRITE))
			{
				UnLock(SomeLock);

				if(Buffer -> FileHandle = Open((STRPTR)Buffer -> ActionData[ARG_NAME],MODE_READWRITE))
				{
					if(Seek(Buffer -> FileHandle,0,OFFSET_END) == -1)
					{
						Close(Buffer -> FileHandle);

						Buffer -> FileHandle = NULL;
					}
				}
			}
			else
				Buffer -> FileHandle = Open((STRPTR)Buffer -> ActionData[ARG_NAME],MODE_NEWFILE);

			break;
	}

		/* Clear signal bit. */

	ClrSignal(SIG_COMMAND);

		/* Did the file open? */

	if(Buffer -> FileHandle)
	{
		Buffer -> Data		= Buffer -> DataBuffer[0];
		Buffer -> DataCount	= 1;
		Buffer -> Fresh		= TRUE;

			/* If not in write mode fill the buffers. */

		if(!Buffer -> WriteAccess)
		{
				/* Fill the first one synchronously. */

			Buffer -> ReadBufFull	= Read(Buffer -> FileHandle,Buffer -> Data,Buffer -> BufLength);
			Buffer -> Read		= TRUE;
			Buffer -> RealPosition	= Buffer -> ReadBufFull;

				/* Restart caller. */

			Signal(Buffer -> Caller,SIG_HANDSHAKE);

				/* Fill the second buffe asynchronously. */

			Buffer -> DataLength[1]	= Buffer -> Cached = Read(Buffer -> FileHandle,Buffer -> DataBuffer[1],Buffer -> BufLength);
			Buffer -> RealPosition += Buffer -> Cached;
		}
		else
		{
			UBYTE TempBuffer[MAX_FILENAME_LENGTH],*Stop;

			strcpy(TempBuffer,(STRPTR)Buffer -> ActionData[ARG_NAME]);

			Stop = PathPart(TempBuffer);

			*Stop = 0;

			if(Buffer -> DirLock = Lock(TempBuffer,ACCESS_READ))
			{
				if(!Info(Buffer -> DirLock,&Buffer -> InfoData))
				{
					UnLock(Buffer -> DirLock);

					Buffer -> DirLock = NULL;
				}
			}

			Signal(Buffer -> Caller,SIG_HANDSHAKE);
		}
	}
	else
		Terminated = TRUE;

		/* Go into loop waiting for commands. */

	while(!Terminated)
	{
		Wait(SIG_COMMAND);

		Done = FALSE;

		Buffer -> Result = 0;

			/* Take care of each action. */

		switch(Buffer -> Action)
		{
				/* Close the file, flush any dirty
				 * buffers and exit.
				 */

			case BUF_CLOSE:

				Buffer -> Result = TRUE;

				if(Buffer -> BufPosition && Buffer -> Written)
				{
					if(Write(Buffer -> FileHandle,Buffer -> Data,Buffer -> BufPosition) != Buffer -> BufPosition)
						Buffer -> Result = FALSE;
				}

				if(!Close(Buffer -> FileHandle))
					Buffer -> Result = FALSE;

				if(Buffer -> DirLock)
					UnLock(Buffer -> DirLock);

				Terminated = TRUE;

				break;

				/* Seek to a specific file position. */

			case BUF_SEEK:

				Buffer -> Result = 0;

					/* Do nothing if buffer is still
					 * untouched and we are required
					 * to seek back to the beginning
					 * of the file.
					 */

				if(Buffer -> Fresh && !Buffer -> ActionData[ARG_OFFSET] && Buffer -> ActionData[ARG_ORIGIN] == SEEK_SET)
				{
					Signal(Buffer -> Caller,SIG_HANDSHAKE);

					Done = TRUE;
				}
				else
				{
					Buffer -> WriteBufFull	= Buffer -> BufLength;
					Buffer -> Read		= FALSE;

					if(Buffer -> BufPosition && Buffer -> Written)
					{
						if(Write(Buffer -> FileHandle,Buffer -> Data,Buffer -> BufPosition) != Buffer -> BufPosition)
							Buffer -> Result = -1;
					}

					if(!Buffer -> Result)
					{
						Buffer -> Result = Buffer -> RealPosition - (Buffer -> ReadBufFull + Buffer -> Cached);

						switch(Buffer -> ActionData[ARG_ORIGIN])
						{
							case SEEK_SET:

								if(Seek(Buffer -> FileHandle,Buffer -> ActionData[ARG_OFFSET],OFFSET_BEGINNING) != -1)
									Buffer -> RealPosition = Buffer -> ActionData[ARG_OFFSET];
								else
									Buffer -> Result = -1;

								break;

							case SEEK_CURR:

								if(!Buffer -> WriteAccess && Buffer -> ActionData[ARG_OFFSET] >= 0 && Buffer -> ReadBufFull - Buffer -> ActionData[ARG_OFFSET] >= 0)
								{
									Buffer -> ReadBufFull	-= Buffer -> ActionData[ARG_OFFSET];
									Buffer -> Data		+= Buffer -> ActionData[ARG_OFFSET];

									Signal(Buffer -> Caller,SIG_HANDSHAKE);

									Done = TRUE;

									break;
								}

								if(Seek(Buffer -> FileHandle,-(Buffer -> ReadBufFull + Buffer -> Cached) + Buffer -> ActionData[ARG_OFFSET],OFFSET_CURRENT) != -1)
									Buffer -> RealPosition += -(Buffer -> ReadBufFull + Buffer -> Cached) + Buffer -> ActionData[ARG_OFFSET];
								else
									Buffer -> Result = -1;

								break;

							case SEEK_END:

								if(Seek(Buffer -> FileHandle,Buffer -> ActionData[ARG_OFFSET],OFFSET_END) != -1)
									Buffer -> RealPosition = Seek(Buffer -> FileHandle,0,OFFSET_CURRENT);
								else
									Buffer -> Result = -1;

								break;

							default:

								Buffer -> Result = -1;
						}

						Buffer -> ReadBufFull = 0;

						if(Buffer -> Result != -1)
						{
							Buffer -> Data		= Buffer -> DataBuffer[0];
							Buffer -> DataCount	= 1;

							if(!Buffer -> WriteAccess)
							{
								Buffer -> ReadBufFull	 = Read(Buffer -> FileHandle,Buffer -> Data,Buffer -> BufLength);
								Buffer -> WriteBufFull	 = 0;
								Buffer -> Read		 = TRUE;
								Buffer -> RealPosition	+= Buffer -> ReadBufFull;

								if(Buffer -> ReadBufFull)
								{
									Buffer -> Cached = Buffer -> DataLength[1] = Read(Buffer -> FileHandle,Buffer -> DataBuffer[1],Buffer -> BufLength);

									Buffer -> RealPosition += Buffer -> Cached;
								}
							}
						}
						else
							Buffer -> LastActionFailed = TRUE;
					}
					else
						Buffer -> ReadBufFull = 0;

					Buffer -> BufPosition	= 0;
					Buffer -> Written	= FALSE;
				}

				break;

				/* Fill the buffer with fresh data. */

			case BUF_FILL:

				Buffer -> Data		= Buffer -> DataBuffer[Buffer -> DataCount];
				Buffer -> ReadBufFull	= Buffer -> DataLength[Buffer -> DataCount];
				Buffer -> WriteBufFull	= 0;
				Buffer -> BufPosition	= 0;
				Buffer -> Read		= TRUE;
				Buffer -> Written	= FALSE;
				Buffer -> Fresh		= FALSE;

				if(Buffer -> ReadBufFull)
					WasFull = TRUE;
				else
					WasFull = FALSE;

					/* The buffer contents have been
					 * swapped, now wake the caller
					 * up and fill the next buffer
					 * asynchronously.
					 */

				Signal(Buffer -> Caller,SIG_HANDSHAKE);

				Done = TRUE;

				if(WasFull)
				{
					Buffer -> DataCount = (Buffer -> DataCount + 1) % BUFFER_NUMBER;

					Buffer -> Cached = Buffer -> DataLength[Buffer -> DataCount] = Read(Buffer -> FileHandle,Buffer -> DataBuffer[Buffer -> DataCount],Buffer -> BufLength);

					Buffer -> RealPosition += Buffer -> Cached;

					if(!Buffer -> DataLength[Buffer -> DataCount])
					{
						if(IoErr())
							Buffer -> LastActionFailed = TRUE;
					}
				}

				break;

				/* Flush the contents of the buffer to disk. */

			case BUF_FLUSH:

				if(Buffer -> BufPosition && Buffer -> Written)
				{
					Data			= Buffer -> Data;
					Length			= Buffer -> BufPosition;

					Buffer -> Data		= Buffer -> DataBuffer[Buffer -> DataCount];
					Buffer -> DataCount	= (Buffer -> DataCount + 1) % BUFFER_NUMBER;

					Buffer -> ReadBufFull	= 0;
					Buffer -> WriteBufFull	= Buffer -> BufLength;
					Buffer -> BufPosition	= 0;
					Buffer -> Read		= FALSE;
					Buffer -> Written	= FALSE;

					Signal(Buffer -> Caller,SIG_HANDSHAKE);

					Done = TRUE;

					if(Write(Buffer -> FileHandle,Data,Length) != Length)
						Buffer -> LastActionFailed = TRUE;
					else
					{
						Buffer -> RealPosition += Length;

						if(Buffer -> DirLock)
						{
							if(!Info(Buffer -> DirLock,&Buffer -> InfoData))
							{
								UnLock(Buffer -> DirLock);

								Buffer -> DirLock = NULL;
							}
						}
					}
				}
				else
				{
					Buffer -> ReadBufFull	= 0;
					Buffer -> WriteBufFull	= Buffer -> BufLength;
					Buffer -> BufPosition	= 0;
					Buffer -> Read		= FALSE;
					Buffer -> Written	= FALSE;
				}

				Buffer -> Fresh = FALSE;

				break;
		}

			/* Ring back if necessary. */

		if(!Done && !Terminated)
			Signal(Buffer -> Caller,SIG_HANDSHAKE);
	}

		/* Remove the message from the list. */

	ObtainSemaphore(&DoubleBufferSemaphore);

	Remove((struct Node *)Buffer);

	ReleaseSemaphore(&DoubleBufferSemaphore);

		/* Lock & quit. */

	Forbid();

	Signal(Buffer -> Caller,SIG_HANDSHAKE);
}

	/* BufferFill(struct Buffer *Buffer):
	 *
	 *	Fills a given buffer with fresh data.
	 */

STATIC BYTE __inline
BufferFill(struct Buffer *Buffer)
{
	if(Buffer -> LastActionFailed)
		return(FALSE);
	else
	{
		if(!Buffer -> ReadBufFull)
		{
			Buffer -> Action = BUF_FILL;

			Forbid();

			Signal(Buffer -> Child,SIG_COMMAND);

			ClrSignal(SIG_HANDSHAKE);

			Wait(SIG_HANDSHAKE);

			Permit();
		}

		return(TRUE);
	}
}

	/* BufferFlush(struct Buffer *Buffer):
	 *
	 *	Flush the contents of a given buffer to disk.
	 */

STATIC BYTE __inline
BufferFlush(struct Buffer *Buffer)
{
	if(Buffer -> LastActionFailed)
		return(FALSE);
	else
	{
		if(Buffer -> BufPosition && Buffer -> Written)
		{
			Buffer -> Action = BUF_FLUSH;

			Forbid();

			Signal(Buffer -> Child,SIG_COMMAND);

			ClrSignal(SIG_HANDSHAKE);

			Wait(SIG_HANDSHAKE);

			Permit();
		}

		return(TRUE);
	}
}

	/* IsValidBuffer(struct Buffer *Buffer):
	 *
	 *	Scans the double buffered file list for
	 *	a valid entry.
	 */

STATIC BYTE __inline
IsValidBuffer(struct Buffer *Buffer)
{
	BYTE		 GotIt = FALSE;
	struct Node	*Node;

	ObtainSemaphore(&DoubleBufferSemaphore);

	Node = DoubleBufferList . lh_Head;

	while(Node -> ln_Succ)
	{
		if(Buffer == (struct Buffer *)Node)
		{
			GotIt = TRUE;

			break;
		}

		Node = Node -> ln_Succ;
	}

	ReleaseSemaphore(&DoubleBufferSemaphore);

	return(GotIt);
}

	/* BPrintf():
	 *
	 *	Prints text into a buffered file.
	 */

LONG __stdargs
BPrintf(struct Buffer *Buffer,STRPTR Format,...)
{
	UBYTE	String[256];
	va_list	VarArgs;

	va_start(VarArgs,Format);
	VSPrintf(String,Format,VarArgs);
	va_end(VarArgs);

	return(BufferWrite(Buffer,String,strlen(String)));
}

	/* BufferClose(struct Buffer *Buffer):
	 *
	 *	Close a buffered filehandle.
	 */

BYTE __regargs
BufferClose(struct Buffer *Buffer)
{
	if(IsValidBuffer(Buffer))
	{
		BYTE Success;

		Buffer -> Action = BUF_CLOSE;

		Forbid();

		Signal(Buffer -> Child,SIG_COMMAND);

		ClrSignal(SIG_HANDSHAKE);

		Wait(SIG_HANDSHAKE);

		Permit();

		Success = Buffer -> Result;

		FreeVec(Buffer);

		return(Success);
	}
	else
		return(FALSE);
}

	/* BufferOpen(STRPTR Name,STRPTR AccessMode):
	 *
	 *	Open a file for buffered I/O.
	 */

struct Buffer * __regargs
BufferOpen(STRPTR Name,STRPTR AccessMode)
{
	struct Buffer *Buffer;

		/* Allocate the buffer data. */

	if(Buffer = (struct Buffer *)AllocVec(sizeof(struct Buffer) + BUFFER_SIZE * BUFFER_NUMBER,MEMF_ANY|MEMF_CLEAR))
	{
		struct Process	*Process;
		WORD		 i;

			/* Set up the first buffer. */

		Buffer -> DataBuffer[0] = (STRPTR)(Buffer + 1);

			/* Set up the individual buffers. */

		for(i = 1 ; i < BUFFER_NUMBER ; i++)
			Buffer -> DataBuffer[i] = &Buffer -> DataBuffer[i - 1][BUFFER_SIZE];

		Buffer -> BufLength	= BUFFER_SIZE;
		Buffer -> WriteBufFull	= Buffer -> BufLength;

			/* Create the asynchronous file server. */

		if(!(Process = CreateNewProcTags(
			NP_Entry,	FileBufferServer,
			NP_Name,	"term file process",
			NP_Priority,	SysBase -> ThisTask -> tc_Node . ln_Pri,
			NP_StackSize,	8192,
			NP_WindowPtr,	-1,
		TAG_DONE)))
		{
			FreeVec(Buffer);

			return(NULL);
		}

			/* Set up the message header. */

		Buffer -> Message . mn_Length	= sizeof(struct Buffer);

		Buffer -> ActionData[ARG_NAME]	= (LONG)Name;
		Buffer -> ActionData[ARG_MODE]	= (LONG)AccessMode;

		Buffer -> Child			= Process;
		Buffer -> Caller		= (struct Process *)SysBase -> ThisTask;

		Forbid();

			/* Send it to the waiting server process. */

		PutMsg(&Process -> pr_MsgPort,&Buffer -> Message);

			/* Wait for ringback. */

		ClrSignal(SIG_HANDSHAKE);

		Wait(SIG_HANDSHAKE);

		Permit();

			/* Do we have a valid filehandle? */

		if(!Buffer -> FileHandle)
		{
			FreeVec(Buffer);

			return(NULL);
		}
		else
			return(Buffer);
	}

	return(NULL);
}

	/* BufferSeek(struct Buffer *Buffer,LONG Offset,LONG Origin):
	 *
	 *	Move the read/write pointer to a specific position
	 *	in a file (not really buffered).
	 */

BYTE __regargs
BufferSeek(struct Buffer *Buffer,LONG Offset,LONG Origin)
{
	Buffer -> Action			= BUF_SEEK;
	Buffer -> ActionData[ARG_OFFSET]	= Offset;
	Buffer -> ActionData[ARG_ORIGIN]	= Origin;

	Forbid();

	Signal(Buffer -> Child,SIG_COMMAND);

	ClrSignal(SIG_HANDSHAKE);

	Wait(SIG_HANDSHAKE);

	Permit();

	if(Buffer -> Result == -1)
		return(FALSE);
	else
		return(TRUE);
}

	/* BufferRead():
	 *
	 *	Read data from a file (buffered).
	 */

LONG __regargs
BufferRead(struct Buffer *Buffer,STRPTR Destination,LONG Size)
{
	LONG	 BytesRead = 0,ToCopy,BufPosition,ReadBufFull;
	UBYTE	*Data;

		/* If there is still data to be written in
		 * the buffer, write it.
		 */

	if(Buffer -> Written)
	{
		if(!BufferFlush(Buffer))
			return(0);
	}

		/* Set up for read access. */

	BufPosition	= Buffer -> BufPosition;
	ReadBufFull	= Buffer -> ReadBufFull;
	Data		= &Buffer -> Data[BufPosition];

		/* Remember access. */

	Buffer -> Used	= TRUE;

		/* Continue until all data has been processed. */

	while(Size)
	{
			/* Determine number of bytes to transfer. */

		if(ToCopy = (Size > ReadBufFull) ? ReadBufFull : Size)
		{
			CopyMem(Data,Destination,ToCopy);

			Size		-= ToCopy;
			BufPosition	+= ToCopy;
			ReadBufFull	-= ToCopy;
			Destination	+= ToCopy;
			Data		+= ToCopy;
			BytesRead	+= ToCopy;
		}
		else
		{
				/* Refill buffer with data. */

			Buffer -> BufPosition	= BufPosition;
			Buffer -> ReadBufFull	= ReadBufFull;

			if(!BufferFill(Buffer))
				return(BytesRead);

			if(!Buffer -> ReadBufFull)
			{
				Buffer -> BufPosition = BufPosition;

				return(BytesRead);
			}

				/* Pick up new data. */

			BufPosition		= Buffer -> BufPosition;
			ReadBufFull		= Buffer -> ReadBufFull;
			Data			= Buffer -> Data;
		}
	}

		/* Install new data. */

	Buffer -> BufPosition	= BufPosition;
	Buffer -> ReadBufFull	= ReadBufFull;

	return(BytesRead);
}

	/* BufferWrite():
	 *
	 *	Write data to a file (buffered).
	 */

LONG __regargs
BufferWrite(struct Buffer *Buffer,STRPTR Source,LONG Size)
{
	LONG	 BytesWritten = 0,ToCopy,BufPosition,WriteBufFull;
	UBYTE	*Data;

		/* If there is still read data in the buffer,
		 * reset the control information.
		 */

	if(Buffer -> Read)
	{
		Buffer -> WriteBufFull	= Buffer -> BufLength;
		Buffer -> BufPosition	= 0;
		Buffer -> Read		= FALSE;
	}

		/* Set up for write access. */

	Buffer -> Written = TRUE;

	BufPosition	= Buffer -> BufPosition;
	WriteBufFull	= Buffer -> WriteBufFull;
	Data		= &Buffer -> Data[BufPosition];

		/* Remember access. */

	Buffer -> Used	= TRUE;

		/* Continue until all data has been processed. */

	while(Size)
	{
			/* Determine number of bytes to transfer. */

		if(ToCopy = (Size > WriteBufFull ? WriteBufFull : Size))
		{
			CopyMem(Source,Data,ToCopy);

			Size		-= ToCopy;
			BufPosition	+= ToCopy;
			WriteBufFull	-= ToCopy;
			Source		+= ToCopy;
			Data		+= ToCopy;
			BytesWritten	+= ToCopy;
		}
		else
		{
				/* Flush the contents of the
				 * write buffer.
				 */

			Buffer -> BufPosition	= BufPosition;
			Buffer -> WriteBufFull	= WriteBufFull;

			if(!BufferFlush(Buffer))
				return(BytesWritten);

				/* Pick up new data. */

			BufPosition		= Buffer -> BufPosition;
			WriteBufFull		= Buffer -> WriteBufFull;
			Data			= Buffer -> Data;

				/* Important - or BufferFlush() won't
				 * write the final buffer contents when
				 * the buffered file handle is freed up.
				 */

			Buffer -> Written = TRUE;
		}
	}

		/* Install new data. */

	Buffer -> BufPosition	= BufPosition;
	Buffer -> WriteBufFull	= WriteBufFull;

	return(BytesWritten);
}
