/*
**	FileBuffer.c
**
**	Double-buffered file I/O routines
**
**	Copyright © 1990-1997 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	:ts=4
*/

#ifndef _GLOBAL_H
#include "Global.h"
#endif

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

	/* OpenFileWithMode(STRPTR Name,STRPTR ModeString):
	 *
	 *	Open a plain AmigaDOS file and turn the stdio mode string
	 *	into the appropriate Open() mode.
	 */

STATIC BPTR
OpenFileWithMode(STRPTR Name,STRPTR ModeString)
{
	LONG Mode;

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

			if(ModeString[1] == '+')
				Mode = MODE_READWRITE;
			else
				Mode = MODE_OLDFILE;

			break;

		case 'w':

			if(ModeString[1] == '+')
			{
				BPTR SomeLock;

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

					if(!DeleteFile(Name))
						return(NULL);
				}

				Mode = MODE_READWRITE;
			}
			else
				Mode = MODE_NEWFILE;

			break;

		case 'a':

			return(OpenToAppend(Name,NULL));

		default:

			return(NULL);
	}

	return(Open(Name,Mode));
}

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

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

	if(File->InfoPort)
	{
			/* This is more or less a trick; we try to obtain
			 * information on a filing system without having a
			 * shared lock on it. We cannot DupLockFromFH()
			 * the file handle since it was allocated for
			 * exclusive access. The ACTION_DISK_INFO packet
			 * solves this problem as it requires only the
			 * filing system task (MsgPort) to be given.
			 */

		if(!DoPkt(File->InfoPort,ACTION_DISK_INFO,MKBADDR(&File->InfoData),0,0,0,0))
		{
			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 SAVE_DS
FileBufferServer(VOID)
{
	BOOL Terminated,Done,WasFull;
	struct Buffer *Buffer;
	struct MsgPort *Port;
	struct Process *Me;
	UBYTE *String;
	LONG Length;
	APTR Data;

	Terminated = FALSE;

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

	Me = (struct Process *)FindTask(NULL);

	Port = &Me->pr_MsgPort;

	WaitPort(Port);

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

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

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

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

	Buffer->WriteAccess = (BOOL)(String[0] != 'r');

	Buffer->FileHandle = OpenFileWithMode((STRPTR)Buffer->ActionData[ARG_NAME],String);

		/* 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((struct Task *)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
		{
			Buffer->InfoPort = ((struct FileHandle *)BADDR(Buffer->FileHandle))->fh_Type;

			ObtainInfo(Buffer);

			Signal((struct Task *)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;

				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((struct Task *)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((struct Task *)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((struct Task *)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((struct Task *)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((struct Task *)Buffer->Caller,SIG_HANDSHAKE);
	}

		/* Remove the message from the list. */

	ObtainSemaphore(&DoubleBufferSemaphore);

	Remove((struct Node *)Buffer);

	ReleaseSemaphore(&DoubleBufferSemaphore);

		/* Lock & quit. */

	Forbid();

	Signal((struct Task *)Buffer->Caller,SIG_HANDSHAKE);
}

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

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

			ShakeHands((struct Task *)Buffer->Child,SIG_COMMAND);
		}

		return(TRUE);
	}
}

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

STATIC BOOL
IsValidBuffer(struct Buffer *Buffer)
{
	struct Node *Node;
	BOOL GotIt;

	ObtainSemaphore(&DoubleBufferSemaphore);

	GotIt = FALSE;

	for(Node = DoubleBufferList.lh_Head ; Node->ln_Succ ; Node = Node->ln_Succ)
	{
		if(Buffer == (struct Buffer *)Node)
		{
			GotIt = TRUE;

			break;
		}
	}

	ReleaseSemaphore(&DoubleBufferSemaphore);

	return(GotIt);
}

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

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

		/* Allocate buffer handle (dummy). */

	if(Buffer = (struct Buffer *)AllocVecPooled(sizeof(struct Buffer),MEMF_ANY | MEMF_CLEAR))
	{
			/* Provide basic information. */

		DateStamp(&Buffer->OpenDate);

		Buffer->WriteAccess = (BOOL)(AccessMode[0] != 'r');

		Buffer->SimpleIO = TRUE;

		Buffer->FileHandle = OpenFileWithMode(Name,AccessMode);

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

		if(Buffer->FileHandle)
		{
				/* To keep track of the free space still
				 * available on the filing system.
				 */

			if(Buffer->WriteAccess)
			{
				Buffer->InfoPort = ((struct FileHandle *)BADDR(Buffer->FileHandle))->fh_Type;

				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 *
OpenFileBuffered(STRPTR Name,STRPTR AccessMode)
{
	struct Buffer *Buffer;
	LONG Size;

	Size = (Config->MiscConfig->IOBufferSize + 15) & ~15;

	if(Size < 2048)
		Size = 2048;

		/* Allocate the buffer data. */

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

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

	if(Buffer)
	{
		struct Process *Process;
		LONG i;

			/* Set up the first buffer. */

		Buffer->DataBuffer[0] = (UBYTE *)(((ULONG)(Buffer + 1) + 15) & ~15);

			/* Set up the individual buffers. */

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

		Buffer->BufLength		= Size;
		Buffer->WriteBufFull	= Buffer->BufLength;

			/* Create the asynchronous file server. */

		if(!(Process = CreateNewProcTags(
			NP_Entry,		FileBufferServer,
			NP_Name,		"term File Process",
			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 *)FindTask(NULL);

		Forbid();

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

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

			/* Wait for ringback. */

		WaitForHandshake();

		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
BPrintf(struct Buffer *Buffer,STRPTR Format,...)
{
	UBYTE	String[256];
	va_list	VarArgs;

	va_start(VarArgs,Format);
	LimitedVSPrintf(sizeof(String),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.
	 */

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

			ShakeHands((struct Task *)Buffer->Child,SIG_COMMAND);
		}

		return(TRUE);
	}
}

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

BOOL
BufferClose(struct Buffer *Buffer)
{
	if(IsValidBuffer(Buffer))
	{
		BOOL Success;

			/* Unbuffered file handle? */

		if(Buffer->SimpleIO)
		{
				/* 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;

			ShakeHands((struct Task *)Buffer->Child,SIG_COMMAND);

			Success = Buffer->Result;
		}

		FreeVecPooled(Buffer);

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

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

struct Buffer *
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).
	 */

BOOL
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;

		ShakeHands((struct Task *)Buffer->Child,SIG_COMMAND);

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

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

LONG
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,ToCopy,BufPosition,ReadBufFull;
		UBYTE *Data;

		BytesRead = 0;

			/* 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
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,ToCopy,BufPosition,WriteBufFull;
		UBYTE *Data;

		BytesWritten = 0;

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