#import <FTPManager.h>
#import <ClockView.h>
#import <PercentView.h>
#import <ProsperoVLINK.h>

#import <objc/NXStringTable.h>
#import <appkit/Application.h>
#import <appkit/Matrix.h>
#import <appkit/OpenPanel.h>
#import <appkit/appkit.h>
#import <appkit/nextstd.h>
#import <ctype.h>
#import <fcntl.h>
#import <regex.h>
#import <signal.h>
#import <sys/param.h>
#import <sys/types.h>
#import <sys/stat.h>

#define DEFAULT_DIR "."
#define NIB_PATH "FTPManager.nib"
/* The owner of the application defaults */
extern const char *NeXTArchieOwner;

/* Code returned when modal loop is aborted */
#define ABORT_TRANSFER 1

/* Number of calls to the dirListingTE before we say quit */
#define LISTING_TIMEOUT_COUNT 6	// = 30 seconds

/* Import the error message NXStringTable keys */
#import "errMessages.keys"

/* Ftp command result codes */
#define CONNECTED			220
#define LOGIN_OK 				331
#define PASSWORD_OK 			230
#define CWD_OK 				250
#define CWD_OK2 				200
#define TRANSFER_COMPLETE	226
#define GOODBYE				221
#define DATA_CONNECTION	150
#define PORT_OK				200
/* Ftp error codes I know of, most from looking at ftpd.c source */
#define FTP_ERR0				500
#define FTP_ERR1				502
#define FTP_ERR2				503
#define FTP_ERR3				530
#define FTP_ERR4				550
#define FTP_ERR5				551
#define FTP_ERR6				553
#define FTP_ERR7				421	// Some hosts send this if there are too many users

/* ftpMode flag defines */
#define BEGIN_SESSION			10
#define CONNECT_SUCCESS		11
#define USER_LOGIN			20
#define LOGIN_SUCCESS		21
#define USER_PASSWORD		30
#define PASSWORD_SUCCESS	31
#define REMOTE_CHDIR		40
#define CHDIR_SUCCESS		41
#define GET_TARGET_SIZE		50
#define SIZE_SUCCESS			51
#define FILE_TRANSFER		60
#define TRANSFER_SUCCESS	61
#define FILE_LISTING			62
#define LISTING_SUCCESS		63
#define DISCONNECT			70
#define DISCONNECT_SUCCESS	71
#define FTP_ERROR				100
#define FTP_SELF_ERROR		110

/* Debugging level defines */
#define FTP_RESULT 	4	// Write results from ftp program
#define FTP_COMMAND	8	// Write command sent to ftp program
#define LOW	10
#define MED	20
#define HIGH	30
#define MEGA	40

/* Ftp output buffers and variables */
static char *ftpFullErrMessage;
static char ftpErrMessage[128];
static char *listingBuffer;
static int listingSize,listingBufferSize;

/* The code number of the last ftp output result */
static int lastFtpResult;
/* Regular expression structs for patterns of interest */
static struct regex *ftpResultExpr;
static struct regex *unreachableNetworkExpr;
static struct regex *transferSizeExpr;
static struct regex *unkownHostExpr;
static struct regex *transferCompleteExpr;

/* Timed entry prototypes */
void FtpDaemon(DPSTimedEntry te, double timeNow, FTPManager *self);
void ListingDaemon(DPSTimedEntry te, double timeNow, FTPManager *self);

/* Import routines which parse the ftp dir command output into DirEntry's. */
#import "ftpParse.c"

@implementation FTPManager

- init
{
	[super init];
	getwd(ftpDirectory);
	return self;
}

- abortTransfer: sender
{
	kill(ftpProcessPID,SIGKILL);
	[NXApp stopModal : ABORT_TRANSFER];

	return self;
}

- displayErrOutput: sender
{
id textField;
	/* Display the error panel and display the last ftp output in
		its textfield */
	textField = [ftpErrViewID docView];
	[textField setText: ftpFullErrMessage];
	[ftpErrPanelID makeKeyAndOrderFront: self];
	return self;
}

- setLocalDir: sender
{
	[self setLocalPath: ftpDirectory file: NULL panel: YES];
	return self;
}

- setLocalPath: (const char *) dirName file: (const char *) fileName panel: (BOOL) display;
{
int saveResult,fileLength;
		
	if(display == YES)
	{
		if(fileName == NULL)
		{	// Set local directory
			if(pwdPanelID == nil)
			{
				pwdPanelID = [OpenPanel new];
				if(pwdPanelID == nil)
				return [self error: SORRY key: OPEN_PANEL_FAILED];
			}
			[pwdPanelID setTitle: "Set Local Directory"];
			saveResult = [pwdPanelID runModalForDirectory:ftpDirectory file:NULL];
			if(saveResult == 1)
			{
				strcpy(ftpDirectory,[pwdPanelID directory]);
				return self;
			}
			return nil;
		}
		else
		{	// Prompt the user for the filename
			if(saveAsPanelID == nil)
			{
				saveAsPanelID = [SavePanel new];
				if(saveAsPanelID == nil)
				return [self error: SORRY key: SAVE_PANEL_FAILED];
			}
			saveResult = [saveAsPanelID  runModalForDirectory: (const char *) ftpDirectory 
						file:	fileName];
			if(saveResult == 1)
			{
				strcpy(ftpDirectory,[saveAsPanelID directory]);
				fileName = strrchr([saveAsPanelID filename],'/');
				fileName ++;
			}
			else
				return nil;
		}
	}
	
	/* Copy the local file name to fileName */
	fileLength = strlen(ftpDirectory) + strlen(fileName) + 1;
	if(localFilePath)
		NX_FREE(localFilePath);
	NX_MALLOC(localFilePath,char, fileLength);
	if(localFilePath == NULL)
		return [self error: ALERT key: LOCALPATH_ALLOC_FAILED];

	sprintf(localFilePath,"%s/%s",ftpDirectory,fileName);

	return self;
}

/* Write a command line to the ftp program */
- writeFtp : sender
{
	[self writeFtpCommand: "%s", [sender stringValue]];
	return self;
}
- writeFtpCommand : (char *) formatString, ...
{
va_list  ap;

	va_start(ap, formatString);
	vfprintf(stdout, formatString,ap);
if(ftpDebugLevel > FTP_COMMAND)
	vfprintf(errFile,formatString,ap);
	va_end(ap);

	return self;
}

/* Cover methods for the full blown runModal::::: */
- runModal: (const char *) hostName get: (const char *) targetPathName
{
	[self runModal: hostName get: targetPathName anon: YES
		write: NO binary: YES vlink: nil];
	return self;
}
- runModal: (const char *) hostName get: (const char *) targetPathName dir: (BOOL) listing
{
	[self runModal: hostName get: targetPathName anon: YES
		write: NO binary: YES vlink: nil];
	return self;
}
- runModal: (const char *) hostName get: (const char *) targetPathName anon: (BOOL) anonymous
{
	[self runModal: hostName get: targetPathName anon: anonymous
		write: NO binary: YES vlink: nil];
	return self;
}

- runModal: (const char *) hostName get: (const char *) targetPathName anon: (BOOL) anonymous
	write: (BOOL) overWrite binary: (BOOL) mode vlink: dirVLINK
{
/* pty channels as returned from Stevens pty code */
int masterChannel,slaveChannel;
int pty_master(), pty_slave();
int currentFlags,lastSize, modalStatus = 0;
float currentSize,percent;
NXModalSession theSession;
struct stat transferStatus;
char localHostname[64],*localUsername;
char *remoteDirectory,*remoteFilename,*tmpBuffer,sizeBuffer[16];

	/* Ignore the anonymous flag for now */

	/* Load my nib panel */
	if(ftpPanelID == nil)
	{
		if([NXApp loadNibSection: NIB_PATH owner: self withNames: NO] == nil)
			[self error: ALERT key: FTP_PANEL_FAILED];
	}

	/* Set the errFile to stderr */
	errFile = stderr;
	setvbuf(errFile,NULL,_IOLBF,0);
	/* Read the FtpDebugLevel defaults setting */
	ftpDebugLevel = atoi(NXGetDefaultValue(NeXTArchieOwner, "FtpDebugLevel"));

	/* Write a message logging the start of the ftp session */
	[self debug: 0, "Starting ftp session: UNIX clock = %d\n",time( (long *) 0)];

	/* Open the master pty and set raw mode */
	masterChannel = pty_master();
	currentFlags =  fcntl(masterChannel,F_GETFL,0);
	fcntl(masterChannel,F_SETFL,currentFlags | O_NDELAY);
	/* Fork the child process */
	if( (ftpProcessPID = vfork()) < 0)
		return [self error: ALERT key: FTP_FORK_FAILED];

	/* Reset the parents stdin and stdout to the pty channel */
	if(ftpProcessPID > 0)
	{
		close(0);
		close(1);
		dup(masterChannel);	// Set this as stdin
		dup(masterChannel);	// & to stdout
		/* Setup line oriented communication */
		setvbuf(stdin,NULL,_IOLBF,0);
	}
	else
	{	/* The child process opens the slave pty */
	int controlTerm;
		slaveChannel = pty_slave(masterChannel);
		if(slaveChannel < 0)
		{
			fprintf(stderr,"Failed to open pty slave\n");
			_exit(1);
		}
		close(masterChannel);
		/* Get rid of the controlling terminal so that password prompts
			are redirected to the pty. */
		if( (controlTerm = open("/dev/tty", O_RDWR)) >= 0 )
		{
			ioctl(controlTerm,TIOCNOTTY,NULL);
			close(controlTerm);
		}
[self debug: LOW, "Starting ftp...\n"];

		/* Set std everything to the slave pty */
		close(0);
		close(1);
		close(2);
		dup(slaveChannel);	// Set this as stdin
		dup(slaveChannel);	// & stdout
		dup(slaveChannel);	// & stderr
		/* Start the ftp program */
		execlp("ftp","ftp", "-n", hostName,NULL);
		fprintf(stderr,"Failed to exec ftp\n");
		_exit(1);
	}

	/* Determine the remote and local file names */
	if(dirVLINK == nil)
	{
		remoteFilename = strrchr(targetPathName,'/');  remoteFilename ++;
	[self debug: LOW, "remoteFilename: %s\n", remoteFilename];
		[self setLocalPath: ftpDirectory file: remoteFilename panel: NO];
	[self debug: LOW, "localFilePath: %s\n", localFilePath];
		
		NX_MALLOC(tmpBuffer,char,strlen(targetPathName)+1);
		if(tmpBuffer == NULL)
		{
			kill(ftpProcessPID,SIGKILL);
			return [self error: ALERT key: REMOTE_ALLOC_FAILED];
		}
		strcpy(tmpBuffer, targetPathName);
		remoteDirectory = tmpBuffer;
		remoteDirectory[strlen(targetPathName) - strlen(remoteFilename)] = '\0';
	[self debug: LOW, "remoteDirectory: %s\n", remoteDirectory];
	}
	else
	{
		remoteDirectory = targetPathName;
		remoteFilename = NULL;
		localFilePath = NULL;
		tmpBuffer = NULL;
	}

	/* Add a timed entry for reading & displaying child's output */
	ftpMode = BEGIN_SESSION;
	ftpDaemonTE = DPSAddTimedEntry(1.0, FtpDaemon,self,
						NX_MODALRESPTHRESHOLD);

	/* Initialize some variables */
	transferComplete = NO;
	lastSize = 0;
	lastFtpResult = 0;
	listingTEStarted = NO;
	listingTECalls = 0;
	dirListingTE = (DPSTimedEntry) 0;
	ftpResultExpr = re_compile("[1-5][0-9][0-9] ",0);
	unreachableNetworkExpr = re_compile("Network.*unreachable",1);
	transferSizeExpr = re_compile("[0-9]* bytes",1);
	unkownHostExpr = re_compile("Unknown.*Host",1);
	transferCompleteExpr =  re_compile("226.*Transfer",1);

	/* Here we go */
[self debug: MEGA, "FTPM: Begging modal session\n"];
	[statusBoxID removeFromSuperview];
	[timerViewID startMinSecTimer: self];
	[hostnameFieldID setStringValue: hostName];
	[messageFieldID setStringValue: "Connecting to remote host..."];
	[ftpPanelID display];
	[NXApp beginModalSession: &theSession for: ftpPanelID];
	while(transferComplete == NO)
	{
		modalStatus = [NXApp runModalSession: &theSession];
		if(modalStatus != NX_RUNCONTINUES)
			break;
		switch(ftpMode)
		{
			case CONNECT_SUCCESS :
				ftpMode = USER_LOGIN;
[self debug: MED, "FTPM: USER_LOGIN\n"];
				[messageFieldID setStringValue: "Connected to remote host..."];
				/* Initiate anonymous login */
				[self writeFtpCommand: "user anonymous\n"];
				break;
			case LOGIN_SUCCESS :
				ftpMode = USER_PASSWORD;
[self debug: MED, "FTPM: USER_PASSWORD\n"];
				/* Send user@hostname as password */
				gethostname(localHostname,63);
				localHostname[63] = '\0';
				localUsername = (char *) NXUserName();
				[self writeFtpCommand: "%s@%s\n",localUsername,localHostname];
				break;
			case PASSWORD_SUCCESS :
				ftpMode = REMOTE_CHDIR;
[self debug: MED, "FTPM: REMOTE_CHDIR\n"];
				[messageFieldID setStringValue: "Anonymous login successful..."];
				[self writeFtpCommand: "cd %s\n", remoteDirectory];
				if(mode == YES)
					[self writeFtpCommand: "binary\n"];
				else
					[self writeFtpCommand: "ascii\n"];
				break;
			case CHDIR_SUCCESS :
				if(dirVLINK == nil)
				{
						ftpMode = FILE_TRANSFER;
	[self debug: MED, "FTPM: FILE_TRANSFER\n"];
					[messageFieldID setStringValue: "Retrieving remote file..."];
					if( stat(localFilePath,&transferStatus) != -1)
					{
						if(overWrite == NO)
						{
						int alertRslt = NXRunAlertPanel(NULL,
							"The local file: %s exists.  Overwrite?",
									"Yes","Save As...","Cancel",remoteFilename);
							switch(alertRslt)
							{
								case NX_ALERTDEFAULT :
	[self debug: HIGH, "FTPM: AlertPanel = NX_ALERTDEFAULT\n"];
									truncate(localFilePath,0);
									break;
								case NX_ALERTALTERNATE :
	[self debug: HIGH, "FTPM: AlertPanel = NX_ALERTALTERNATE\n"];
									if([self setLocalPath: ftpDirectory file: remoteFilename 
										panel: YES] == nil)
									{
										[self abortTransfer: self];
										break;
									}
									/* In case the user changed their mind */
									if( stat(localFilePath,&transferStatus) != -1)
										truncate(localFilePath,0);
									break;
								case NX_ALERTOTHER :
	[self debug: HIGH, "FTPM: AlertPanel = NX_ALERTOTHER\n"];
								case NX_ALERTERROR :
									[self abortTransfer: self];
									break;
								}
						}
					}
					else
						truncate(localFilePath,0);
					[self writeFtpCommand: "get %s %s\n", remoteFilename, localFilePath];
					[sizeFieldID setStringValue: "0"];
					[statusViewID displayPercent: 0.0];
					[[[hostnameFieldID window] contentView] addSubview: statusBoxID];
					[ftpPanelID display];
				}
				/* Else get the output from the dir command */
				else
				{
					[messageFieldID setStringValue: "Retrieving remote directory listing..."];
					listingSize = 0;
					listingBufferSize = 1024;
					NX_MALLOC(listingBuffer,char,listingBufferSize);
					if(listingBuffer == NULL)
						return [self error: ALERT key: DIRLISTING_ALLOC_FAILED];
					listingBuffer[0] = '\0';
					ftpMode = FILE_LISTING;
					[self writeFtpCommand: "dir\n"];
				}
				break;
			case TRANSFER_SUCCESS :
			case LISTING_SUCCESS :
				ftpMode = DISCONNECT;
[self debug: MED, "FTPM: DISCONNECT\n"];
				if(dirVLINK == nil)
				{
					[messageFieldID setStringValue: "Transfer complete."];
					[statusViewID displayPercent: 1.0];
				}
				else
				{
					DPSRemoveTimedEntry(dirListingTE);
					dirListingTE = (DPSTimedEntry) 0;
					[messageFieldID setStringValue: "Listing complete."];
				}
				[self writeFtpCommand: "bye\n"];
				break;
			case DISCONNECT_SUCCESS:
			case FTP_ERROR :
				transferComplete = YES;
				break;
			default:
				break;
		}

		/* Compute the local files size and update the percent completion */
		if(ftpMode >= FILE_TRANSFER && dirVLINK == nil)
		{
			if( stat(localFilePath,&transferStatus) != -1)
			{
			BOOL update;
				currentSize = transferStatus.st_size;
				sprintf(sizeBuffer,"%d bytes",(int)currentSize);
				[sizeFieldID setStringValue: sizeBuffer];
				if(targetFileSize > 0)
				{
					percent = currentSize / (float)targetFileSize;
[self debug: MEGA,"FTPM: File transfer percent = %f\n",percent];
					if(currentSize > lastSize)
					{
						lastSize = currentSize;
						update = NO;
						if(percent <= 1.0)
						{	// Just in case the size was parsed incorrectly
							if([statusViewID displayPercent: percent] != nil)
								update = YES;
						}
[self conditionalDebug: update : HIGH, "size = %d; percent = %f\n", lastSize, percent];
					}
				}
			}
		}
	}
	/* Remove the timed entry */
	DPSRemoveTimedEntry(ftpDaemonTE);
	if(dirListingTE)
		DPSRemoveTimedEntry(dirListingTE);
	[timerViewID endTimer: self];

	/* Set the indicators to their final state */
	if(ftpMode != FTP_ERROR && dirVLINK == nil)
	{
		if(modalStatus != ABORT_TRANSFER)
		{
			if( targetFileSize > 0)
			{
				sprintf(sizeBuffer,"%d bytes", targetFileSize);
				[sizeFieldID setStringValue: sizeBuffer];
			}
		}
		else if(modalStatus == ABORT_TRANSFER)
		{
			[statusBoxID removeFromSuperview];
			[messageFieldID setStringValue: "Transfer Aborted."];
			[ftpPanelID display];
			/* Remove the trasfer file */
			unlink(localFilePath);
		}
	}

	/* End the modal session */	
	[NXApp endModalSession: &theSession];
	/* Order out the panel after a slight delay to make sure the final
		state was indicated */
	if(dirVLINK == nil)
		[ftpPanelID perform: @selector(orderOut:) with: self 
			afterDelay: 2000 cancelPrevious: NO];
	else
		[ftpPanelID orderOut: self];

	/* Display an alert if the FTP_ERROR mode is set */
	if(ftpMode == FTP_ERROR)
	{
	int alertRslt;
		[self writeFtpCommand: "bye\n"];
		alertRslt = NXRunAlertPanel(NULL,"An ftp error occurred, the error was:\n\t[%s]",
			"Ok","Info...",NULL,ftpErrMessage);
		if(alertRslt == NX_ALERTALTERNATE)
			[self displayErrOutput: self];
		/* Remove the trasfer file. Maybe I should prompt the user? */
		unlink(localFilePath);
	}
	else if(dirVLINK != nil && modalStatus != ABORT_TRANSFER)
	{
		/* Parse the listingBuffer to generate the DirEntry linked list */
		if( [dirVLINK setListing: ParseBuffer(listingBuffer)] == nil)
			[self error: ALERT key: SETLISTING_FAILED];
	}

[self debug: MEGA,"FTPM: Done.\n"];

	NX_FREE(tmpBuffer);
	NX_FREE(listingBuffer);
	NX_FREE(ftpFullErrMessage);
	NX_FREE(localFilePath);
	localFilePath = NULL;
	ftpFullErrMessage = NULL;
	listingBuffer = NULL;

	return self;
}

void FtpDaemon(DPSTimedEntry te, double timeNow, FTPManager *self)
{
int n;
char ftpRsltBuffer[1024],*bufferPtr,*tmpPtr,_150_RsltBuffer[128];
BOOL condition;
BOOL ValidFTPCode(int code);
int GetSize(char *buffer,FTPManager *self);
char *FTPResultCode(char *buffer,int *result);
static double byeTimeOut;

	/* Don't care about anything after the ``221 Goodbye..'' code */
 	if(self->transferComplete == YES)
		return;

	/* Read the ftp channel and display it in status TextField */
	n = read(0, ftpRsltBuffer,1023);
	if(n == -1) n ++;
	ftpRsltBuffer[n] = '\0';
	_150_RsltBuffer[0] = '\0';
	condition = (n > 0);
[self conditionalDebug: condition : FTP_RESULT, "FTPD: %s\n", ftpRsltBuffer];

	/* Check the output for the ``ftp: connect: Network is unreachable'' message,
		and the ``... unkown host'' message since ftp does not return an
		error code(or any) for this */
	if(re_match(ftpRsltBuffer, unreachableNetworkExpr) == 1)
	{
		strcpy(ftpErrMessage,"Ftp claims this network is unreachable");
		if(ftpFullErrMessage != NULL)
			NX_FREE(ftpFullErrMessage);
		NX_MALLOC(ftpFullErrMessage,char,strlen(ftpRsltBuffer)+1);
		if(ftpFullErrMessage == NULL)
		{
			[self error: ALERT key: ERRBUFFER_ALLOC_FAILED];
			// User chose not to abort
			self->ftpMode = FTP_SELF_ERROR;
			return;
		}
		strcpy(ftpFullErrMessage, ftpRsltBuffer);
		self->ftpMode = FTP_ERROR;
		return;
	}
	else if(re_match(ftpRsltBuffer, unkownHostExpr) == 1)
	{
		sprintf(ftpErrMessage,"Ftp claims the host:%s is unkown",
			[self->hostnameFieldID stringValue]);
		if(ftpFullErrMessage != NULL)
			NX_FREE(ftpFullErrMessage);
		NX_MALLOC(ftpFullErrMessage,char,strlen(ftpRsltBuffer)+1);
		if(ftpFullErrMessage == NULL)
		{
			[self error: ALERT key: ERRBUFFER_ALLOC_FAILED];
			// User chose not to abort
			self->ftpMode = FTP_SELF_ERROR;
			return;
		}
		strcpy(ftpFullErrMessage, ftpRsltBuffer);
		self->ftpMode = FTP_ERROR;
		return;
	}

	/* See if we are looking for a directory listing, and if so,
		copy the output  to the listing buffer.  Also, the only ftp result
		code we are looking for is the ``226 Transfer complete'' message.
		The simple approach used in the following while loop will match too many
		things, so we do the regex matching here.  One problem with this is that if
		the transfer does not complete, we can get stuck here so we start
		another timed entry to make sure that the size of the listingBuffer continues
		to grow.  All of these special case situations are beginning to make my parsing
		approach rather inelegant.  I need to figure a more robust, yet simple method
		of parsing the ftp output. */
	if(self->ftpMode == FILE_LISTING && n > 0)
	{
		/* Start the listing TE if not already running */
		if(self->listingTEStarted == NO)
		{
			self->dirListingTE  = DPSAddTimedEntry(5.0, ListingDaemon,self,
						NX_MODALRESPTHRESHOLD);
			self->listingTEStarted = YES;
		}

		/* Check to see that the listing buffer size is large enough */
		listingSize += n;
		if(listingSize >= listingBufferSize)
		{
			listingBufferSize = listingSize + 512;
			NX_REALLOC(listingBuffer,char, listingBufferSize);
			if(listingBuffer == NULL)
			{
				[self error: ALERT key: DIRLISTING_REALLOC_FAILED];
				// User chose not to abort
				self->ftpMode = FTP_SELF_ERROR;
				return;
			}
		}
		strcat(listingBuffer,ftpRsltBuffer);
		
		/* Check for the transfer complete code */
		if(re_match(ftpRsltBuffer,transferCompleteExpr) == 1)
			lastFtpResult = 226;

		/* Bypass the normal ftp code checking */
		ftpRsltBuffer[0] = '\0';
	}

	/* Scan the ftpRsltBuffer for the ftp result numbers */
	bufferPtr = ftpRsltBuffer;
	while( (tmpPtr = FTPResultCode(bufferPtr,&lastFtpResult)) != NULL)
	{
		bufferPtr = NULL;
		/* Check for an error code and save the ftp output if there was one */
		if(lastFtpResult >= 500)
		{
			sscanf((tmpPtr-4),"%[^\n]",ftpErrMessage);
			if(ftpFullErrMessage != NULL)
				NX_FREE(ftpFullErrMessage);
			NX_MALLOC(ftpFullErrMessage,char,strlen(ftpRsltBuffer)+1);
			if(ftpFullErrMessage == NULL)
			{
				[self error: ALERT key: ERRBUFFER_ALLOC_FAILED];
				// User chose not to abort
				self->ftpMode = FTP_SELF_ERROR;
				return;
			}
			strcpy(ftpFullErrMessage, ftpRsltBuffer);
			self->ftpMode = FTP_ERROR;
			return;
		}
	
		/* See if this is the ``150 Opening..'' command that has the file size */
		if(self->ftpMode == FILE_TRANSFER && lastFtpResult == DATA_CONNECTION)
		{
			if(_150_RsltBuffer[0] == '\0')	// True if not already filled
					sscanf(tmpPtr,"%[^\n]\n",_150_RsltBuffer);
[self debug: MED,"FTPD: _150_RsltBuffer: %s\n", _150_RsltBuffer];
		}

[self debug: HIGH, "FTPD_loop: lastFtpResult = %d\n", lastFtpResult];
		condition = (lastFtpResult != 0);
[self conditionalDebug: condition : MED, "FTPD_loop: lastFtpResult = %d\n", lastFtpResult];
	}

[self debug: MEGA, "FTPD_last: lastFtpResult = %d\n", lastFtpResult];

[self debug: MEGA,"FTPD: ftpMode = %d\n", self->ftpMode];
	switch(self->ftpMode)
	{
		case BEGIN_SESSION :
			if(lastFtpResult == CONNECTED)
			{
				lastFtpResult = 0;
				self->ftpMode = CONNECT_SUCCESS;
[self debug: MED, "FTPD: CONNECT_SUCCESS \n"];
			}
			break;
		case USER_LOGIN :
			if(lastFtpResult == LOGIN_OK)
			{
				lastFtpResult = 0;
				self->ftpMode = LOGIN_SUCCESS;
[self debug: MED, "FTPD: LOGIN_SUCCESS \n"];
			}
			break;
		case USER_PASSWORD :
			if(lastFtpResult == PASSWORD_OK)
			{
				lastFtpResult = 0;
				self->ftpMode = PASSWORD_SUCCESS;
[self debug: MED, "FTPD: PASSWORD_SUCCESS \n"];
			}
			break;
		case REMOTE_CHDIR :
			if(lastFtpResult == CWD_OK || lastFtpResult == CWD_OK2)
			{
				lastFtpResult = 0;
				self->ftpMode = CHDIR_SUCCESS;
[self debug: MED, "FTPD: CHDIR_SUCCESS \n"];
			}
			break;
		case FILE_TRANSFER :
			if(lastFtpResult == DATA_CONNECTION && _150_RsltBuffer[0] != '\0')
			{
				/* Parse the dir to get the file size */
				self->targetFileSize = GetSize(_150_RsltBuffer,self);
[self debug: MED, "FTPD: targetSize = %d \n",self->targetFileSize];
			}
			else if(lastFtpResult == TRANSFER_COMPLETE)
			{
				lastFtpResult = 0;
				self->ftpMode = TRANSFER_SUCCESS;
[self debug: MED, "FTPD: TRANSFER_SUCCESS \n"];
			}
			break;
		case FILE_LISTING :
			if(lastFtpResult == TRANSFER_COMPLETE)
			{
				lastFtpResult = 0;
				self->ftpMode = LISTING_SUCCESS;
				/* Prepare the disconnect time out variable */
				byeTimeOut = timeNow;
			}
			break;
		case DISCONNECT :
			if(lastFtpResult == GOODBYE || (timeNow - byeTimeOut) > 5.0 )
			{
				lastFtpResult = 0;
				self->ftpMode = DISCONNECT_SUCCESS;
[self debug: MED, "FTPD: DISCONNECT_SUCCESS \n"];
				self->transferComplete = YES;
[self debug: HIGH, "FTPD: Set transferComplete\n"];
			}
			break;
	}
}

char *FTPResultCode(char *ftpRsltBuffer,int *result)
{
int ftpCode;
char codeString[5];
static char *searchBuffer;
BOOL ValidFTPCode(int code);

	/* Go through the buffer looking for patterns "[1-5][0-9][0-9] " */
	if(ftpRsltBuffer != NULL && ftpRsltBuffer[0] != '\0')
		searchBuffer = ftpRsltBuffer;
	else if(ftpRsltBuffer != NULL)
		return NULL;

	if(re_match(searchBuffer, ftpResultExpr) == 1)
	{
		/* Move the search pointer past the code string */
		searchBuffer = ftpResultExpr->start + 4;
		strncpy(codeString, ftpResultExpr->start,4);
		codeString[4] = '\0';
		ftpCode = atoi(codeString);
		/* Check the code number */
		if(ValidFTPCode(ftpCode) == YES)
		{
			*result = ftpCode;
			return searchBuffer;
		}
		else
			return FTPResultCode(NULL,result);
	}
	searchBuffer = NULL;
	return searchBuffer;
}

BOOL ValidFTPCode(int code)
{
	/* Check this code against those I am interested in */
	switch(code)
	{
		case CONNECTED:
		case LOGIN_OK:
		case PASSWORD_OK:
		case CWD_OK:
		case TRANSFER_COMPLETE:
		case GOODBYE:
		case DATA_CONNECTION:
		case PORT_OK:
			return YES;
			break;
		case FTP_ERR0:
		case FTP_ERR1:
		case FTP_ERR2:
		case FTP_ERR3:
		case FTP_ERR4:
		case FTP_ERR5:
		case FTP_ERR6:
		case FTP_ERR7:
			return YES;
		default:
			break;
	}
	return NO;
}

/* Parse the ``150 ...'' output for the size */
int GetSize(char *buffer,FTPManager *self)
{
int size = 0;
char tmpBuffer[32];

[self debug: HIGH,"Begin size parsing\n"];
	/* Find the end of the line */
	if( re_match(buffer, transferSizeExpr) != 1)
		return 0;
	sscanf(transferSizeExpr->start,"%[0-9] ",tmpBuffer);
[self debug: MED,"(%s bytes)\n",tmpBuffer];
	size = atoi(tmpBuffer);
	return size;
}

/* The proc which tries to determine if a dir command fails to complete */
void ListingDaemon(DPSTimedEntry te, double timeNow, FTPManager *self)
{
static int lastListingSize = 0;
	if(listingBufferSize != lastListingSize)
		lastListingSize = listingBufferSize;
	else
		self->listingTECalls ++;
	
	if(self->listingTECalls >= LISTING_TIMEOUT_COUNT)
	{
		/* Set the ftpFullErrMessage ptr to the incomplete dir listing
			and set the ftpErrMessage */
		strcpy(ftpErrMessage,"ftp ``dir'' command timed out");
		if(ftpFullErrMessage != NULL)
			NX_FREE(ftpFullErrMessage);
		ftpFullErrMessage = listingBuffer;
		listingBuffer = NULL;
		self->ftpMode = FTP_ERROR;
	}
}

/* Report the error associated with the keyString and depending on
	the continue depending on the severity */
- error:(int) severity key:(const char *) keyString
{
const char *errMessage;
const char *replyRequest;

int alertResult;

	errMessage = [errStringTable valueForStringKey: keyString];
	switch(severity)
	{
		case SORRY :
			replyRequest = [errStringTable valueForStringKey: REPLY_REQUEST2];
			break;
		case ALERT :
			replyRequest = [errStringTable valueForStringKey: REPLY_REQUEST1];
			break;
		case SEVERE :
		default:
			replyRequest = [errStringTable valueForStringKey: REPLY_REQUEST0];
			break;
	}

	if(severity >= SEVERE)
		alertResult = NXRunAlertPanel("Error","%s\n\t%s","Quit",NULL,NULL,
			errMessage,replyRequest);
	else
		alertResult = NXRunAlertPanel("Error","%s\n\t%s","Ok","Quit",NULL,
			errMessage,replyRequest);
	if(severity >= SEVERE || alertResult == NX_ALERTALTERNATE)
		[NXApp terminate: self];
	
	return self;
}

/* My debugging methods */
#import <stdarg.h>

- debug: (int) debugLevel, ...
{
va_list  ap;
char *format;

	// Only those calls with levels less than the current debugging level are active
	if(debugLevel > ftpDebugLevel)
		return self;

	va_start(ap, debugLevel);
	format = va_arg(ap,char *);
	vfprintf(errFile,format,ap);
	va_end(ap);
	fflush(errFile);

	return self;
}

- conditionalDebug: (BOOL) condition : (int) debugLevel, ...
{
va_list  ap;
char *format;

	if(condition == NO)
		return self;
	if(debugLevel > ftpDebugLevel)
		return self;

	va_start(ap, debugLevel);
	format = va_arg(ap,char *);
	vfprintf(errFile,format,ap);
	va_end(ap);
	fflush(errFile);

	return self;
}

/* The sender must implement the [[sender selectedCell] tag] */
- setDebugLevel: sender
{
	if([sender respondsTo: @selector(selectedCell)])
		ftpDebugLevel = [[sender selectedCell] tag];
	else
		ftpDebugLevel = 0;	// Disable debugging
	return self;
}

- setErrFile: (FILE *) err
{
	errFile = err;
	return self;
}

/* Unimplemented routines */
- runBatch: (const char *) hostName get: (const char *) targetPathName
{
	[self notImplemented: _cmd];
	return self;
}
- runBatch: (const char *) hostName get: (const char *) targetPathName priority: (int) priority
{
	[self notImplemented: _cmd];
	return self;
}

@end
