/* $Revision Header * Header built automatically - do not edit! *************
 *
 *	(C) Copyright 1991 by Olaf 'Olsen' Barthel & MXM
 *
 *	Name .....: Buffer.c
 *	Created ..: Tuesday 03-Sep-91 17:44
 *	Revision .: 3
 *
 *	Date            Author          Comment
 *	=========       ========        ====================
 *	23-Oct-91	Olsen		Closing files twice fixed.
 *	19-Sep-91	Olsen		Nasty bugs fixed.
 *	03-Sep-91	Olsen		Created this file!
 *
 * $Revision Header ********************************************************/

#include "termGlobal.h"

	/* Size of a file buffer. */

#define BUFFER_SIZE	32768

	/* The two message signals. */

#define	BUFFER_COMMAND	SIGBREAKF_CTRL_E
#define BUFFER_SYNC	SIGBREAKF_CTRL_F

	/* 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()
{
	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 = (UBYTE *)Buffer -> ActionData[ARG_MODE];

	Buffer -> WriteAccess = TRUE;

		/* Put the message into the list. */

	ObtainSemaphore(&DoubleBufferSemaphore);

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

	ReleaseSemaphore(&DoubleBufferSemaphore);

		/* Check for the open type. */

	switch(String[0])
	{
		case 'r':	if(String[1] == '+')
					Buffer -> FileHandle = Open((UBYTE *)Buffer -> ActionData[ARG_NAME],MODE_READWRITE);
				else
				{
					Buffer -> WriteAccess = FALSE;

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

				break;

		case 'w':	if(String[1] == '+')
				{
					if(SomeLock = Lock((UBYTE *)Buffer -> ActionData[ARG_NAME],ACCESS_WRITE))
					{
						UnLock(SomeLock);

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

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

				break;

		case 'a':	if(SomeLock = Lock((UBYTE *)Buffer -> ActionData[ARG_NAME],ACCESS_WRITE))
				{
					UnLock(SomeLock);

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

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

				break;

		default:	break;
	}

		/* Clear signal bit. */

	SetSignal(0,BUFFER_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,BUFFER_SYNC);

				/* Fill the second buffe asynchronously. */

			Buffer -> DataLength[1]	= Buffer -> Cached = Read(Buffer -> FileHandle,Buffer -> DataBuffer[1],Buffer -> BufLength);
			Buffer -> RealPosition += Buffer -> Cached;
		}
		else
			Signal(Buffer -> Caller,BUFFER_SYNC);
	}
	else
		Terminated = TRUE;

		/* Go into loop waiting for commands. */

	while(!Terminated)
	{
		Wait(BUFFER_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:	if(Buffer -> BufPosition && Buffer -> Written)
						Write(Buffer -> FileHandle,Buffer -> Data,Buffer -> BufPosition);

					Close(Buffer -> FileHandle);

					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,BUFFER_SYNC);

						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,BUFFER_SYNC);

											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,BUFFER_SYNC);

					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,BUFFER_SYNC);

						Done = TRUE;

						if(Write(Buffer -> FileHandle,Data,Length) != Length)
							Buffer -> LastActionFailed = TRUE;
						else
							Buffer -> RealPosition += Length;
					}
					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,BUFFER_SYNC);
	}

		/* Remove the message from the list. */

	ObtainSemaphore(&DoubleBufferSemaphore);

	Remove((struct Node *)Buffer);

	ReleaseSemaphore(&DoubleBufferSemaphore);

		/* Lock & quit. */

	Forbid();

	Signal(Buffer -> Caller,BUFFER_SYNC);
}

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

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

			Signal(Buffer -> Child,BUFFER_COMMAND);

			Wait(BUFFER_SYNC);
		}

		return(TRUE);
	}
}

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

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

			Signal(Buffer -> Child,BUFFER_COMMAND);

			Wait(BUFFER_SYNC);
		}

		return(TRUE);
	}
}

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

STATIC BYTE __regargs
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,UBYTE *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;

		Signal(Buffer -> Child,BUFFER_COMMAND);

		Wait(BUFFER_SYNC);

		Success = Buffer -> Result;

		FreeVec(Buffer);

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

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

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

		/* Allocate the buffer data. */

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

			/* Set up the first buffer. */

		Buffer -> DataBuffer[0] = (UBYTE *)(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 Server",
			NP_Priority,	0,
			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;

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

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

			/* Wait for ringback. */

		Wait(BUFFER_SYNC);

			/* 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;

	Signal(Buffer -> Child,BUFFER_COMMAND);

	Wait(BUFFER_SYNC);

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

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

LONG __regargs
BufferRead(struct Buffer *Buffer,UBYTE *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];

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

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

		if(ToCopy = (Size > ReadBufFull) ? ReadBufFull : Size)
		{
			memcpy(Destination,Data,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,UBYTE *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];

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

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

		if(ToCopy = (Size > WriteBufFull ? WriteBufFull : Size))
		{
			memcpy(Data,Source,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);
}
