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

#include "termGlobal.h"

	/* Argument types. */

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

	/* Seek offsets. */

enum	{	SEEKFILE_SET,SEEKFILE_CURR,SEEKFILE_END };

	/* Command codes. */

enum	{	BUF_CLOSE,BUF_SEEK,BUF_FILL,BUF_FLUSH };

	/* ObtainInfo(struct Buffer *File):
	 *
	 *	Obtain information on the disk a buffered file
	 *	resides on.
	 */

STATIC VOID __regargs
ObtainInfo(struct Buffer *File)
{
		/* Lock on the path available? */

	if(File -> DirLock)
	{
			/* Obtain information... */

		if(!Info(File -> DirLock,&File -> InfoData))
		{
				/* No success, release resources. */

			UnLock(File -> DirLock);

			File -> DirLock = NULL;

			File -> InfoData . id_NumBlocks		= 0;
			File -> InfoData . id_BytesPerBlock	= 0;
		}
	}
}

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

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

			Stop = PathPart(TempBuffer);

			*Stop = 0;

			if(TempLock = Lock(TempBuffer,ACCESS_READ))
			{
				BPTR OldDir = CurrentDir(TempLock);

				Buffer -> DirLock = Lock(":",ACCESS_READ);

				CurrentDir(OldDir);

				UnLock(TempLock);
			}

			ObtainInfo(Buffer);

			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] == SEEKFILE_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;
						else
							ObtainInfo(Buffer);
					}

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

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

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

								break;

							case SEEKFILE_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 SEEKFILE_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
					{
						ObtainInfo(Buffer);

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

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

	/* OpenFileSimple(STRPTR Name,STRPTR AccessMode):
	 *
	 *	Open simple (unbuffered) file.
	 */

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

		/* Allocate buffer handle (dummy). */

	if(Buffer = (struct Buffer *)AllocVecPooled(sizeof(struct Buffer),MEMF_ANY | MEMF_CLEAR))
	{
		BPTR SomeLock;

			/* Provide basic information. */

		DateStamp(&Buffer -> OpenDate);

		Buffer -> WriteAccess = TRUE;

		Buffer -> SimpleIO = TRUE;

			/* Open the file. */

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

				if(AccessMode[1] == '+')
					Buffer -> FileHandle = Open(Name,MODE_READWRITE);
				else
				{
					Buffer -> WriteAccess = FALSE;

					Buffer -> FileHandle = Open(Name,MODE_OLDFILE);
				}

				break;

			case 'w':

				if(AccessMode[1] == '+')
				{
					if(SomeLock = Lock(Name,ACCESS_WRITE))
					{
						UnLock(SomeLock);

						DeleteFile(Name);
					}

					Buffer -> FileHandle = Open(Name,MODE_READWRITE);
				}
				else
					Buffer -> FileHandle = Open(Name,MODE_NEWFILE);

				break;

			case 'a':

				if(SomeLock = Lock(Name,ACCESS_WRITE))
				{
					UnLock(SomeLock);

					if(Buffer -> FileHandle = Open(Name,MODE_READWRITE))
					{
						if(Seek(Buffer -> FileHandle,0,OFFSET_END) == -1)
						{
							Close(Buffer -> FileHandle);

							Buffer -> FileHandle = NULL;
						}
					}
				}
				else
					Buffer -> FileHandle = Open(Name,MODE_NEWFILE);

				break;
		}

			/* Did we succeed in opening the file? */

		if(Buffer -> FileHandle)
		{
				/* Try to obtain a lock on the destination directory. */

			if(Buffer -> WriteAccess)
			{
				UBYTE	 TempBuffer[MAX_FILENAME_LENGTH],
					*Stop;
				BPTR	 TempLock;

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

				Stop = PathPart(TempBuffer);

				*Stop = 0;

				if(TempLock = Lock(TempBuffer,ACCESS_READ))
				{
					BPTR OldDir = CurrentDir(TempLock);

					Buffer -> DirLock = Lock(":",ACCESS_READ);

					CurrentDir(OldDir);

					UnLock(TempLock);
				}

				ObtainInfo(Buffer);
			}

				/* Link the file into the list. */

			ObtainSemaphore(&DoubleBufferSemaphore);

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

			ReleaseSemaphore(&DoubleBufferSemaphore);

			return(Buffer);
		}
		else
			FreeVecPooled(Buffer);
	}

	return(NULL);
}

	/* OpenFileBuffered(STRPTR Name,STRPTR AccessMode):
	 *
	 *	Open double-buffered file.
	 */

STATIC struct Buffer * __regargs
OpenFileBuffered(STRPTR Name,STRPTR AccessMode)
{
	struct Buffer	*Buffer;
	LONG		 Size;

	Size = Config -> MiscConfig -> IOBufferSize;

		/* Allocate the buffer data. */

	do
	{
		Buffer = (struct Buffer *)AllocVecPooled(sizeof(struct Buffer) + Size * BUFFER_NUMBER,MEMF_ANY | MEMF_CLEAR);

		Size /= 2;
	}
	while(!Buffer && Size > 2048);

	if(Buffer)
	{
		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] + Config -> MiscConfig -> IOBufferSize;

		Buffer -> BufLength	= Config -> MiscConfig -> IOBufferSize;
		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)))
		{
			FreeVecPooled(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)
		{
			FreeVecPooled(Buffer);

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

	return(NULL);
}

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

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

BYTE __regargs
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);
	}
}

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

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

			/* Unbuffered file handle? */

		if(Buffer -> SimpleIO)
		{
				/* Drop the destination drawer lock. */

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

				/* Close the file. */

			if(Close(Buffer -> FileHandle))
				Success = TRUE;
			else
				Success = FALSE;

				/* Unlink the handle. */

			ObtainSemaphore(&DoubleBufferSemaphore);

			Remove((struct Node *)Buffer);

			ReleaseSemaphore(&DoubleBufferSemaphore);
		}
		else
		{
			Buffer -> Action = BUF_CLOSE;

			Forbid();

			Signal(Buffer -> Child,SIG_COMMAND);

			ClrSignal(SIG_HANDSHAKE);

			Wait(SIG_HANDSHAKE);

			Permit();

			Success = Buffer -> Result;
		}

		FreeVecPooled(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)
{
		/* Simple file handling? */

	if(Config -> MiscConfig -> SimpleIO)
		return(OpenFileSimple(Name,AccessMode));
	else
	{
		struct Buffer *Buffer;

			/* Try to open a buffered file, if unsuccessful
			 * fall back to simple file I/O.
			 */

		if(!(Buffer = OpenFileBuffered(Name,AccessMode)))
			Buffer = OpenFileSimple(Name,AccessMode);

		return(Buffer);
	}
}

	/* 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)
{
	if(Buffer -> SimpleIO)
	{
		if(Seek(Buffer -> FileHandle,Offset,Origin) == -1)
			return(FALSE);
		else
			return(TRUE);
	}
	else
	{
		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)
{
	if(Buffer -> SimpleIO)
	{
		LONG Bytes;

		Buffer -> Used = TRUE;

		if((Bytes = Read(Buffer -> FileHandle,Destination,Size)) < 0)
			Bytes = 0;

		return(Bytes);
	}
	else
	{
		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)
{
	if(Buffer -> SimpleIO)
	{
		LONG Bytes;

		Buffer -> Used = TRUE;

		Buffer -> WriteAccess = TRUE;

		if((Bytes = Write(Buffer -> FileHandle,Source,Size)) < 0)
			Bytes = 0;
		else
		{
			Buffer -> BufPosition += Bytes;

			if(Buffer -> BufPosition >= Config -> MiscConfig -> IOBufferSize)
			{
				Buffer -> BufPosition = 0;

				ObtainInfo(Buffer);
			}
		}

		return(Bytes);
	}
	else
	{
		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);
	}
}
