/*
**	termXPR.c
**
**	External transfer protocol support routines
**
**	Copyright © 1990-1993 by Olaf `Olsen' Barthel & MXM
**		All Rights Reserved
*/

#include "termGlobal.h"

	/* How many options xpr_options() will display in a single column. */

#define OPTION_WRAP	 16

	/* These variables keep the transferred bytes and transfer
	 * time in seconds.
	 */

STATIC LONG		 ByteVal,
			 ByteMax,
			 TimeVal,
			 TimeMax;

	/* Transfer statistics. */

STATIC ULONG		 CPS_Minimum,
			 CPS_Maximum,
			 CPS_Average,
			 CPS_Count;

	/* The name of the file being transmitted, in case the
	 * `override transfer path' feature is enabled.
	 */

STATIC struct Buffer	*CurrentFile;

	/* Yet another flag, this one determines whether we already told
	 * the user that the file we are currently processing will fit
	 * on the destination device.
	 */

STATIC BYTE		 Alerted,CarrierMessage;

	/* OpenTransferWindow():
	 *
	 *	Local routine to open the transfer window.
	 */

STATIC BYTE
OpenTransferWindow(VOID)
{
		/* Block window input. */

	BlockWindows();

		/* Try to open the transfer window panel. */

	if(TransferPanel(LocaleString(MSG_TERMXPR_RECEIVE_FILES_TXT)))
	{
			/* Supply the log text. */

		LogAction(LocaleString(MSG_TERMXPR_LOGMSG_INITIATE_BINARY_DOWNLOAD_TXT));

			/* Return success. */

		return(TRUE);
	}
	else
	{
			/* Re-enable window input. */

		ReleaseWindows();

			/* Return failure. */

		return(FALSE);
	}
}

	/* NewFileName(STRPTR Source,STRPTR Buffer):
	 *
	 *	Build a valid file and path name, including path
	 *	substitution, etc.
	 */

STATIC STRPTR __regargs
NewFileName(STRPTR Source,STRPTR Buffer)
{
	if(Config -> MiscConfig -> OverridePath && !Uploading)
	{
		if(DownloadPath)
			strcpy(Buffer,DownloadPath);
		else
			strcpy(Buffer,Config -> PathConfig -> BinaryDownloadPath);

		if(!Buffer[0])
		{
			if(!GetCurrentDirName(Buffer,MAX_FILENAME_LENGTH))
				Buffer[0] = 0;
		}

		if(Buffer[0])
		{
			if(AddPart(Buffer,FilePart(Source),MAX_FILENAME_LENGTH))
				return(Buffer);
		}
	}
	else
	{
		if(FilePart(Source) == Source)
		{
			if(GetCurrentDirName(Buffer,MAX_FILENAME_LENGTH))
			{
				if(AddPart(Buffer,Source,MAX_FILENAME_LENGTH))
					return(Buffer);
			}
		}
	}

	return(Source);
}

	/* IdentifySource(STRPTR Name,STRPTR BBSName,struct DateStamp *OpenDate):
	 *
	 *	Attach source information (BBS name, date and time) to a file.
	 */

STATIC VOID __inline
IdentifySource(STRPTR Name,STRPTR BBSName,struct DateStamp *OpenDate)
{
	UBYTE		SharedBuffer[MAX_FILENAME_LENGTH],
			Time[20],
			Date[20];
	struct DateTime	DateTime;

	memcpy(&DateTime . dat_Stamp,OpenDate,sizeof(struct DateStamp));

		/* Prepare for date conversion. */

	DateTime . dat_Format	= FORMAT_DOS;
	DateTime . dat_Flags	= 0;
	DateTime . dat_StrDay	= NULL;
	DateTime . dat_StrDate	= Date;
	DateTime . dat_StrTime	= Time;

		/* Convert the date. */

	if(DateToStr(&DateTime))
	{
		if(BBSName[0])
			SPrintf(SharedBuffer,"%s %s %s",BBSName,Date,Time);
		else
			SPrintf(SharedBuffer,"%s %s",Date,Time);

		SetComment(Name,SharedBuffer);
	}
}

	/* GetSeconds(STRPTR String):
	 *
	 *	Tries to turn a string of the format hh:mm:ss into
	 *	an integer number.
	 */

STATIC LONG __inline
GetSeconds(STRPTR String)
{
	UBYTE	Buffer[20];
	LONG	Seconds = 0;

	memset(Buffer,0,20);

	strcpy(Buffer,String);

	Seconds += Atol(&Buffer[6]);

	Buffer[5] = 0;

	Seconds += Atol(&Buffer[3]) * 60;

	Buffer[2] = 0;

	Seconds += Atol(&Buffer[0]) * 3600;

	return(Seconds);
}

	/* TruncateName(STRPTR FileName):
	 *
	 *	Truncates a file name to a maximum of 48 characters.
	 */

STATIC STRPTR __inline
TruncateName(STRPTR FileName)
{
	WORD Len = strlen(FileName);

	if(Len > 48)
	{
		WORD i;

		for(i = Len - 48 ; i < Len ; i++)
		{
			if(i >= Len - 44 && FileName[i] == '/')
			{
				STATIC UBYTE NameBuffer[MAX_FILENAME_LENGTH];

				strcpy(NameBuffer,".../");

				strcat(NameBuffer,&FileName[i + 1]);

				return(NameBuffer);
			}
		}
	}

	return(FileName);
}

	/* CalculateBlocks(LONG Size,LONG BlockSize):
	 *
	 *	Calculate the number of blocks a file will
	 *	occupy if saved to a disk.
	 */

STATIC LONG __inline
CalculateBlocks(LONG Size,LONG BlockSize)
{
	LONG	Blocks		= 1;		/* One for the file header. */
	BYTE	HasExtension	= FALSE,	/* No extension block yet. */
		Extension	= 0;		/* How many block pointers yet. */

		/* Round to next block. */

	Size = ((Size + BlockSize - 1) / BlockSize) * BlockSize;

	while(Size)
	{
			/* Add another block. */

		Blocks++;

			/* Subtract another block. */

		Size -= BlockSize;

			/* Add another block pointer, if 72 have been
			 * added, add another extension block.
			 */

		if((Extension++) == 72)
		{
				/* If no extension block has been generated
				 * yet, we were running on the block pointers
				 * of the file header itself.
				 */

			if(!HasExtension)
				HasExtension = TRUE;
			else
				Blocks++;

				/* Reset extension block counter. */

			Extension = 0;
		}
	}

	return(Blocks);
}

	/* GetOptionMode(struct xpr_option *Option):
	 *
	 *	Turn text into a boolean value.
	 */

STATIC BYTE __inline
GetOptionMode(struct xpr_option *Option)
{
	if(Option)
	{
		STATIC STRPTR TrueOptions[] =
		{
			"ON",
			"TRUE",
			"T",
			"YES",
			"Y",
			NULL
		};

		register WORD i;

		for(i = 0 ; TrueOptions[i] ; i++)
		{
			if(!Stricmp(Option -> xpro_value,TrueOptions[i]))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* CreateAllOptionGadgets():
	 *
	 *	Create all the gadgets for the xpr options control panel.
	 */

STATIC struct Gadget *
CreateAllOptionGadgets(LONG *Count,LONG NumOpts,struct xpr_option *Opts[],struct Gadget *GadgetArray[],struct Gadget **GadgetList,APTR VisualInfo)
{
	struct Gadget		*Gadget;
	struct NewGadget	 NewGadget;
	LONG			 i,MaxLength = 0,MaxLength1 = 0,MaxWidth,MaxWidth1,Len,NumOptsSmall;
	BYTE			 WrapIt = FALSE;

	SZ_SizeSetup(Window -> WScreen,&UserFont,TRUE);

	memset(&NewGadget,0,sizeof(struct NewGadget));

	*Count = 0;

	SZ_ResetMaxWidth();

	if(Gadget = CreateContext(GadgetList))
	{
		NewGadget . ng_TextAttr		= &UserFont;
		NewGadget . ng_VisualInfo	= VisualInfo;
		NewGadget . ng_Flags		= NG_HIGHLABEL;

			/* Precalculate gadget left edge offset. */

		if((NumOptsSmall = NumOpts) > OPTION_WRAP)
			NumOptsSmall = OPTION_WRAP;

		for(i = 0 ; i < NumOptsSmall ; i++)
		{
			if(Opts[i])
			{
				switch(Opts[i] -> xpro_type)
				{
					case XPRO_BOOLEAN:

						if((Len = SZ_TextWidth(Opts[i] -> xpro_description)) > MaxLength)
							MaxLength = Len;

						SZ_UpdateMaxWidth(CHECKBOX_KIND,NULL,0,NULL);

						break;

					case XPRO_HEADER:

						if((Len = SZ_TextWidth(Opts[i] -> xpro_description)) > MaxLength)
							MaxLength = Len;

						break;

					case XPRO_LONG:
					case XPRO_STRING:
					case XPRO_COMMPAR:

						if((Len = SZ_TextWidth(Opts[i] -> xpro_description)) > MaxLength)
							MaxLength = Len;

						SZ_UpdateMaxWidth(STRING_KIND,NULL,20,NULL);

						break;

					case XPRO_COMMAND:

						SZ_UpdateMaxWidth(TEXT_KIND,Opts[i] -> xpro_description,0,NULL);

						break;

					default:

						break;
				}
			}
		}

		MaxWidth = SZ_ResetMaxWidth();

		SZ_AddLeftOffset(MaxLength + INTERWIDTH);

		for(i = OPTION_WRAP ; i < NumOpts ; i++)
		{
			if(Opts[i])
			{
				switch(Opts[i] -> xpro_type)
				{
					case XPRO_BOOLEAN:

						if((Len = SZ_TextWidth(Opts[i] -> xpro_description)) > MaxLength1)
							MaxLength1 = Len;

						SZ_UpdateMaxWidth(CHECKBOX_KIND,NULL,0,NULL);

						break;

					case XPRO_HEADER:

						if((Len = SZ_TextWidth(Opts[i] -> xpro_description)) > MaxLength1)
							MaxLength1 = Len;

						break;

					case XPRO_LONG:
					case XPRO_STRING:
					case XPRO_COMMPAR:

						if((Len = SZ_TextWidth(Opts[i] -> xpro_description)) > MaxLength1)
							MaxLength1 = Len;

						SZ_UpdateMaxWidth(STRING_KIND,NULL,20,NULL);

						break;

					case XPRO_COMMAND:

						SZ_UpdateMaxWidth(TEXT_KIND,Opts[i] -> xpro_description,0,NULL);

						break;

					default:

						break;
				}
			}
		}

		MaxWidth1 = SZ_ResetMaxWidth();

		SZ_SetWidth(MaxWidth);

		for(i = 0 ; i < NumOpts ; i++)
		{
			if(i == OPTION_WRAP + 1)
				WrapIt = TRUE;

			if(!Opts[i])
				continue;

			if(WrapIt)
			{
				SZ_SetWidth(MaxWidth1);
				SZ_AddLeftOffset(MaxLength1 + INTERWIDTH);
			}

			switch(Opts[i] -> xpro_type)
			{
				case XPRO_BOOLEAN:

					NewGadget . ng_GadgetText	= Opts[i] -> xpro_description;
					NewGadget . ng_GadgetID		= i;

					GadgetArray[i] = Gadget = CreateGadget(CHECKBOX_KIND,Gadget,&NewGadget,
						SZ_Adjust,	TRUE,
						SZ_AutoWidth,	TRUE,
						SZ_NewColumn,	WrapIt,

						GTCB_Checked,	GetOptionMode(Opts[i]),
					TAG_DONE);

					break;

				case XPRO_LONG:

					NewGadget . ng_GadgetText	= Opts[i] -> xpro_description;
					NewGadget . ng_GadgetID		= i;

					GadgetArray[i] = Gadget = CreateGadget(INTEGER_KIND,Gadget,&NewGadget,
						SZ_Adjust,	TRUE,
						SZ_AutoWidth,	TRUE,
						SZ_NewColumn,	WrapIt,

						GTIN_Number,	Atol(Opts[i] -> xpro_value),
						GTST_EditHook,	&CommandHook,
					TAG_DONE);

					break;

				case XPRO_STRING:
				case XPRO_COMMPAR:

					NewGadget . ng_GadgetText	= Opts[i] -> xpro_description;
					NewGadget . ng_GadgetID		= i;

					GadgetArray[i] = Gadget = CreateGadget(STRING_KIND,Gadget,&NewGadget,
						SZ_Adjust,	TRUE,
						SZ_AutoWidth,	TRUE,
						SZ_NewColumn,	WrapIt,

						GTST_String,	Opts[i] -> xpro_value,
						GTST_MaxChars,	Opts[i] -> xpro_length - 1,
						GTST_EditHook,	&CommandHook,
					TAG_DONE);

					break;

				case XPRO_HEADER:

					NewGadget . ng_GadgetText	= Opts[i] -> xpro_description;
					NewGadget . ng_GadgetID		= i;
					NewGadget . ng_Flags		= NG_HIGHLABEL;

					GadgetArray[i] = Gadget = CreateGadget(TEXT_KIND,Gadget,&NewGadget,
						SZ_Adjust,	TRUE,
						SZ_AutoWidth,	TRUE,

						GTTX_Text,	" ",
					TAG_DONE);

					NewGadget . ng_Flags = 0;

					break;

				case XPRO_COMMAND:

					NewGadget . ng_GadgetText	= Opts[i] -> xpro_description;
					NewGadget . ng_GadgetID		= i;

					GadgetArray[i] = Gadget = CreateGadget(BUTTON_KIND,Gadget,&NewGadget,
						SZ_Adjust,	TRUE,
						SZ_AutoWidth,	TRUE,
					TAG_DONE);

					break;

				default:break;
			}

			WrapIt = FALSE;
		}

		SZ_ResetMaxWidth();

		SZ_UpdateMaxWidth(BUTTON_KIND,LocaleString(MSG_GLOBAL_SAVE_GAD),0,NULL);
		SZ_UpdateMaxWidth(BUTTON_KIND,LocaleString(MSG_GLOBAL_CANCEL_GAD),0,NULL);

		SZ_SetWidth(SZ_ResetMaxWidth());

		NewGadget . ng_GadgetText	= LocaleString(MSG_GLOBAL_SAVE_GAD);
		NewGadget . ng_GadgetID		= GAD_XPROPTIONS_USE;

		GadgetArray[i++] = Gadget = CreateGadget(BUTTON_KIND,Gadget,&NewGadget,
			SZ_AlignExtra,	TRUE,
			SZ_Adjust,	TRUE,
			SZ_AutoWidth,	TRUE,
			SZ_AlignLeft,	TRUE,
			SZ_AlignBottom,	TRUE,

			GT_Underscore,	'_',
		TAG_DONE);

		NewGadget . ng_GadgetText	= LocaleString(MSG_GLOBAL_CANCEL_GAD);
		NewGadget . ng_GadgetID		= GAD_XPROPTIONS_CANCEL;

		GadgetArray[i++] = Gadget = CreateGadget(BUTTON_KIND,Gadget,&NewGadget,
			SZ_Adjust,	TRUE,
			SZ_AutoWidth,	TRUE,
			SZ_AlignRight,	TRUE,

			GT_Underscore,	'_',
		TAG_DONE);

		if(Gadget)
			*Count = i;
	}

	return(Gadget);
}

	/* SerialErrorReport(struct IOExtSer *Request):
	 *
	 *	Report a certain I/O error cause.
	 */

STATIC BYTE __regargs
SerialErrorReport(struct IOExtSer *Request)
{
	STRPTR	ErrorMessage;
	BYTE	IsFatal = FALSE;

	switch(Request -> IOSer . io_Error)
	{
		case SerErr_LineErr:

			ErrorMessage = LocaleString(MSG_TERMXPR_ERROR_HARDWARE_DATA_OVERRUN_TXT);

			IsFatal = TRUE;

			break;

		case SerErr_ParityErr:

			ErrorMessage = LocaleString(MSG_TERMXPR_ERROR_PARITY_ERROR_TXT);

			IsFatal = TRUE;

			break;

		case SerErr_TimerErr:

			ErrorMessage = LocaleString(MSG_TERMXPR_ERROR_TIMER_ERROR_TXT);
			break;

		case SerErr_BufOverflow:

			ErrorMessage = LocaleString(MSG_TERMXPR_ERROR_READ_BUFFER_OVERFLOWED_TXT);
			break;

		case SerErr_NoDSR:

			ErrorMessage = LocaleString(MSG_TERMXPR_ERROR_NO_DSR_TXT);

			IsFatal = TRUE;

			break;

		case SerErr_DetectedBreak:

			ErrorMessage = LocaleString(MSG_TERMXPR_ERROR_BREAK_DETECTED_TXT);

			IsFatal = TRUE;

			break;

		default:

			ErrorMessage = LocaleString(MSG_TERMXPR_ERROR_UNKNOWN_CAUSE_TXT);
			break;
	}

	AddTransferInfo(LocaleString(MSG_TERMXPR_ERROR_TEMPLATE_TXT),Request -> IOSer . io_Error,ErrorMessage);

	if(IsFatal)
	{
		TransferError = TRUE;

		return(TRUE);
	}
	else
		return(FALSE);
}

	/* xpr_fopen(STRPTR FileName,STRPTR AccessMode):
	 *
	 *	Open a file for random access.
	 */

LONG __saveds __asm
xpr_fopen(register __a0 STRPTR FileName,register __a1 STRPTR AccessMode)
{
	UBYTE		 RealName[MAX_FILENAME_LENGTH];
	struct Buffer	*File;

	Alerted = CarrierMessage = FALSE;

		/* Reset transfer counters. */

	ByteVal = ByteMax = TimeVal = TimeMax = 0;

		/* Reset CPS statistics. */

	CPS_Minimum	= (ULONG)~0;
	CPS_Maximum	= 0;
	CPS_Average	= 0;
	CPS_Count	= 0;

		/* No window available for output? */

	if(!TransferWindow)
		OpenTransferWindow();

		/* Disable the time and data bars in the transfer window. */

	if(TransferWindow)
	{
		GhostStats(TransferGadgetArray[GAD_TRANSFER_PERCENT]);
		GhostStats(TransferGadgetArray[GAD_TRANSFER_TIME]);
	}

		/* Determine the file name. */

	FileName = NewFileName(FileName,RealName);

		/* Determine file transfer mode... */

	if(File = BufferOpen(FileName,AccessMode))
	{
		switch(AccessMode[0])
		{
			case 'r':

				LogAction(LocaleString(MSG_TERMXPR_LOGMSG_SEND_FILE_TXT),FileName);
				break;

			case 'w':

				LogAction(LocaleString(MSG_TERMXPR_LOGMSG_RECEIVE_FILE_TXT),FileName);
				break;

			case 'a':

				LogAction(LocaleString(MSG_TERMXPR_LOGMSG_UPDATE_FILE_TXT),FileName);
				break;
		}

		CurrentFile = File;
	}

	DidTransfer = TRUE;

	return((LONG)File);
}

	/* xpr_fclose(struct Buffer *File):
	 *
	 *	Close a file opened by xpr_fopen.
	 */

LONG __saveds __asm
xpr_fclose(register __a0 struct Buffer *File)
{
	UBYTE			RealName[MAX_FILENAME_LENGTH];
	struct DateStamp	OpenDate;
	BYTE			WriteAccess,
				Used;

		/* Save some information. */

	OpenDate	= File -> OpenDate;
	WriteAccess	= File -> WriteAccess;
	Used		= File -> Used;

		/* Get the name of the file. */

	if(!NameFromFH(File -> FileHandle,RealName,MAX_FILENAME_LENGTH))
		RealName[0] = 0;

		/* Close the file and see what it brings... */

	if(BufferClose(File) && RealName[0])
	{
			/* Did any access take place at all?
			 * xprzmodem.library for example just
			 * opens and closes a file in order to
			 * see if it exists.
			 */

		if(!Used)
			LogAction(LocaleString(MSG_TERMXPR_CLOSE_FILE_TXT),RealName);
		else
		{
				/* Did we receive or send a file? */

			if(WriteAccess)
			{
				LONG Size;

					/* Did the file remain empty? */

				if(!(Size = GetFileSize(RealName)))
				{
					AddTransferInfo(LocaleString(MSG_TERMXPR_FILE_REMOVED_TXT),FilePart(RealName));

						/* Delete empty file. */

					if(DeleteFile(RealName))
						LogAction(LocaleString(MSG_TERMXPR_CLOSE_FILE_REMOVED_TXT),RealName);
					else
						LogAction(LocaleString(MSG_TERMXPR_CLOSE_FILE_TXT),RealName);
				}
				else
				{
					struct Node *SomeNode;

					if(ByteMax)
					{
						if(Size >= ByteMax)
							AddTransferInfo(LocaleString(MSG_TERMXPR_FILE_RECEIVED_TXT),FilePart(RealName));
						else
							AddTransferInfo(LocaleString(MSG_TERMXPR_INCOMPLETE_FILE_RECEIVED_TXT),FilePart(RealName));
					}

						/* Try to identify the file type. */

					switch(Config -> MiscConfig -> IdentifyFiles)
					{
						case IDENTIFY_FILETYPE:

							Identify(RealName);
							break;

						case IDENTIFY_SOURCE:

							IdentifySource(RealName,CurrentBBSName,&OpenDate);
							break;
					}

					if(CPS_Minimum == (ULONG)~0)
						CPS_Minimum = 0;

					if(CPS_Count == 0)
					{
						CPS_Average	= 0;
						CPS_Count	= 1;
					}

					LogAction(LocaleString(MSG_TERMXPR_CLOSE_FILE_BYTES_TXT),RealName,Size,CPS_Minimum,CPS_Average / CPS_Count,CPS_Maximum);

					if(SomeNode = CreateGenericListNode(0,RealName))
						AddGenericListNode(GenericListTable[GLIST_DOWNLOAD],SomeNode,ADD_GLIST_BOTTOM);
				}
			}
			else
			{
					/* Set the archived bit on files we uploaded? */

				if(Config -> MiscConfig -> SetArchivedBit)
				{
					struct FileInfoBlock *FileInfo;

					if(FileInfo = (struct FileInfoBlock *)AllocDosObject(DOS_FIB,TAG_DONE))
					{
						BPTR FileLock;

							/* Get a lock on it. */

						if(FileLock = Lock(RealName,ACCESS_READ))
						{
								/* Examine the file. */

							if(Examine(FileLock,FileInfo))
							{
									/* Remove the lock. */

								UnLock(FileLock);

									/* Set the `archived' bit. */

								SetProtection(RealName,FileInfo -> fib_Protection | FIBF_ARCHIVE);
							}
							else
								UnLock(FileLock);
						}

						FreeDosObject(DOS_FIB,FileInfo);
					}
				}

				AddTransferInfo(LocaleString(MSG_TERMXPR_FILE_SENT_TXT),FilePart(RealName));

				LogAction(LocaleString(MSG_TERMXPR_CLOSE_FILE_TXT),RealName);

				RemoveUploadListItem(RealName);
			}
		}
	}

	CurrentFile = NULL;

	return(1);
}

	/* xpr_fread(APTR Buffer,LONG Size,LONG Count,struct Buffer *File):
	 *
	 *	Read a few bytes from a file.
	 */

LONG __saveds __asm
xpr_fread(register __a0 APTR Buffer,register __d0 LONG Size,register __d1 LONG Count,register __a1 struct Buffer *File)
{
	return(BufferRead(File,Buffer,Size * Count) / Size);
}

	/* xpr_fwrite(APTR Buffer,LONG Size,LONG Count,struct Buffer *File):
	 *
	 *	Write a few bytes to a file.
	 */

LONG __saveds __asm
xpr_fwrite(register __a0 APTR Buffer,register __d0 LONG Size,register __d1 LONG Count,register __a1 struct Buffer *File)
{
	return(BufferWrite(File,Buffer,Size * Count) / Size);
}

	/* xpr_fseek(struct Buffer *File,LONG Offset,LONG Origin):
	 *
	 *	Move the read/write pointer in a file.
	 */

LONG __saveds __asm
xpr_fseek(register __a0 struct Buffer *File,register __d0 LONG Offset,register __d1 LONG Origin)
{
	return(BufferSeek(File,Offset,Origin) ? 0 : -1);
}

	/* xpr_sread(APTR Buffer,LONG Size,LONG Timeout):
	 *
	 *	Read a few bytes from the serial port (including
	 *	timeouts).
	 */

LONG __saveds __asm
xpr_sread(register __a0 APTR Buffer,register __d0 LONG Size,register __d1 ULONG Timeout)
{
		/* No window available for output? */

	if(!TransferWindow)
	{
		if(!OpenTransferWindow())
			return(-1);
	}

		/* Are both IORequests available? */

	if(WriteRequest && ReadRequest)
	{
			/* Valid size parameter? */

		if(Size > 0)
		{
				/* How many bytes are still in the serial buffer? */

			WriteRequest -> IOSer . io_Command = SDCMD_QUERY;

			DoIO(WriteRequest);

				/* Return error if carrier is lost. */

			if((WriteRequest -> io_Status & CIAF_COMCD) && Config -> SerialConfig -> CheckCarrier)
			{
				if(!CarrierMessage)
				{
					CarrierMessage = TRUE;

					AddTransferInfo(LocaleString(MSG_TERMXPR_CARRIER_LOST_TXT));
				}

				Online = FALSE;

				TransferError = TRUE;

				return(-1);
			}
			else
			{
					/* Enough data pending to be read? */

				if(WriteRequest -> IOSer . io_Actual >= Size)
				{
					ReadRequest -> IOSer . io_Command	= CMD_READ;
					ReadRequest -> IOSer . io_Data		= Buffer;
					ReadRequest -> IOSer . io_Length	= Size;

					if(!DoIO(ReadRequest))
					{
						BytesIn += Size;

						return(Size);
					}
					else
					{
						if(SerialErrorReport(ReadRequest))
							return(-1);
						else
							return(0);
					}
				}
				else
				{
						/* Any timeout specified? */

					if(Timeout)
					{
						register ULONG SignalSet,WindowMask = PORTMASK(TransferWindow -> UserPort);

							/* Set up the timer. */

						TimeRequest -> tr_node . io_Command	= TR_ADDREQUEST;
						TimeRequest -> tr_time . tv_secs	= Timeout / MILLION;
						TimeRequest -> tr_time . tv_micro	= Timeout % MILLION;

							/* Set up the read request. */

						ReadRequest -> IOSer . io_Command	= CMD_READ;
						ReadRequest -> IOSer . io_Data		= Buffer;
						ReadRequest -> IOSer . io_Length	= Size;

							/* Prevent early termination. */

						ClrSignal(SIG_SERIAL | SIG_TIMER);

							/* Start IO... */

						SendIO(ReadRequest);
						SendIO(TimeRequest);

						FOREVER
						{
							SignalSet = Wait(SIG_SERIAL | SIG_TIMER | WindowMask);

								/* Receive buffer filled? */

							if(SignalSet & SIG_SERIAL)
							{
									/* Did the request terminate gracefully? */

								if(!WaitIO(ReadRequest))
								{
										/* Abort the timer request. */

									if(!CheckIO(TimeRequest))
										AbortIO(TimeRequest);

									WaitIO(TimeRequest);

									BytesIn += Size;

									return(Size);
								}
								else
								{
										/* Abort the timer request. */

									if(!CheckIO(TimeRequest))
										AbortIO(TimeRequest);

									WaitIO(TimeRequest);

									if(SerialErrorReport(ReadRequest))
										return(-1);
									else
										return(0);
								}
							}

								/* Hit by timeout? */

							if(SignalSet & SIG_TIMER)
							{
									/* Abort the read request. */

								if(!CheckIO(ReadRequest))
									AbortIO(ReadRequest);

								WaitIO(ReadRequest);

									/* Remove the timer request. */

								WaitIO(TimeRequest);

									/* Did the driver receive any
									 * data?
									 */

								if(ReadRequest -> IOSer . io_Actual)
								{
									BytesIn += ReadRequest -> IOSer . io_Actual;

									return((LONG)ReadRequest -> IOSer . io_Actual);
								}
								else
								{
									LONG Length;

										/* Take a second look and query the number of
										 * bytes ready to be received, there may
										 * still be some bytes in the buffer.
										 * Note: this depends on the way the
										 * driver handles read abort.
										 */

									WriteRequest -> IOSer . io_Command = SDCMD_QUERY;

									DoIO(WriteRequest);

										/* Are there any bytes to be transferred? */

									if(Length = WriteRequest -> IOSer . io_Actual)
									{
											/* Don't read too much. */

										if(Length > Size)
											Length = Size;

											/* Read the data. */

										ReadRequest -> IOSer . io_Command	= CMD_READ;
										ReadRequest -> IOSer . io_Data		= Buffer;
										ReadRequest -> IOSer . io_Length	= Length;

											/* Read the data. */

										if(!DoIO(ReadRequest))
										{
											BytesIn += Length;

											return(Length);
										}
										else
										{
											if(SerialErrorReport(ReadRequest))
												return(-1);
										}
									}

									return(0);
								}
							}

								/* Abort file transfer? */

							if(SignalSet & WindowMask)
							{
								if(xpr_chkabort() == -1)
								{
									if(!CheckIO(ReadRequest))
										AbortIO(ReadRequest);

									if(!CheckIO(TimeRequest))
										AbortIO(TimeRequest);

									WaitIO(ReadRequest);
									WaitIO(TimeRequest);

									SendAbort = TRUE;

									return(-1);
								}
							}
						}
					}
					else
					{
						LONG Length = WriteRequest -> IOSer . io_Actual;

							/* Return as many bytes as are currently
							 * in the buffer.
							 */

						if(Length)
						{
								/* Don't read too much! */

							if(Length > Size)
								Length = Size;

								/* Fill the buffer. */

							ReadRequest -> IOSer . io_Command	= CMD_READ;
							ReadRequest -> IOSer . io_Data		= Buffer;
							ReadRequest -> IOSer . io_Length	= Length;

							if(!DoIO(ReadRequest))
							{
								BytesIn += Length;

								return(Length);
							}
							else
							{
								if(SerialErrorReport(ReadRequest))
									return(-1);
							}
						}

						return(0);
					}
				}
			}
		}
		else
			return(0);
	}
	else
		return(-1);
}

	/* xpr_swrite(APTR Buffer,LONG Size):
	 *
	 *	Write a few bytes to the serial port.
	 */

LONG __saveds __asm
xpr_swrite(register __a0 APTR Buffer,register __d0 LONG Size)
{
	if(WriteRequest)
	{
			/* Return error if carrier is lost. */

		if(Config -> SerialConfig -> CheckCarrier)
		{
				/* Query the serial status bits. */

			WriteRequest -> IOSer . io_Command = SDCMD_QUERY;

			DoIO(WriteRequest);

				/* Is the carrier signal still present? */

			if(WriteRequest -> io_Status & CIAF_COMCD)
			{
				if(!CarrierMessage)
				{
					CarrierMessage = TRUE;

					AddTransferInfo(LocaleString(MSG_TERMXPR_CARRIER_LOST_TXT));
				}

				Online = FALSE;

				TransferError = TRUE;

				return(-1);
			}
		}

			/* Send the data. */

		WriteRequest -> IOSer . io_Command	= CMD_WRITE;
		WriteRequest -> IOSer . io_Data		= Buffer;
		WriteRequest -> IOSer . io_Length	= Size;

		if(!DoIO(WriteRequest))
			BytesOut += Size;
		else
		{
			if(SerialErrorReport(WriteRequest))
				return(-1);
		}

		return(0);
	}

	return(-1);
}

	/* xpr_sflush():
	 *
	 *	Release the contents of all serial buffers.
	 */

LONG __saveds __asm
xpr_sflush()
{
	if(WriteRequest)
	{
		WriteRequest -> IOSer . io_Command = CMD_CLEAR;

		if(DoIO(WriteRequest))
		{
			if(SerialErrorReport(WriteRequest))
				return(-1);
		}

		return(0);
	}
	else
		return(-1);
}

	/* xpr_update(struct XPR_UPDATE *UpdateInfo):
	 *
	 *	Update the information displayed in the transfer window.
	 */

LONG __saveds __asm
xpr_update(register __a0 struct XPR_UPDATE *UpdateInfo)
{
		/* No window available for output? */

	if(!TransferWindow)
	{
		if(!OpenTransferWindow())
			return(0);
	}

	if(UpdateInfo)
	{
		struct RastPort	*RPort		= TransferWindow -> RPort;
		BYTE		 NewByte	= FALSE,
				 NewTime	= FALSE;

		if((UpdateInfo -> xpru_updatemask & XPRU_PROTOCOL) && UpdateInfo -> xpru_protocol)
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_1],0,UpdateInfo -> xpru_protocol);

		if((UpdateInfo -> xpru_updatemask & XPRU_MSG) && UpdateInfo -> xpru_msg)
			AddTransferInfo(UpdateInfo -> xpru_msg);

		if((UpdateInfo -> xpru_updatemask & XPRU_ERRORMSG) && UpdateInfo -> xpru_errormsg)
			AddTransferInfo(UpdateInfo -> xpru_errormsg);

		if((UpdateInfo -> xpru_updatemask & XPRU_FILENAME) && UpdateInfo -> xpru_filename)
		{
			UBYTE	RealName[MAX_FILENAME_LENGTH];
			STRPTR	FileName;

			FileName = NewFileName(UpdateInfo -> xpru_filename,RealName);

			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_2],0,TruncateName(FileName));

			if(FileTransferInfo)
			{
				if(FileTransferInfo -> CurrentFile)
				{
					struct FileTransferNode *Node = (struct FileTransferNode *)FileTransferInfo -> CurrentFile -> Node . mln_Succ;

					if(Node -> Node . mln_Succ)
						SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_2],1,TruncateName(Node -> Name));
					else
						SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_2],1,LocaleString(MSG_GLOBAL_NONE_TXT));
				}

				SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],2,ConvNumber,FileTransferInfo -> TotalSize);

				if(FileTransferInfo -> TotalFiles)
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],4,LocaleString(MSG_TRANSFERPANEL_NUMBER_TO_GO_PERCENT_TXT),FileTransferInfo -> DoneFiles,FileTransferInfo -> TotalFiles,(100 * FileTransferInfo -> DoneFiles) / FileTransferInfo -> TotalFiles);
				else
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],4,LocaleString(MSG_TRANSFERPANEL_NUMBER_TO_GO_PERCENT_TXT),FileTransferInfo -> DoneFiles,FileTransferInfo -> TotalFiles,0);
			}
		}

		if((UpdateInfo -> xpru_updatemask & XPRU_FILESIZE) && UpdateInfo -> xpru_filesize != -1)
		{
			if(CurrentFile)
			{
				if(CurrentFile -> DirLock)
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],0,ConvNumber10,UpdateInfo -> xpru_filesize);
				else
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],0,ConvNumber,UpdateInfo -> xpru_filesize);
			}
			else
				SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],0,ConvNumber,UpdateInfo -> xpru_filesize);

			if(ByteMax = UpdateInfo -> xpru_filesize)
				NewByte = TRUE;
		}

		if((UpdateInfo -> xpru_updatemask & XPRU_BYTES) && UpdateInfo -> xpru_bytes != -1)
		{
			if(CurrentFile)
			{
				if(CurrentFile -> DirLock)
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],1,ConvNumber10,ByteVal = UpdateInfo -> xpru_bytes);
				else
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],1,ConvNumber,ByteVal = UpdateInfo -> xpru_bytes);
			}
			else
				SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],1,ConvNumber,ByteVal = UpdateInfo -> xpru_bytes);

			if(FileTransferInfo)
			{
				ULONG Len = FileTransferInfo -> DoneSize + UpdateInfo -> xpru_bytes;

				if(FileTransferInfo -> TotalSize)
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],3,LocaleString(MSG_TRANSFERPANEL_NUMBER_PERCENT_TXT),Len,(100 * Len) / FileTransferInfo -> TotalSize);
				else
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],3,LocaleString(MSG_TRANSFERPANEL_NUMBER_PERCENT_TXT),Len,0);
			}

			if(ByteMax)
				NewByte = TRUE;

			if(CurrentFile)
			{
				if(CurrentFile -> DirLock && !Uploading)
				{
					if(CurrentFile -> InfoData . id_NumBlocks && CurrentFile -> InfoData . id_BytesPerBlock)
					{
						if(ByteMax)
						{
							register LONG Space = CurrentFile -> InfoData . id_NumBlocks - CurrentFile -> InfoData . id_NumBlocksUsed;

							if(Space >= 0)
							{
								register LONG Blocks = CalculateBlocks(ByteMax,CurrentFile -> InfoData . id_BytesPerBlock);

								if(Space < Blocks)
								{
									if(!Alerted)
									{
										Alerted = TRUE;

										BumpWindow(TransferWindow);

										Say(LocaleString(MSG_TERMXPR_SAY_FILE_MAY_NOT_FIT_TXT));
									}

									SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_2],2,LocaleString(MSG_TERMXPR_BYTES_FULL_TXT),Space * CurrentFile -> InfoData . id_BytesPerBlock,100 * (CurrentFile -> InfoData . id_NumBlocksUsed) / CurrentFile -> InfoData . id_NumBlocks,LocaleString(MSG_TERMXPR_FILE_MAY_NOT_FIT_TXT));
								}
								else
									SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_2],2,LocaleString(MSG_TERMXPR_BYTES_FULL_TXT),Space * CurrentFile -> InfoData . id_BytesPerBlock,100 * (CurrentFile -> InfoData . id_NumBlocksUsed) / CurrentFile -> InfoData . id_NumBlocks,"");
							}
							else
								SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_2],2,LocaleString(MSG_TERMXPR_BYTES_FULL_TXT),(CurrentFile -> InfoData . id_NumBlocks - CurrentFile -> InfoData . id_NumBlocksUsed) * CurrentFile -> InfoData . id_BytesPerBlock,100 * (CurrentFile -> InfoData . id_NumBlocksUsed) / CurrentFile -> InfoData . id_NumBlocks);
						}
						else
							SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_2],2,LocaleString(MSG_TERMXPR_BYTES_FULL_TXT),(CurrentFile -> InfoData . id_NumBlocks - CurrentFile -> InfoData . id_NumBlocksUsed) * CurrentFile -> InfoData . id_BytesPerBlock,100 * (CurrentFile -> InfoData . id_NumBlocksUsed) / CurrentFile -> InfoData . id_NumBlocks);
					}
				}
			}
		}

		if((UpdateInfo -> xpru_updatemask & XPRU_BLOCKS) && UpdateInfo -> xpru_blocks != -1)
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT1],5,ConvNumber,UpdateInfo -> xpru_blocks);


		if((UpdateInfo -> xpru_updatemask & XPRU_DATARATE) && UpdateInfo -> xpru_datarate != -1)
		{
			if(UpdateInfo -> xpru_datarate)
			{
				if(UpdateInfo -> xpru_datarate < CPS_Minimum)
					CPS_Minimum = UpdateInfo -> xpru_datarate;

				if(UpdateInfo -> xpru_datarate > CPS_Maximum)
					CPS_Maximum = UpdateInfo -> xpru_datarate;

				if((ULONG)(CPS_Average + UpdateInfo -> xpru_datarate) >= CPS_Average)
				{
					CPS_Average += UpdateInfo -> xpru_datarate;

					CPS_Count++;
				}

				SZ_PrintLine(TransferWindow -> RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],0,ConvNumber,UpdateInfo -> xpru_datarate);
			}
			else
				SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],0,"0");
		}

		if((UpdateInfo -> xpru_updatemask & XPRU_CHARDELAY) && UpdateInfo -> xpru_chardelay != -1)
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],1,ConvNumber,UpdateInfo -> xpru_chardelay);

		if((UpdateInfo -> xpru_updatemask & XPRU_PACKETDELAY) && UpdateInfo -> xpru_packetdelay != -1)
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],2,ConvNumber,UpdateInfo -> xpru_packetdelay);

		if((UpdateInfo -> xpru_updatemask & XPRU_PACKETTYPE) && UpdateInfo -> xpru_packettype != -1)
		{
			if(UpdateInfo -> xpru_packettype > 32 && UpdateInfo -> xpru_packettype < 256)
			{
				if(IsPrintable[UpdateInfo -> xpru_packettype])
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],3,"`%lc'",UpdateInfo -> xpru_packettype);
				else
					SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],3,ConvNumber,UpdateInfo -> xpru_packettype);
			}
			else
				SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],3,ConvNumber,UpdateInfo -> xpru_packettype);
		}

		if((UpdateInfo -> xpru_updatemask & XPRU_BLOCKCHECK) && UpdateInfo -> xpru_blockcheck)
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],4,UpdateInfo -> xpru_blockcheck);

		if((UpdateInfo -> xpru_updatemask & XPRU_BLOCKSIZE) && UpdateInfo -> xpru_blocksize != -1)
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT1],5,ConvNumber,UpdateInfo -> xpru_blocksize);


		if((UpdateInfo -> xpru_updatemask & XPRU_EXPECTTIME) && UpdateInfo -> xpru_expecttime)
		{
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT2],0,UpdateInfo -> xpru_expecttime);

			if(TimeMax = GetSeconds((STRPTR)UpdateInfo -> xpru_expecttime))
				NewTime = TRUE;
		}

		if((UpdateInfo -> xpru_updatemask & XPRU_ELAPSEDTIME) && UpdateInfo -> xpru_elapsedtime)
		{
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_LEFT2],1,UpdateInfo -> xpru_elapsedtime);

			TimeVal = GetSeconds((STRPTR)UpdateInfo -> xpru_elapsedtime);

			if(TimeMax)
				NewTime = TRUE;
		}

		if((UpdateInfo -> xpru_updatemask & XPRU_ERRORS) && UpdateInfo -> xpru_errors != -1)
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT2],0,ConvNumber,UpdateInfo -> xpru_errors);

		if((UpdateInfo -> xpru_updatemask & XPRU_TIMEOUTS) && UpdateInfo -> xpru_timeouts != -1)
			SZ_PrintLine(RPort,TransferBoxArray[BOX_TRANSFER_RIGHT2],1,ConvNumber,UpdateInfo -> xpru_timeouts);

		if(NewByte)
		{
			ShowStats(TransferGadgetArray[GAD_TRANSFER_PERCENT],ByteVal,ByteMax);

			if(ByteMax)
				ShowString(TransferGadgetArray[GAD_TRANSFER_PERCENT],"%ld%%",(100 * ByteVal) / ByteMax);
			else
				ShowString(TransferGadgetArray[GAD_TRANSFER_PERCENT],"%ld%%",0);
		}

		if(NewTime)
		{
			LONG TimeDif = (TimeMax - TimeVal) < 0 ? 0 : TimeMax - TimeVal;

			ShowStats(TransferGadgetArray[GAD_TRANSFER_TIME],TimeDif,TimeMax);

			ShowString(TransferGadgetArray[GAD_TRANSFER_TIME],"%2ld:%02ld:%02ld",TimeDif / 3600,(TimeDif / 60) % 60,TimeDif % 60);
		}
	}

	return(1);
}

	/* xpr_chkabort():
	 *
	 *	Check if the user has aborted the transfer.
	 */

LONG __saveds __asm
xpr_chkabort()
{
	struct IntuiMessage	*Massage;
	ULONG			 IClass,
				 Code;
	struct Gadget		*Gadget;
	LONG			 Result = 0;

		/* No window available for output? */

	if(!TransferWindow)
	{
		if(!OpenTransferWindow())
			return(-1);
	}

		/* Process all incoming messages. */

	while(Massage = (struct IntuiMessage *)GT_GetIMsg(TransferWindow -> UserPort))
	{
		IClass	= Massage -> Class;
		Code	= Massage -> Code;
		Gadget	= (struct Gadget *)Massage -> IAddress;

		GT_ReplyIMsg(Massage);

		KeySelect(TransferGadgetArray,4,Code,TransferWindow,&Gadget,&IClass,&Code);

		if(!Result)
		{
			if(IClass == IDCMP_NEWSIZE)
			{
				if(!(TransferWindow -> Flags & WFLG_ZOOMED))
					RefreshTransferWindow();
			}

			if(IClass == IDCMP_CLOSEWINDOW)
			{
				LogAction(LocaleString(MSG_TERMXPR_LOGMSG_TRANSFER_ABORTED_TXT));

				TransferAborted = TRUE;

				Result = -1;
			}

			if(IClass == IDCMP_GADGETUP)
			{
				switch(Gadget -> GadgetID)
				{
					case GAD_TRANSFER_ABORT:

						LogAction(LocaleString(MSG_TERMXPR_LOGMSG_TRANSFER_ABORTED_TXT));

						TransferAborted = TRUE;

						Result = -1;

						break;

					case GAD_TRANSFER_SKIP:

						LogAction(LocaleString(MSG_TERMXPR_LOGMSG_FILE_SKIPPED_TXT));

						Result = 1;

						break;
				}
			}
		}
	}

	return(Result);
}

	/* CreateAllGetsGadgets():
	 *
	 *	Create all the gadgets required by xpr_gets() and the like.
	 */

struct Gadget *
CreateAllGetsGadgets(BYTE LoadGadget,BYTE Password,WORD MaxChars,STRPTR String,STRPTR Prompt,struct Gadget **GadgetArray,struct Gadget **GadgetList,APTR VisualInfo,struct Screen *Screen)
{
	struct Gadget		*Gadget;
	struct NewGadget	 NewGadget;

	if(MaxChars < 1)
		MaxChars = 255;

	memset(&NewGadget,0,sizeof(struct NewGadget));

	SZ_SizeSetup(Screen,&UserFont,TRUE);

	if(Gadget = CreateContext(GadgetList))
	{
		WORD Width = SZ_TextWidth(Prompt),Counter = 0;

		if(Width < SZ_Width(STRING_KIND,NULL,60,NULL))
			Width = SZ_Width(STRING_KIND,NULL,60,NULL);

		SZ_SetWidth(Width);

		NewGadget . ng_GadgetText	= Prompt;
		NewGadget . ng_TextAttr		= &UserFont;
		NewGadget . ng_VisualInfo	= VisualInfo;
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= PLACETEXT_ABOVE;

		if(LoadGadget)
		{
			GadgetArray[Counter++] = Gadget = CreateGadget(Password ? PASSWORD_KIND : STRING_KIND,Gadget,&NewGadget,
				SZ_Adjust,	TRUE,
				SZ_AutoWidth,	TRUE,
				SZ_LeaveFolder,	TRUE,

				GTST_MaxChars,	MaxChars,
				GTST_String,	String,
				GA_TabCycle,	FALSE,
			TAG_DONE);

			NewGadget . ng_GadgetID = Counter;

			GadgetArray[Counter++] = Gadget = CreateGadget(FILE_KIND,Gadget,&NewGadget,
				SZ_Adjust,	TRUE,
				SZ_AutoWidth,	TRUE,
				SZ_PlaceLeft,	TRUE,
			TAG_DONE);
		}
		else
		{
			GadgetArray[Counter++] = Gadget = CreateGadget(Password ? PASSWORD_KIND : STRING_KIND,Gadget,&NewGadget,
				SZ_Adjust,	TRUE,
				SZ_AutoWidth,	TRUE,

				GTST_MaxChars,	MaxChars,
				GTST_String,	String,
				GA_TabCycle,	FALSE,
			TAG_DONE);

			GadgetArray[Counter++] = NULL;
		}

		SZ_UpdateMaxWidth(BUTTON_KIND,LocaleString(MSG_TERMXPR_OKAY_GAD),0,NULL);
		SZ_UpdateMaxWidth(BUTTON_KIND,LocaleString(MSG_GLOBAL_CANCEL_GAD),0,NULL);

		SZ_SetWidth(SZ_ResetMaxWidth());

		NewGadget . ng_GadgetText	= LocaleString(MSG_TERMXPR_OKAY_GAD);
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= 0;

		GadgetArray[Counter++] = Gadget = CreateGadget(BUTTON_KIND,Gadget,&NewGadget,
			SZ_Adjust,	TRUE,
			SZ_AutoWidth,	TRUE,
			SZ_AlignExtra,	TRUE,
			SZ_AlignLeft,	TRUE,
			SZ_AlignBottom,	TRUE,

			GT_Underscore,	'_',
		TAG_DONE);

		NewGadget . ng_GadgetText	= LocaleString(MSG_GLOBAL_CANCEL_GAD);
		NewGadget . ng_GadgetID		= Counter;

		GadgetArray[Counter] = Gadget = CreateGadget(BUTTON_KIND,Gadget,&NewGadget,
			SZ_Adjust,	TRUE,
			SZ_AutoWidth,	TRUE,
			SZ_AlignRight,	TRUE,

			GT_Underscore,	'_',
		TAG_DONE);
	}

	return(Gadget);
}

	/* xpr_gets(STRPTR Prompt,STRPTR Buffer):
	 *
	 *	Prompt the user for string input.
	 */

LONG __saveds __asm
xpr_gets(register __a0 STRPTR Prompt,register __a1 STRPTR Buffer)
{
	struct Gadget	*GadgetList = NULL;
	struct Gadget	*GadgetArray[4];
	struct Window	*PanelWindow;
	LONG		 Success = FALSE;

	if(!Prompt)
		Prompt = LocaleString(MSG_TERMXPR_INPUT_REQUIRED_TXT);

	if(CreateAllGetsGadgets(FALSE,FALSE,0,Buffer,Prompt,GadgetArray,&GadgetList,VisualInfo,Window -> WScreen))
	{
		if(PanelWindow = OpenWindowTags(NULL,
			WA_Left,	GetScreenLeft(Window) + (GetScreenWidth(Window) - SZ_GetWindowWidth()) / 2,
			WA_Top,		GetScreenTop(Window) + (GetScreenHeight(Window) - SZ_GetWindowHeight())	/ 2,
			WA_Width,	SZ_GetWindowWidth(),
			WA_Height,	SZ_GetWindowHeight(),

			WA_Activate,	TRUE,
			WA_DragBar,	TRUE,
			WA_DepthGadget,	TRUE,
			WA_CloseGadget,	TRUE,
			WA_RMBTrap,	TRUE,
			WA_CustomScreen,Window -> WScreen,

			WA_IDCMP,	IDCMP_GADGETDOWN | IDCMP_ACTIVEWINDOW | IDCMP_CLOSEWINDOW | IDCMP_VANILLAKEY | IDCMP_RAWKEY | BUTTONIDCMP | STRINGIDCMP,

			WA_Title,	LocaleString(MSG_GLOBAL_ENTER_TEXT_TXT),
		TAG_DONE))
		{
			struct IntuiMessage	*Massage;
			ULONG			 IClass,Code;
			struct Gadget		*Gadget;
			BYTE			 Terminated = FALSE;

			PushWindow(PanelWindow);

			AddGList(PanelWindow,GadgetList,(UWORD)-1,(UWORD)-1,NULL);
			RefreshGList(GadgetList,PanelWindow,NULL,(UWORD)-1);
			GT_RefreshWindow(PanelWindow,NULL);

			ActiveGadget = NULL;

			while(!Terminated)
			{
				WaitPort(PanelWindow -> UserPort);

				while(!Terminated && (Massage = (struct IntuiMessage *)GT_GetIMsg(PanelWindow -> UserPort)))
				{
					IClass	= Massage -> Class;
					Code	= Massage -> Code;
					Gadget	= (struct Gadget *)Massage -> IAddress;

					GT_ReplyIMsg(Massage);

					if(IClass == IDCMP_VANILLAKEY)
						KeySelect(GadgetArray,3,Code,PanelWindow,&Gadget,&IClass,&Code);

					if(IClass == IDCMP_RAWKEY && Code == HELP_CODE)
						DisplayBeep(PanelWindow -> WScreen);

					if(IClass == IDCMP_GADGETDOWN)
					{
						if((Gadget -> GadgetType & GTYP_GTYPEMASK) == GTYP_STRGADGET)
							ActiveGadget = Gadget;
					}

					if(IClass == IDCMP_ACTIVEWINDOW && ActiveGadget)
						ActivateGadget(ActiveGadget,PanelWindow,NULL);

					if(IClass == IDCMP_CLOSEWINDOW)
						Terminated = TRUE;

					if(IClass == IDCMP_GADGETUP)
					{
						switch(Gadget -> GadgetID)
						{
							case 0:
							case 2:	strcpy(Buffer,SZ_GetString(GadgetArray[0]));

								Success = TRUE;

								Terminated = TRUE;

								break;

							case 3:	Terminated = TRUE;

								break;
						}
					}
				}
			}

			ActiveGadget = NULL;

			RemoveGList(PanelWindow,GadgetList,(UWORD)-1);

			PopWindow();

			CloseWindow(PanelWindow);
		}

		FreeGadgets(GadgetList);
	}

	return(Success);
}

	/* xpr_setserial(LONG Status):
	 *
	 *	Set/read the serial status (parameters).
	 */

LONG __saveds __asm
xpr_setserial(register __d0 LONG Status)
{
	if(WriteRequest)
	{
		STATIC LONG XprBauds[12] =
		{
			   110,
			   300,
			  1200,
			  2400,
			  4800,
			  9600,
			 19200,
			 31250,
			 38400,
			 57600,
			 76800,
			115200
		};

		LONG Return,i;

		WriteRequest -> IOSer . io_Command = SDCMD_QUERY;

		DoIO(WriteRequest);

		Return = WriteRequest -> io_SerFlags & 0xFF;

		if(WriteRequest -> io_ExtFlags & SEXTF_MSPON)
			Return |= ST_PARTYMARKON;

		if(WriteRequest -> io_ExtFlags & SEXTF_MARK)
			Return |= ST_PARTYMARK;

		if(WriteRequest -> io_StopBits == 2)
			Return |= ST_2BITS;

		if(WriteRequest -> io_ReadLen == 7)
			Return |= ST_READ7;

		if(WriteRequest -> io_WriteLen == 7)
			Return |= ST_WRITE7;

		for(i = 0 ; i < 12 ; i++)
		{
			if(XprBauds[i] >= WriteRequest -> io_Baud)
			{
				Return |= (i << 16);

				break;
			}
		}

			/* `term' supports a number of serial transfer speed rates
			 * not included in the XPR standard. For this reason this
			 * xpr_setserial() implementation will not change the serial
			 * line parameters.
			 */
/*
		if(Status != -1)
		{
			WriteRequest -> io_SerFlags = Status & 0xFF;
			WriteRequest -> io_ExtFlags = 0;

			if(Status & ST_PARTYMARKON)
				WriteRequest -> io_ExtFlags |= SEXTF_MSPON;

			if(Status & ST_PARTYMARK)
				WriteRequest -> io_ExtFlags |= SEXTF_MARK;

			if(Status & ST_2BITS)
				WriteRequest -> io_StopBits = 2;
			else
				WriteRequest -> io_StopBits = 1;

			if(Status & ST_READ7)
				WriteRequest -> io_ReadLen = 7;
			else
				WriteRequest -> io_ReadLen = 8;

			if(Status & ST_WRITE7)
				WriteRequest -> io_WriteLen = 7;
			else
				WriteRequest -> io_WriteLen = 8;

			ReadRequest -> io_Baud		= WriteRequest -> io_Baud;
			ReadRequest -> io_SerFlags	= WriteRequest -> io_SerFlags;
			ReadRequest -> io_ExtFlags	= WriteRequest -> io_ExtFlags;
			ReadRequest -> io_StopBits	= WriteRequest -> io_StopBits;
			ReadRequest -> io_ReadLen	= WriteRequest -> io_ReadLen;
			ReadRequest -> io_WriteLen	= WriteRequest -> io_WriteLen;

			WriteRequest -> IOSer . io_Command = SDCMD_SETPARAMS;

			DoIO(WriteRequest);

			ReadRequest -> IOSer . io_Command = SDCMD_SETPARAMS;

			DoIO(ReadRequest);
		}
*/
		return(Return);
	}
	else
		return(-1);
}

	/* xpr_ffirst(STRPTR Buffer,STRPTR Pattern):
	 *
	 *	Batch file upload: find the first matching file and return
	 *	its name.
	 */

LONG __saveds __asm
xpr_ffirst(register __a0 STRPTR Buffer,register __a1 STRPTR Pattern)
{
	if(FileTransferInfo)
	{
		FileTransferInfo -> DoneSize	= 0;
		FileTransferInfo -> DoneFiles	= 0;

		FileTransferInfo -> CurrentFile	= (struct FileTransferNode *)FileTransferInfo -> FileList . mlh_Head;
		FileTransferInfo -> CurrentSize	= FileTransferInfo -> CurrentFile -> Size;

		strcpy(Buffer,FileTransferInfo -> CurrentFile -> Name);

		return(1);
	}
	else
		return(0);
}

	/* xpr_fnext(LONG OldState,STRPTR Buffer,STRPTR Pattern):
	 *
	 *	Batch file upload: find the next matching file
	 *	- if any - and return its name.
	 */

LONG __saveds __asm
xpr_fnext(register __d0 LONG OldState,register __a0 STRPTR Buffer,register __a1 STRPTR Pattern)
{
	if(FileTransferInfo)
	{
		if(FileTransferInfo -> CurrentFile -> Node . mln_Succ -> mln_Succ)
		{
			FileTransferInfo -> DoneSize	+= FileTransferInfo -> CurrentSize;
			FileTransferInfo -> DoneFiles	+= 1;

			FileTransferInfo -> CurrentFile  = (struct FileTransferNode *)FileTransferInfo -> CurrentFile -> Node . mln_Succ;
			FileTransferInfo -> CurrentSize  = FileTransferInfo -> CurrentFile -> Size;

			strcpy(Buffer,FileTransferInfo -> CurrentFile -> Name);

			return(1);
		}
	}

	return(0);
}

	/* xpr_finfo(STRPTR FileName,LONG InfoType):
	 *
	 *	Return information on a given file.
	 */

LONG __saveds __asm
xpr_finfo(register __a0 STRPTR FileName,register __d0 LONG InfoType)
{
	struct FileInfoBlock	*FileInfo;
	LONG			 Size;

	switch(InfoType)
	{
			/* Return the file size. */

		case 1:	Size = 0;

			if(FileInfo = (struct FileInfoBlock *)AllocDosObject(DOS_FIB,TAG_DONE))
			{
				UBYTE	RealName[MAX_FILENAME_LENGTH];
				BPTR	FileLock;

				FileName = NewFileName(FileName,RealName);

				if(FileLock = Lock(FileName,ACCESS_READ))
				{
					if(Examine(FileLock,FileInfo))
					{
						if(FileInfo -> fib_DirEntryType < 0)
							Size = FileInfo -> fib_Size;
					}

					UnLock(FileLock);
				}

				FreeDosObject(DOS_FIB,FileInfo);
			}

			return(Size);

			/* Return the file transfer mode. */

		case 2:	return(BinaryTransfer ? 1 : 2);

			/* Ignore the rest. */

		default:return(0);
	}
}

	/* xpr_options(LONG NumOpts,struct xpr_option **Opts):
	 *
	 *	Provide a more polished user interface to set the
	 *	transfer protocol options.
	 */

ULONG __saveds __asm
xpr_options(register __d0 LONG NumOpts,register __a0 struct xpr_option **Opts)
{
	struct Gadget	*GadgetList = NULL;
	struct Gadget	*GadgetArray[34];
	struct Window	*PanelWindow;
	LONG		 i,Count;

	ULONG		 Flags = 0;

		/* We only have 32 bits! */

	if(NumOpts > 32)
		NumOpts = 32;

	if(CreateAllOptionGadgets(&Count,NumOpts,Opts,GadgetArray,&GadgetList,VisualInfo))
	{
		if(PanelWindow = OpenWindowTags(NULL,
			WA_Left,	GetScreenLeft(Window) + (GetScreenWidth(Window) - SZ_GetWindowWidth()) / 2,
			WA_Top,		GetScreenTop(Window) + (GetScreenHeight(Window) - SZ_GetWindowHeight())	/ 2,
			WA_Width,	SZ_GetWindowWidth(),
			WA_Height,	SZ_GetWindowHeight(),

			WA_Activate,	TRUE,
			WA_DragBar,	TRUE,
			WA_DepthGadget,	TRUE,
			WA_CloseGadget,	TRUE,
			WA_RMBTrap,	TRUE,
			WA_CustomScreen,Window -> WScreen,

			WA_IDCMP,	IDCMP_GADGETDOWN | IDCMP_ACTIVEWINDOW | IDCMP_CLOSEWINDOW | IDCMP_RAWKEY | IDCMP_VANILLAKEY | CHECKBOXIDCMP | STRINGIDCMP | INTEGERIDCMP | BUTTONIDCMP,

			WA_Title,	OptionTitle ? OptionTitle : LocaleString(MSG_TERMXPR_TRANSFER_OPTIONS_TXT),
		TAG_DONE))
		{
			struct IntuiMessage	*Massage;
			ULONG			 IClass,Code;
			struct Gadget		*Gadget;
			BYTE			 Terminated = FALSE,
						 CheckFlags = FALSE;

			PushWindow(PanelWindow);

			AddGList(PanelWindow,GadgetList,(UWORD)-1,(UWORD)-1,NULL);
			RefreshGList(GadgetList,PanelWindow,NULL,(UWORD)-1);
			GT_RefreshWindow(PanelWindow,NULL);

			ActiveGadget = NULL;

			ClrSignal(SIG_BREAK);

			while(!Terminated)
			{
				if(Wait(PORTMASK(PanelWindow -> UserPort) | SIG_BREAK) & SIG_BREAK)
					break;

				while(!Terminated && (Massage = (struct IntuiMessage *)GT_GetIMsg(PanelWindow -> UserPort)))
				{
					IClass	= Massage -> Class;
					Code	= Massage -> Code;
					Gadget	= (struct Gadget *)Massage -> IAddress;

					GT_ReplyIMsg(Massage);

					if(IClass == IDCMP_VANILLAKEY)
						KeySelect(GadgetArray,Count,Code,PanelWindow,&Gadget,&IClass,&Code);

					if(IClass == IDCMP_RAWKEY && Code == HELP_CODE)
						DisplayBeep(PanelWindow -> WScreen);

					if(IClass == IDCMP_GADGETDOWN)
					{
						if((Gadget -> GadgetType & GTYP_GTYPEMASK) == GTYP_STRGADGET)
							ActiveGadget = Gadget;
					}

					if(IClass == IDCMP_ACTIVEWINDOW && ActiveGadget)
						ActivateGadget(ActiveGadget,PanelWindow,NULL);

					if(IClass == IDCMP_CLOSEWINDOW)
						Terminated = TRUE;

					if(IClass == IDCMP_GADGETUP)
					{
						switch(Gadget -> GadgetID)
						{
							case GAD_XPROPTIONS_USE:

								Terminated = CheckFlags = TRUE;
								break;

							case GAD_XPROPTIONS_CANCEL:

								Terminated = TRUE;
								break;

							default:

								if(Gadget -> GadgetID < NumOpts)
								{
									if(Opts[Gadget -> GadgetID] -> xpro_type == XPRO_COMMAND)
									{
										Flags |= (1 << Gadget -> GadgetID);

										Terminated = CheckFlags = TRUE;
									}
								}

								break;
						}
					}
				}
			}

			if(CheckFlags)
			{
				for(i = 0 ; i < NumOpts ; i++)
				{
					switch(Opts[i] -> xpro_type)
					{
						case XPRO_BOOLEAN:

							if(SZ_GetChecked(GadgetArray[i]) != GetOptionMode(Opts[i]))
							{
								Flags |= (1 << i);

								if(SZ_GetChecked(GadgetArray[i]))
									strcpy(Opts[i] -> xpro_value,"yes");
								else
									strcpy(Opts[i] -> xpro_value,"no");

								NewOptions = TRUE;
							}

							break;

						case XPRO_COMMPAR:
						case XPRO_LONG:
						case XPRO_STRING:

							if(strcmp(Opts[i] -> xpro_value,SZ_GetString(GadgetArray[i])))
							{
								Flags |= (1 << i);

								strcpy(Opts[i] -> xpro_value,SZ_GetString(GadgetArray[i]));

								NewOptions = TRUE;
							}

							break;
					}
				}
			}
			else
			{
				NewOptions	= FALSE;
				Flags		= NULL;
			}

			ActiveGadget = NULL;

			RemoveGList(PanelWindow,GadgetList,(UWORD)-1);

			PopWindow();

			CloseWindow(PanelWindow);
		}
	}

	FreeGadgets(GadgetList);

	return(Flags);
}

	/* xpr_unlink(STRPTR FileName):
	 *
	 *	Remove (delete) a given file.
	 */

LONG __saveds __asm
xpr_unlink(register __a0 STRPTR FileName)
{
		/* Sorry, but it is far too dangerous to let the protocol
		 * remove any files with path name substitution enabled.
		 * The protocol could accidentally hit the wrong file.
		 */

	if(!Config -> MiscConfig -> OverridePath)
	{
		if(DeleteFile(FileName))
		{
			LogAction(LocaleString(MSG_TERMXPR_LOGMSG_DELETE_FILE_TXT),FilePart(FileName));

			return(0);
		}
	}

	return(-1);
}

	/* xpr_squery():
	 *
	 *	Check how many characters are present in the serial buffer.
	 */

LONG __saveds __asm
xpr_squery()
{
	if(WriteRequest)
	{
		WriteRequest -> IOSer . io_Command = SDCMD_QUERY;

		DoIO(WriteRequest);

			/* Return error if carrier is lost. */

		if((WriteRequest -> io_Status & CIAF_COMCD) && Config -> SerialConfig -> CheckCarrier)
		{
			if(!CarrierMessage)
			{
				CarrierMessage = TRUE;

				AddTransferInfo(LocaleString(MSG_TERMXPR_CARRIER_LOST_TXT));
			}

			Online = FALSE;

			TransferError = TRUE;
		}
		else
			return((LONG)WriteRequest -> IOSer . io_Actual);
	}

	return(-1);
}

	/* xpr_getptr(LONG InfoType):
	 *
	 *	Return a pointer to the term custom screen.
	 */

LONG __saveds __asm
xpr_getptr(register __d0 LONG InfoType)
{
	if(InfoType == 1)
		return((LONG)Window -> WScreen);
	else
		return(-1);
}

	/* xpr_stealopts(STRPTR Prompt,STRPTR Buffer):
	 *
	 *	Steal the contents of the options buffer (replacement
	 *	for xpr_gets).
	 */

LONG __saveds __asm
xpr_stealopts(register __a0 STRPTR Prompt,register __a1 STRPTR Buffer)
{
	if(Buffer)
		strcpy(ProtocolOptsBuffer,Buffer);

	return(1);
}
