/*
 * Copyright (c) 1991 by the University of Washington
 *
 * For copying and distribution information, please see the file
 * <uw-copyright.h>.
 *
 * Written by Clifford Neuman (bcn@isi.edu) with changes by
 *            Brendan Kehoe (brendan@cs.widener.edu) and
 *            George Ferguson (ferguson@cs.rochester.edu).
 */
#include <uw-copyright.h>

#import "ArchieServer.h"
#import "MyBrowserCell.h"
#import "ProsperoVLINK.h"
#import <ClockView.h>
#import <CalendarView.h>
#import <FTPManager.h>

#import <objc/HashTable.h>
#import <objc/List.h>
#import <objc/NXStringTable.h>
#import <objc/Storage.h>
#import <appkit/Application.h>
#import <appkit/ButtonCell.h>
#import <appkit/Matrix.h>
#import <appkit/NXBrowser.h>
#import <appkit/SavePanel.h>
#import <appkit/appkit.h>
#import <NXCType.h>
#import <defaults.h>
#import <stdio.h>
#import <sys/time.h>
#import <perrno.h>
#import <rdgram.h>
#import <archie.h>
#import <mach.h>
#import <fcntl.h>
#import <signal.h>

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

/* The Prospero debugging level */
int	pfs_debug;

/* Globals defined in dirsend2.c used to communicate the query status */
extern char dirsendStatusBuffer[128];
char statusBuffer[128];

#define QUERY_ABORT 1
int queryActiveFlag;		// A flag used to indicate that the query thread is active
void QueryThreadProc(ArchieServer *self);

/* The query string sent to the query_archie() procedure */
const char *searchString;

/* Sort enums */
enum {ByName,ByDate,NoSort};
/* Niceness level enums */
enum {MinNice = 0,LowNice = 500,HighNice = 10000,MaxNice = 32765};

/* Defaults stuff */
/* The owner of the application defaults */
const char *NeXTArchieOwner = "NeXTArchie_SMS";
enum {QueryType,MaxReplies,Offset,SortType,
	ExactFlag,NiceLevel,Hostname,HostTag,DebugLevel,FtpDebugLevel}; 
/* A stringification macro */
#define DefaultString(STR) #STR
/* A macro for retriveing default values */
#define GetDefault(STR) NXGetDefaultValue(NeXTArchieOwner, #STR)
/* A flag indicating if the pref panel widgets are in sync with the defaults */
BOOL resetPrefPanel;
/* The virgin defaults */
static struct _NXDefault *NextArchieDefaults;
#define _QueryType	"C"
#define _MaxReplies	"100"
#define _Offset		"0"
#define _SortType	"0"
#define _ExactFlag	"YES"
#define _NiceLevel	"0"
#define _Hostname	"archie.mcgill.ca"
#define _HostTag	"0"
#define _DebugLevel	"2"
#define _FtpDebugLevel "0"

/* The title for the custom host radio button */
#define CUSTOM_TITLE "Custom host"
#define CUSTOM_TAG 7

@implementation ArchieServer

/* Initialization */
+ initialize
{
int i;
	/* Register the defaults for the preference items */
	NextArchieDefaults = (struct _NXDefault *) malloc(11*sizeof(struct _NXDefault));
	for(i = 0; i < 10; i ++)
	{
		NextArchieDefaults[i].name = (char *) malloc(16);
		NextArchieDefaults[i].value = (char *) malloc(32);
	}
	NextArchieDefaults[10].name = NULL;
	NextArchieDefaults[10].value = NULL;
	strcpy(NextArchieDefaults[QueryType].name, DefaultString(QueryType));
	strcpy(NextArchieDefaults[MaxReplies].name, DefaultString(MaxReplies));
	strcpy(NextArchieDefaults[Offset].name, DefaultString(Offset));
	strcpy(NextArchieDefaults[SortType].name, DefaultString(SortType));
	strcpy(NextArchieDefaults[ExactFlag].name, DefaultString(ExactFlag));
	strcpy(NextArchieDefaults[NiceLevel].name, DefaultString(NiceLevel));
	strcpy(NextArchieDefaults[Hostname].name, DefaultString(Hostname));
	strcpy(NextArchieDefaults[HostTag].name, DefaultString(HostTag));
	strcpy(NextArchieDefaults[DebugLevel].name, DefaultString(DebugLevel));
	strcpy(NextArchieDefaults[FtpDebugLevel].name, DefaultString(FtpDebugLevel));
	strcpy(NextArchieDefaults[QueryType].value, _QueryType);
	strcpy(NextArchieDefaults[MaxReplies].value, _MaxReplies);
	strcpy(NextArchieDefaults[Offset].value, _Offset);
	strcpy(NextArchieDefaults[SortType].value, _SortType);
	strcpy(NextArchieDefaults[ExactFlag].value, _ExactFlag);
	strcpy(NextArchieDefaults[NiceLevel].value, _NiceLevel);
	strcpy(NextArchieDefaults[Hostname].value, _Hostname);
	strcpy(NextArchieDefaults[HostTag].value, _HostTag);
	strcpy(NextArchieDefaults[DebugLevel].value, _DebugLevel);
	strcpy(NextArchieDefaults[FtpDebugLevel].value, _FtpDebugLevel);

	NXRegisterDefaults(NeXTArchieOwner, NextArchieDefaults);

	return self;
}

- init
{
const char *defString;

	[super init];

	/* Initialize the host keyed hashtable */
	hostHashTable = [[HashTable alloc] initKeyDesc: "*"];
	if(hostHashTable == nil)
		return [self error: SEVERE key: HOSTHASHTABLE_ALLOC_FAILED];
	hostList = [[List alloc] initCount: 0];
	if(hostList == nil)
		return [self error: SEVERE key: HOSTLIST_ALLOC_FAILED];

	/* Load the default values for the archie options */
	defString = GetDefault(QueryType);
	queryType = defString[0];
	maxReplies = atoi(GetDefault(MaxReplies));
	offset = atoi(GetDefault(Offset));
	sortType = atoi(GetDefault(SortType));
	cmpProc = (sortType == ByName ? AQ_DEFCMP: AQ_INVDATECMP);
	defString = GetDefault(ExactFlag);
	exactFlag = (defString[0] == 'Y' ? YES: NO);
	defString = GetDefault(NiceLevel);
	niceLevel = atoi(defString);
	hostname = GetDefault(Hostname);
	if(hostname == NULL)
		hostname = ARCHIE_HOST;
	hostTag= atoi(GetDefault(HostTag));
	defString = GetDefault(DebugLevel);
	pfs_debug =  debugLevel = atoi(defString);
	resetPrefPanel = YES;

	return self;
}

- archieRequest: sender
{
int threadExitStatus,modalStatus;
char messageBuffer[128];
NXModalSession  theSession;
/* Prototypes taken from pfs/perrmesg.c */
int spwarnmesg(char *buf,char *prefix,int no,char *text);
int sperrmesg(char *buf,char *prefix,int no,char *text);

	/* Get the search string from the interface and query the Archie server */
	searchString = [sender stringValue];
	
	/* Begin a modal session which gives the user the opportunity to
		abort the query */
	[NXApp beginModalSession: &theSession for: queryPanelID];
	[hostnameID setStringValue: hostname];
	strcpy(statusBuffer,"Querying Archie Server...");
	strcpy(dirsendStatusBuffer,"Querying Archie Server...");
	[statusID setStringValue: statusBuffer];
	[queryTimerID startMinSecTimer: self];
	[queryPanelID display];

	/* Create a thread to query the Archie server */
	queryActiveFlag = 1;
	queryThread = cthread_fork((cthread_fn_t) QueryThreadProc,(any_t) self);

	while ( queryActiveFlag )
	{
		modalStatus = [NXApp runModalSession:&theSession];
		if ( modalStatus != NX_RUNCONTINUES )
			break;
		/* Display the current query status */
		if( strcmp(statusBuffer,dirsendStatusBuffer) )
		{
			[statusID setStringValue: dirsendStatusBuffer];
			strcpy(statusBuffer,dirsendStatusBuffer);
		}
		cthread_yield();
	}
	[queryPanelID orderOut: self];
	[NXApp endModalSession: &theSession];

	/* Check result status */
	if(queryActiveFlag)
		// Query was aborted
		thread_resume(cthread_thread(queryThread));

	threadExitStatus = (int) cthread_join(queryThread);
	if(modalStatus != QUERY_ABORT && threadExitStatus > 0)
	{
		if(perrno == 0)
			strcpy(messageBuffer,"No matches found");
		else
			sperrmesg(messageBuffer,"Propsero Error Message:\n",0,NULL);
		NXBeep();
		NXRunAlertPanel(NULL, messageBuffer,NULL,NULL,NULL);
		return nil;
	}
	else if(modalStatus == QUERY_ABORT)
		return nil;

	if(pwarn)
	{
		spwarnmesg(messageBuffer,"Propsero Warning Message:\n",0,NULL);
		NXBeep();
		NXRunAlertPanel(NULL, messageBuffer,NULL,NULL,NULL);
	}

	/* Parse the Archie response for display in the browser */
	if(threadExitStatus == 0)
		if( [self parseResponse: archieRslt] != nil)
		{
			/* Set the fileBrowserID cells class to MyBrowserCell */
			[fileBrowserID setCellClass: [MyBrowserCell class]];
			[fileBrowserID setPathSeparator: ':'];  // Not used presently
			[fileBrowserID loadColumnZero];
		}

	return self;
}

- parseResponse: (VLINK) response
{
ProsperoVLINK *v;
List *hostFiles;
const char *host_name;

	[hostHashTable freeObjects];
	[hostList freeObjects];
	while(response)
	{	/* Step 1 - Create a ProsperoVLINK object this VLINK */
		v = [[ProsperoVLINK alloc] initVLINK: response];
		if(v == nil)
		{	// Error
			return [self error: ALERT key: VLINK_ALLOC_FAILED];
		}
		/* Step 2 - See if the hostname has been entered as a key in the hostHashTable */
		host_name = [v hostname];
		if( [hostHashTable isKey: host_name] == YES)
		{	// Then get the List of files for this host
			hostFiles = (List *) [hostHashTable valueForKey: host_name];
			if(hostFiles == nil)
			{	// Error
				return [self error: ALERT key: VALID_KEY_NIL_ENTRY];
			}
			// Add this file to the host list
			[hostFiles addObject: v];
		}
		else
		{	// Create a new List for this host and add it to the
			//	hostHashTable for fast acces and to the hostList to maintain
			//	the requested order
			hostFiles = [[List alloc] initCount: 0];
			if(hostFiles == nil)
				return [self error: SEVERE key: HOSTFILES_ALLOC_FAILED];
			[hostFiles addObject: v];
			[hostHashTable insertKey: host_name value: hostFiles];
			[hostList addObject: v];
		}
		response = response->next;
	}
	return self;
}

/* Interrupt the request */
- interruptRequest: sender
{
	/* Suspend and abort the query thread */
	thread_suspend(cthread_thread(queryThread));
	cthread_abort(queryThread);
	
	/* Stop the modal loop */
	[NXApp stopModal: QUERY_ABORT];

	return self;
}

/* A simple wrapper function that is passed to cthread_fork().  The thread that
	calls this function simply invokes the archie_query() function and sets the
	archieRslt instance variable to its reslt.
	It returns 0 on sucess and nonzero if an error occurs.
*/
void QueryThreadProc(ArchieServer *self)
{
int queryFlags;

	if(self->sortType == NoSort)
		queryFlags = AQ_NOSORT;
	else
		queryFlags = 0;
	if(self->exactFlag == YES)
		self->queryType = NXToLower(self->queryType);

	/* Initialize the Prospero error variables */ 
	perrno = 0; *p_err_string = '\0';
	pwarn = 0;  *p_warn_string = '\0';

	self->archieRslt = archie_query(self->hostname,searchString,self->maxReplies,
		self->offset,self->queryType,self->cmpProc,queryFlags);
	
	queryActiveFlag = 0;
	if(self->archieRslt == NULL)
		cthread_exit( (any_t)1 );	// No matches found, query aborted, or Prospero error

	cthread_exit( (any_t)0 );		// Successful query
}

/* Bring up the info panel */
- infoPanel: sender
{
	if(infoPanelID == nil)
	{
		[NXApp loadNibSection: "Info.nib" owner: self withNames: NO];
		if(infoPanelID == nil)
			return [self error: SORRY key: INFO_PANEL_FAILED];
	}
	[infoPanelID makeKeyAndOrderFront: self];
	return self;
}

/* Display the query options panel */
- preferences: sender
{
	if(prefPanelID == nil)
	{
		[NXApp loadNibSection: "Preferences.nib" owner: self withNames: NO];
		if(prefPanelID == nil)
			return [self error: SORRY key: PREF_PANEL_FAILED];
	}
	if(resetPrefPanel == YES)
	{
	int nice,query;
		[ hostMatrixID selectCellWithTag: hostTag];
		if(hostTag == CUSTOM_TAG)
			[customHostID setStringValue: hostname];
		[ sortMatrixID selectCellAt: sortType: 0];
		switch(queryType)
		{
			case 'C' :
				query = 0;
				break;
			case 'S' :
				query = 1;
				break;
			case 'R' :
				query = 2;
				break;
			case '=' :
				query = 3;
				break;
			default :
				query = 0;
				break;
		}
		[ patternMatrixID selectCellAt: query: 0];
		[ exactBtnID setState: exactFlag];
		switch(niceLevel)
		{
			case MinNice :
				nice = 0;
				break;
			case LowNice :
				nice = 1;
				break;
			case HighNice :
				nice = 2;
				break;
			case MaxNice :
				nice = 3;
				break;
			default :
				nice = 0;
				break;
		}
		[ niceMatrixID selectCellAt: 0 : nice];
		[ hitsFormID setIntValue: maxReplies];
		[ debugMatrixID selectCellAt: 0 : debugLevel];
	}

	[[prefPanelID orderFront: self] display];
	return self;
}

/* Methods which set the query options */
- setQueryType: sender
{
	switch([sender selectedRow])
	{
		case 0 :
			queryType = 'C';
			break;
		case 1 :
			queryType = 'S';
			break;
		case 2 :
			queryType = 'R';
			break;
		case 3 :
		default:
			queryType = '=';
			break;
	}
	return self;
}
- setExactMode: sender
{
	exactFlag = [sender state];
	return self;
}
- setMaxReplies: sender
{
	maxReplies = [sender intValue];
	return self;
}
- setOffset: sender
{
	offset = [sender intValue];
	return self;
}
- setSortType: sender
{
	sortType = [sender selectedRow];
	switch(sortType)
	{
		case 0 :
			cmpProc = AQ_DEFCMP;
			break;
		case 1 :
			cmpProc = AQ_INVDATECMP;
			break;
		case 2 :
			cmpProc = NULL;
			break;
		default :
			cmpProc = AQ_DEFCMP;
			break;
	}
	return self;
}
- setNiceLevel: sender
{
	switch([sender selectedCol])
	{
		case 0 :
			niceLevel = 	MinNice;
			break;
		case 1 :
			niceLevel = 	LowNice;
			break;
		case 2 :
			niceLevel = 	HighNice;
			break;
		case 3 :
			niceLevel = 	MaxNice;
			break;
		default :
			niceLevel = 	MinNice;
			break;
	}
	return self;
}
- setHostname: sender
{
	/* See if this is a custom host request */
	if( strcmp([[sender selectedCell] title],CUSTOM_TITLE) == 0 )
		hostname = [customHostID stringValue];
	else
		hostname = (char *) [[sender selectedCell] title];
	hostTag= [[sender selectedCell] tag]; 
	return self;
}
/* The target of the ``Custom hostname'' TextField */
- setCustomHost: sender
{
	hostname = [sender stringValue];
	hostTag = CUSTOM_TAG;
	[hostMatrixID selectCellWithTag: hostTag];
	return self;
}
- setDebugLevel: sender
{
ButtonCell *cell;
	cell = [sender selectedCell];
	pfs_debug = debugLevel = [cell tag];
	return self;
}

/* Ftp methods */
- retrieveFile: sender
{
const char *remoteHost,*remoteFile;
BOOL binaryMode;
int selectedColumn;

	if(ftpObject == nil)
	{
		ftpObject = [[FTPManager alloc] init];
		if(ftpObject == nil)
			return [self error: ALERT key: FTPOBJECT_ALLOC_FAILED];
	}
	remoteHost = [selectedFile hostname];
	remoteFile = [selectedFile filePath];
	if([modeMatrixID selectedRow] == 0)
		binaryMode = NO;
	else
		binaryMode = YES;
	/* Activate the FTPManager for the transfer */
	if([selectedFile isDirectory] == NO)
		[ftpObject runModal: remoteHost get: remoteFile anon: YES
			write: NO binary: binaryMode vlink: nil];
	else
	{
		[ftpObject runModal: remoteHost get: remoteFile anon: YES
			write: NO binary: NO vlink: selectedFile];
		selectedColumn = [fileBrowserID selectedColumn];
		[fileBrowserID reloadColumn: selectedColumn+1];
	}
	return self;
}

- setTransferDir : sender
{
	if(ftpObject == nil)
	{
		ftpObject = [[FTPManager alloc] init];
		if(ftpObject == nil)
			return [self error: ALERT key: FTPOBJECT_ALLOC_FAILED];
	}
	[ftpObject setLocalDir: self];

	return self;
}

/* Read/Restore default preferences */
- saveDefaults: sender
{
/* An updateable defaults vector */
int i;
struct _NXDefault *_NextArchieDefaults;
	_NextArchieDefaults = (struct _NXDefault *) malloc(10*sizeof(struct _NXDefault));
	if(_NextArchieDefaults == NULL)
		return [self error: ALERT key: NXDEFAULTS_ALLOC_FAILED];

	for(i = 0; i < 9; i ++)
	{
		_NextArchieDefaults[i].name = (char *) malloc(16);
		_NextArchieDefaults[i].value = (char *) malloc(32);
		if(_NextArchieDefaults[i].name == NULL ||
			_NextArchieDefaults[i].value == NULL)
			return [self error: ALERT key: NXDEFAULTS_ITEM_ALLOC_FAILED];
	}
	_NextArchieDefaults[9].name = NULL;
	_NextArchieDefaults[9].value = NULL;

	strcpy(_NextArchieDefaults[QueryType].name, DefaultString(QueryType));
	strcpy(_NextArchieDefaults[MaxReplies].name, DefaultString(MaxReplies));
	strcpy(_NextArchieDefaults[Offset].name, DefaultString(Offset));
	strcpy(_NextArchieDefaults[SortType].name, DefaultString(SortType));
	strcpy(_NextArchieDefaults[ExactFlag].name, DefaultString(ExactFlag));
	strcpy(_NextArchieDefaults[NiceLevel].name, DefaultString(NiceLevel));
	strcpy(_NextArchieDefaults[Hostname].name, DefaultString(Hostname));
	strcpy(_NextArchieDefaults[HostTag].name, DefaultString(HostTag));
	strcpy(_NextArchieDefaults[DebugLevel].name, DefaultString(DebugLevel));
	sprintf(_NextArchieDefaults[QueryType].value,"%c",queryType);
	sprintf(_NextArchieDefaults[MaxReplies].value,"%d",maxReplies);
	sprintf(_NextArchieDefaults[Offset].value,"%d",offset);
	sprintf(_NextArchieDefaults[SortType].value,"%d",sortType);
	sprintf(_NextArchieDefaults[ExactFlag].value,"%s",(exactFlag == YES ? "YES" : "NO"));
	sprintf(_NextArchieDefaults[NiceLevel].value,"%d",niceLevel);
	sprintf(_NextArchieDefaults[Hostname].value,"%s",hostname);
	sprintf(_NextArchieDefaults[HostTag].value,"%d",hostTag);
	sprintf(_NextArchieDefaults[DebugLevel].value,"%d",debugLevel);

	NXWriteDefaults(NeXTArchieOwner, _NextArchieDefaults);

	for(i = 0; i < 9; i ++)
	{
		free(_NextArchieDefaults[i].name);
		free(_NextArchieDefaults[i].value);
	}
	free(_NextArchieDefaults);

	return self;
}

- restoreDefaults: sender
{
	/* Restore the virgin defaults */
	NXWriteDefaults(NeXTArchieOwner, NextArchieDefaults);
	NXUpdateDefaults();

	/* Reset the preferences panel objects */
	resetPrefPanel = YES;
	[self preferences: self];

	return self;
}

/* Browser delegate method which fills the browser with the last
	query result.  This version displays the hosts in column0, the full pathanme
	in column1 and the lowest file in the path in column2. */
- (int)browser: sender  fillMatrix: matrix inColumn:(int) column
{
const char *host_name;
int row,rows,depth;
id hostFileList,link,dirListing;
MyBrowserCell *cell;

	/* Column zero */
	if(column == 0)
	{	// Load the host names

		rows = [hostList count];
		for(row = 0; row < rows; row ++)
		{
			host_name = [[hostList objectAt: row] hostname];
			[matrix addRow];
			cell = [matrix cellAt: row : 0];
			[cell setStringValue: host_name];
			[cell setLoaded:YES];
			[cell setLeaf: NO];
		}
	}
	else if(column == 1)
	{	// Loop through the host's files and display the pathnames

		/* Get the file list for the selected host */
		host_name = [[[sender matrixInColumn: 0] selectedCell] stringValue];
		hostFileList = (List *) [hostHashTable valueForKey: host_name];
		
		rows = [hostFileList count];
		for(row = 0; row < rows; row ++)
		{
			link = (ProsperoVLINK *) [hostFileList objectAt: row];
			[matrix addRow];
			depth = [link fileDepth];
			cell = [matrix cellAt: row : 0];
				[cell setStringValue: [link filePath]];
			[cell setLoaded:YES];
			if(depth > column)
			{
				[cell setLeaf: NO];
				[cell setTag: nil];
			}
			else
			{	// This is the file's level
				if([link isDirectory] == NO)
					[cell setLeaf: YES];
				else
					[cell setLeaf: NO];
				[cell setTag: link];
			}
		}
	}
	else if(column == 2)
	{	// Load one cell and display the end item of the selected path
		
		/* Get the file list for the selected host */
		host_name = [[[sender matrixInColumn: 0] selectedCell] stringValue];
		hostFileList = (List *) [hostHashTable valueForKey: host_name];
		rows = 1;
		row = [[sender matrixInColumn: 1] selectedRow];
		link = (ProsperoVLINK *) [hostFileList objectAt: row];
		[matrix addRow];
		cell = [matrix cellAt: 0 : 0];
		[cell setStringValue: [link fileName]];
		[cell setLoaded:YES];
		[cell setTag: link];
		if( [link isDirectory] == NO)
			[cell setLeaf: YES];
		else
			[cell setLeaf: NO];
	}
	else
	{
		// The directory vlink in the previous column
		cell = [[sender matrixInColumn: column - 1] selectedCell] ;
		link = [cell tag];
		dirListing = [link listing];
		rows = [dirListing count];
		for(row = 0; row < rows; row ++)
		{
			link = [dirListing objectAt: row];
			[matrix addRow];
			cell = [matrix cellAt: row : 0];
			[cell setStringValue: [link fileName]];
			[cell setLoaded:YES];
			[cell setTag: link];
			[cell setEnabled: YES];
			if( [link isDirectory] == NO)
				[cell setLeaf: YES];
			else
				[cell setLeaf: NO];
		}
		if(rows <= 0)
		{
			rows = 1;
			[matrix addRow];
			cell = [matrix cellAt: row : 0];
			if(dirListing == nil)
				[cell setStringValue: "Directory not loaded"];
			else
				[cell setStringValue: "No files"];
			[cell setLoaded:YES];
			[cell setTag: nil];
			[cell setLeaf: YES];
			[cell setEnabled: NO];
		}
	}
	return rows;
}

#define Read 4
#define Write 2
#define Exec 1
#define Owner 448
#define Group 56
#define Other 7

- fileAttributes: sender
{
ProsperoVLINK *link;
short hr,min,sec;
short day,month,year;
int owner,group,other,mode;
int row,col;
MyBrowserCell *cell;

	col = [sender selectedColumn];
	if(col == 0)
	{	// Clear the info display
		[self clearFields];
		[retrieveBtnID setEnabled: NO];
		selectedFile = nil;
		return self;
	}

	row = [[sender matrixInColumn: col] selectedRow];
	cell = [sender getLoadedCellAtRow: row inColumn: col];

	/* The id value of the selected cell is the cell's tag value */
	link = (ProsperoVLINK *) [cell tag];
	selectedFile = link;

	/* Don't display any info for non directories */
	if([cell isLeaf] == NO && col == 1)
	{
		[self clearFields];
		[retrieveBtnID setEnabled: NO];
		selectedFile = nil;
		return self;
	}

	/* Enable ftp transfer if the item is not a directory, listing if it is */	[retrieveBtnID setEnabled: YES];
	if([selectedFile isDirectory] == NO)
		[retrieveBtnID setTitle: "Retrieve..."];
	else
		[retrieveBtnID setTitle: "Listing..."];

	/* Set the modification time and date */
	[link time: &hr : &min : &sec];
	[clockID setTime: hr : min : sec];
	day = [link day];
	month = [link month];	// Runs from 0-11
	year = [link year];
	// Expects month to run from 1-12 for consistency with string dates
	[calendarID setDate: day : month+1 : year];

	/* Set the file info Form */
	[fileInfoID setStringValue: [link hostname] at: 0];
	[fileInfoID setStringValue: [link filePath] at: 1];
	[fileInfoID setStringValue: [link fileName] at: 2];
	[fileInfoID setIntValue: [link fileSize] at: 3];
	[fileInfoID setStringValue: ([link isDirectory] == YES ? "Directory" : "File") at: 4];

	/* Set the file permissions */
	mode = [link mode];
	owner = (mode  & Owner) >> 6;
	group = (mode & Group) >> 3;
	other = mode & Other;
	if(owner & Read)
		[readMatrixID setState: YES at: 0 : 0];
	else
		[readMatrixID setState: NO at: 0 : 0];
	if(group & Read)
		[readMatrixID setState: YES at: 0 : 1];
	else
		[readMatrixID setState: NO at: 0 : 1];
	if(other & Read)
		[readMatrixID setState: YES at: 0 : 2];
	else
		[readMatrixID setState: NO at: 0 : 2];

	if(owner & Write)
		[writeMatrixID setState: YES at: 0 : 0];
	else
		[writeMatrixID setState: NO at: 0 : 0];
	if(group & Write)
		[writeMatrixID setState: YES at: 0 : 1];
	else
		[writeMatrixID setState: NO at: 0 : 1];
	if(other & Write)
		[writeMatrixID setState: YES at: 0 : 2];
	else
		[writeMatrixID setState: NO at: 0 : 2];

	if(owner & Exec)
		[execMatrixID setState: YES at: 0 : 0];
	else
		[execMatrixID setState: NO at: 0 : 0];
	if(group & Exec)
		[execMatrixID setState: YES at: 0 : 1];
	else
		[execMatrixID setState: NO at: 0 : 1];
	if(other & Exec)
		[execMatrixID setState: YES at: 0 : 2];
	else
		[execMatrixID setState: NO at: 0 : 2];

	return self;
}

- clearFields
{
	/* Reset date to now */
	[clockID now];
	[calendarID today];

	/* Reset the file info Form */
	[fileInfoID setStringValue: NULL at: 0];
	[fileInfoID setStringValue: NULL at: 1];
	[fileInfoID setStringValue: NULL at: 2];
	[fileInfoID setStringValue: NULL at: 3];
	[fileInfoID setStringValue: NULL at: 4];

	/* Rest permissions */
	[readMatrixID setState: YES at: 0 : 0];
	[readMatrixID setState: YES at: 0 : 1];
	[readMatrixID setState: YES at: 0 : 2];
	[writeMatrixID setState: YES at: 0 : 0];
	[writeMatrixID setState: YES at: 0 : 1];
	[writeMatrixID setState: YES at: 0 : 2];
	[execMatrixID setState: YES at: 0 : 0];
	[execMatrixID setState: YES at: 0 : 1];
	[execMatrixID setState: YES at: 0 : 2];

	return self;
}

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

@end
	