{\rtf0\ansi{\fonttbl\f0\fmodern Courier;}
\paperw11440
\paperh8340
\margl120
\margr120
{\colortbl;\red0\green0\blue0;}
\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\f0\b\i0\ulnone\fs24\fc0\cf0 #import <appkit/appkit.h>\
#import "FtpFile.h"\
#import "FTPObject.h"\
#import <string.h>\
#import <time.h>\
#import <stdio.h>\
#import <libc.h>\
#import <ctype.h>\
#import <sys/file.h>\
#import <regex.h>\
\

\b0\i /* Items used to parse the field returned by the ftpd LIST command */
\b\i0 \
enum \{Jan=1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec\};\
static int thisYear, thisMonth, thisDay;\
static struct regex *months = NULL;\
\
static BOOL IsDirEntry(char *entry)\
\{\

\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker454 \markername IsDirEntry;}
,}\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul RoutineDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  YES if we think entry is valid, NO otherwise;\
	
\i0\ul Description:
\i\ulnone  This routine checks the entry buffer for a leading\
		UNIX style of mode string. If the first 10 chars appear to\
		satisfy this, we assume this is the start of an entry listing\
		in a ftpd LIST output.;\
	
\i0\ul Args:
\i\ulnone  \
		entry: A buffer containg the LIST output being checked;\
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\i0\fc0\cf0 \

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 char tmp_c;\
int count;\
\
	
\b0\i /* Check the first 10 characters for the unix mode info */
\b\i0 \
	if( strlen(entry) < 11 )\
		return NO;		
\b0\i // Not a valid entry format
\b\i0 \
\
	tmp_c = entry[10];	
\b0\i // Save the 11th char
\b\i0 \
	entry[10] = '\\0';\
	
\b0\i /* The check for a UNIX mode is made by comparing the first 10 chars\
		against the "dlrwxst-" span.  If every char was in the span we\
		assume success. We don't recognize special device files. */
\b\i0 \
	count = strspn(entry,"dlrwxst-");\
	entry[10] = tmp_c;\
	if( count == 10 )\
		return YES;\
\
	return NO;\
\} 
\b0\i // End IsDirEntry()
\b\i0 \
\
/* Get the next line from the 'ftp dir' output */\
static char *NextEntry(char *buffer)\
\{\

\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker1450 \markername NextEntry;}
,}\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul RoutineDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  A pointer into buffer for the next entry if one\
		is found, NULL otherwise;\
	
\i0\ul Description:
\i\ulnone  This routine searches the buffer for the next entry\
		listing in a ftpd LIST output. If buffer is NULL, the last non-NULL\
		buffer passed is used. An entry is recognized by a '\\n'\
		followed by a UNIX style mode string.;\
	
\i0\ul Args:
\i\ulnone  \
		buffer: A buffer containg the LIST output to search;\
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\i0\fc0\cf0 \

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 static char *searchBuffer;\
static int depth;\
char *tmpPtr;\
\
	
\b0\i /* If buffer is not NULL, save a pointer to it in searchBuffer */
\b\i0 \
	if( buffer != NULL )\
		searchBuffer = buffer;\
	
\b0\i /* Find the next "\\n[dl-][rwxs-]x9" sequence in buffer */
\b\i0 \
	tmpPtr = strchr(searchBuffer,'\\n');\
	if( tmpPtr == NULL )\
		return NULL;	
\b0\i // End of buffer or error condition
\b\i0 \
\
	
\b0\i /* Verify the next string is a UNIX mode string. If it is not we\
		try to advance to the next recognizable entry. This is required\
		because some sites have modes I don't recognize, e.g.,\
		'drwx--S---' from ftp.uu.net. */
\b\i0 \
	searchBuffer = ++ tmpPtr;\
	if( IsDirEntry(tmpPtr) == YES )\
	\{	
\b0\i\fc1\cf1 // Return pointer to start of entry
\b\i0\fc0\cf0 \
		depth = 0;\
		return tmpPtr;\
	\}\
	else if( depth < 4 )\
	\{	
\b0\i\fc1\cf1 // If we can't find a valid entry near by forget it
\b\i0\fc0\cf0 \
		depth ++;\
		return NextEntry(buffer);\
	\}\
	depth = 0;\
\
	return 0;\
\} 
\b0\i // End NextEntry()
\b\i0 \
\
static const ListingEntry *ParseAttributes(char **buffer, int *nameLength)\
\{\

\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker2811 \markername ParseAttributes;}
,}\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul RoutineDescription
\ulnone \
	
\i0\ul ReturnValue:
\b\ulnone\fc0\cf0  
\b0\i\fc1\cf1 A const ListingEntry pointer for the LIST entry pointed to\
		by buffer, 0 otherwise. Buffer is set to the name of the current\
		file name.;\
	
\i0\ul Description:
\i\ulnone  This routine parses the buffer for the current entry\
		attributes. It assumes the entry format is equivalent to the UNIX\
		ls -l output.\
	
\i0\ul Args:
\i\ulnone  \
		buffer: A pointer to the buffer containing the ftpd LIST output;\
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\i0\fc0\cf0 \

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 static ListingEntry entry;\
char *tmpPtr, *datePtr, *dayPtr, *yr_timePtr, tmp_c;\
\
	
\b0\i /* Look for the month regular expression */
\b\i0 \
	if( re_match(*buffer, months) != 1 )\
	\{	
\b0\i\fc1\cf1 /* Could not find a match, assume this is not a LIST entry */
\b\i0\fc0\cf0 \
		return 0;\
	\}\
	strncpy(entry.mode, *buffer, 10);\
	datePtr = tmpPtr = (months->start + 2);\
	if( tmpPtr == 0 )\
		return 0;\
\
	
\b0\i /* Determine which month name we found */
\b\i0 \
	switch( tmpPtr[0] )\
	\{\
		case 'A':\
			if( tmpPtr[1] == 'p' )\
				entry.month = Apr;\
			else\
				entry.month = Aug;\
		break;\
		case 'D':\
			entry.month = Dec;\
		break;\
		case 'F':\
			entry.month = Feb;\
		break;\
		case 'J':\
			if( tmpPtr[1] == 'a' )\
				entry.month = Jan;\
			else if( tmpPtr[2] == 'l' )\
				entry.month = Jul;\
			else\
				entry.month = Jun;\
		break;\
		case 'M':\
			if( tmpPtr[2] == 'r' )\
				entry.month = Mar;\
			else\
				entry.month = May;\
		break;\
		case 'N':\
			entry.month = Nov;\
		break;\
		case 'O':\
			entry.month = Oct;\
		break;\
		case 'S':\
			entry.month = Sep;\
		break;\
		default :\
			return 0;	
\b0\i // Never should happen
\b\i0 \
	\}\
\
	
\b0\i /* Now backup to the end of the size field */
\b\i0 \
	while( ! isdigit(*tmpPtr) )\
		tmpPtr --;\
	while( isdigit(*tmpPtr) )\
		tmpPtr --;			
\b0\i\fc1\cf1 // And now to the start of the size
\b\i0\fc0\cf0 \
	tmpPtr ++;\
	entry.size = atoi(tmpPtr);\
	
\b0\i\fc1\cf1 /* Move to the day field */
\b\i0\fc0\cf0 \
	datePtr += 4;\
	while( ! isdigit(*datePtr) )\
		datePtr ++;\
	dayPtr = datePtr;		
\b0\i\fc1\cf1 // Save a pointer to the day
\b\i0\fc0\cf0 \
	while( isdigit(*datePtr) != 0 )\
		datePtr ++;			
\b0\i\fc1\cf1 // Move past the day field
\b\i0\fc0\cf0 \
	tmp_c = *datePtr; *datePtr = '\\0';\
	entry.day = atoi(dayPtr);	
\b0\i\fc1\cf1 // Extract the day
\b\i0\fc0\cf0 \
	*datePtr = tmp_c;\
	
\b0\i\fc1\cf1 /* Get the year or time */
\b\i0\fc0\cf0 \
	datePtr ++;\
	while( isdigit(*datePtr) == 0 )\
		datePtr ++;			
\b0\i\fc1\cf1 // Move to next number
\b\i0\fc0\cf0 \
	yr_timePtr = datePtr;	
\b0\i\fc1\cf1 // Save a pointer to the year or time
\b\i0\fc0\cf0 \
	while( isdigit(*datePtr) != 0 )\
		datePtr ++;			
\b0\i\fc1\cf1 // Move to next non-number
\b\i0\fc0\cf0 \
	if( *datePtr == ':' )\
	\{	
\b0\i\fc1\cf1 // This is a time, the file must be < 12 months old
\b\i0\fc0\cf0 \
		sscanf(yr_timePtr,"%d:%d", &entry.hr, &entry.min);\
		
\b0\i\fc1\cf1 // Calculate the year of the file
\b\i0\fc0\cf0 \
		if( entry.month > thisMonth )\
			entry.year = thisYear - 1;\
		else if( entry.month == thisMonth && entry.day > thisDay)\
			entry.year = thisYear - 1;\
		else\
			entry.year = thisYear;\
		datePtr += 3;		
\b0\i\fc1\cf1 // Move past the minutes & trailing space
\b\i0\fc0\cf0 \
	\}\
	else\
	\{	
\b0\i\fc1\cf1 // yr_timePtr should point to a year
\b\i0\fc0\cf0 \
		tmp_c = *datePtr; *datePtr = '\\0';\
		entry.year = atoi(yr_timePtr);\
		*datePtr = tmp_c;\
		datePtr ++;\
		entry.hr = entry.min = 0;\
	\}\
	
\b0\i\fc1\cf1 /* Lastly set the buffer to the file name */
\b\i0\fc0\cf0 \
	while( isspace(*datePtr) != 0 )\
		datePtr ++;			
\b0\i\fc1\cf1 // Move to first non-space char after time/year
\b\i0\fc0\cf0 \
	*buffer = datePtr;\
	entry.name = datePtr;\
	tmpPtr = strchr(datePtr, '\\n');\
	if( tmpPtr == 0 )\
		*nameLength = strlen(entry.name);\
	else\
		*nameLength = tmpPtr - entry.name;\
\
	return &entry;\
\} 
\b0\i\fc1\cf1 // End ParseAttributes()
\b\i0\fc0\cf0 \
\
@implementation FtpFile\
\
+ initialize\
\{\

\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker6032 \markername ftpInit:host:parentDir:info:;}
,}\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self;\
	
\i0\ul Description:
\i\ulnone  This method initializes the static variables used\
		to parse the UNIX style LIST entries;\
	
\i0\ul Args:
\i\ulnone  \
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\i0\fc0\cf0 \

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 struct tm *timeStruct;\
time_t now;\
\
	
\b0\i /* Intialize search variables. months is a regular epression for the \
		size month day: portion of an entry.*/
\b\i0 \
	months = re_compile("[0-9] [JFMASOND][aepuco][nbrylgptvc] [0-3 ][0-9]",0);\
	time(&now);\
	timeStruct = localtime(&now);\
	thisDay = timeStruct->tm_mday;\
	thisMonth = timeStruct->tm_mon + 1;\
	thisYear = timeStruct->tm_year + 1900;\
\
	return self;\
\}\
\
- ftpInit:(const ListingEntry *) listingEntry host:(const char *) ftpHost\
	parentDir:(const char *) path info:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker6718 \markername ftpInit:host:parentDir:info:;}
,}\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self if successful, [self free] otherwise;\
	
\i0\ul Description:
\i\ulnone  This method initializes an FtpFile object to represent\
		the remote file given in the 
\i0 listingEntry
\i  buffer. listingEntry\
		should point to an entry from a ftpd LIST command.;\
	
\i0\ul Args:
\i\ulnone  \
		listingEntry: An entry from a ftpd LIST command;\
		ftpHost: The anonymous ftp hostname the listing was obtained from;\
		path: The full pathname to the file's parent directory on ftpHost;\
		info: The FTPInfoPtr used to obtain the listing entry;\
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\i0\fc0\cf0 \
char *tmpPtr, *endPath, pathname[MAXPATHLEN];\
int length;\
\

\b0\fs20\fc1\cf1 #ifdef DEBUG\
tmpPtr = strchr(listingEntry->name, '\\n');\
if( tmpPtr != NULL )\
	length = tmpPtr - listingEntry->name;\
else\
	length = strlen(listingEntry->name);\
[self debug: SUPER_DEBUG method:_cmd, "\\n %.*s", length, listingEntry->name];\
#endif\
\

\b\fs24\fc0\cf0 	
\b0\i\fc1\cf1 /* Locate the end of the filename by searching for a '\\n' char.\
		This is neccessary because listingEntry may be only one of\
		several entries. */
\b\i0\fc0\cf0 \
	tmpPtr = strchr(listingEntry->name, '\\n');\
	if( tmpPtr != NULL )\
		*tmpPtr = '\\0';\
	
\b0\i\fc1\cf1 /* Build the remote file's full pathname */
\b\i0\fc0\cf0 \
	length = strlen(listingEntry->name) + 1;\
	if( path != 0 )\
		length += strlen(path) + 1;\
	if( path != 0 )	// Add the parent directory\
		strcpy(pathname, path);\
	else\
		pathname[0] = 0;\
	endPath = index(pathname, '\\0');\
	if( path != 0 && endPath[-1] != '/' )\
	\{	// If the parent dir does not end in a '/' append one\
		*endPath ++ = '/';\
		*endPath = '\\0';\
	\}\
	strcat(pathname, listingEntry->name);\

\b0\fs20 [self debug: SUPER_DEBUG method:_cmd, "%s %.10s s:%d d:%d m:%d y:%d h:%d m:%d",\
	pathname, listingEntry->mode, listingEntry->size, listingEntry->day,\
	listingEntry->month, listingEntry->year, listingEntry->hr, listingEntry->min];
\b\fs24 \
	
\b0\i\fc1\cf1 // Initialize our File superclass
\b\i0\fc0\cf0 \
	[super init: pathname mode: listingEntry->mode size: listingEntry->size\
		day: listingEntry->day month: listingEntry->month\
		year: listingEntry->year hour: listingEntry->hr\
		min: listingEntry->min];\
	[super setSourceHost: ftpHost];\
	ftpInfo = info;\
\
	
\b0\i\fc1\cf1 // Restore the listingEntry buffer if necessary
\b\i0\fc0\cf0 \
	if( tmpPtr != NULL )\
		*tmpPtr = '\\n';\

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 \
	return self;\
\} 
\b0\i\fc1\cf1 // End ftpInit: host: parentDir: info:
\b\i0\fc0\cf0 \
\
- ftpInitDir:(const char *) path host:(const char *) ftpHost\
	ftpd:(FTPObject *) ftpd info:(FTPInfoPtr) info\
\{
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 \

\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker8999 \markername setFTPInfo:;}
,}\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self;\
	
\i0\ul Description:
\i\ulnone  Initializes an FtpFile directory for 
\i0 path
\i  and loads\
		its contents using the 
\i0 ftpd
\i  object. This method is used to\
		initialize a directory whose path was constructed by resolving\
		a link target.  Under these circumstances we haven't descended\
		the directory structure to the directory indicated by 
\i0 path
\i  and,\
		so don't have the UNIX info from the parent directory.;\
	
\i0\ul Args:
\i\ulnone  \
		path: The pathname for the directory;\
		ftpHost: Remote FTP hostname;\
		ftpd: The FTPObject managing FTP transactions to ftpHost;\
		info: The FTPInfoPtr to use with future FTP transactions;\
*/
\b\i0\fc0\cf0 \

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 	
\b0\i\fc1\cf1 // Initialize our File superclass with dummy values
\b\i0\fc0\cf0 \
	[super init: path mode: "d?????????" size: 0\
		day: 1 month: 1 year: 1970 hour: 0 min: 0];\
	[super setSourceHost: ftpHost];\
	delegate = ftpd;\
	ftpInfo = info;\
\
	// Load our contents\
	listing = [self dirListing: path ftpd: ftpd];\
\
	return self;\

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 \}\
\
- setFTPInfo:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker9963 \markername setFTPInfo:;}
,}\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self;\
	
\i0\ul Description:
\i\ulnone  Set our ftpInfo variable to info;\
	
\i0\ul Args:
\i\ulnone  \
		info: The FTPInfoPtr to use with future FTP transactions;\
*/
\b\i0\fc0\cf0 \

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 	ftpInfo = info;\
\
	return self;\
\} 
\b0\i // End setFTPInfo:
\b\i0 \
\
- ftpLoadDir: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker10205 \markername ftpLoadDir:;}
,}\pard\tx180\tx360\tx540\tx720\tx900\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  An ObjectList of the directory contents;\
	
\i0\ul Description:
\i\ulnone  This method obtains a listing of a remote\
		directory by invoking our 
\b dirListing:ftpd:
\b0  method. It first\
		checks the sender's orphan directory list in case our\
		contents were loaded by resolving a link.;\
	
\i0\ul Args:
\i\ulnone \
		sender: A FTPObject subclass we can use to talk to\
			the remote ftpd;\
*/
\b\i0\fc0\cf0 \

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 id dirListing;\
FtpFile *us;\
\
	if( emptyDir == YES )\
		return nil;\
\
	listing = nil;\
	
\b0\i\fc1\cf1 // Check the orphan dir list first
\b\i0\fc0\cf0 \
	us = [sender removeOrphanDir: sourcePath];\
	if( us != nil )\
	\{	
\b0\i\fc1\cf1 // Our contents have been loaded earlier
\b\i0\fc0\cf0 \
		dirListing = [us dirListing];\
		us->listing = nil;\
		[us free];\
	\}\
	else\
		dirListing = [self dirListing: sourcePath ftpd: sender];\
\
	if( dirListing != nil )\
		listing = dirListing;\
	else\
		[super setEmptyDir: YES];\
\
	return dirListing;\
\} 
\b0\i\fc1\cf1 // End ftpLoadDir:
\b\i0\fc0\cf0 \
\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 - dirListing:(const char *) pathname ftpd:(FTPObject *) ftpd\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker11136 \markername dirListing:ftpd:;}
,}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  An ObjectList of FtpFiles for the pathname directory.\
		If an error occurs, nil is returned.;\
	
\i0\ul Description:
\i\ulnone  This method reads the entries in the directory\
		given by pathname using the FTPObject ftpd and creates an\
		ObjectList of FtpFiles for the directory. If an entry is a\
		link, we try to resolve its target. If we fail the lnTarget\
		is left nil to indicate we were not smart enough to find the\
		file the link points to.;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0 \
		pathname: The full or relative pathname to the directory to load;\
		ftpd: The FTPObject we use to retreive the directory listing;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\i0\fc0\cf0 NXStream *listingStream;\
char *listingBuf,
\fc1\cf1  *entry, path[MAXPATHLEN], *pathFormat;\
const char *
\fc0\cf0 tmpPtr, *
\fc1\cf1 lnTargetName;\
int index, length, maxLength, 
\fc0\cf0 sourcePathLen
\fc1\cf1 ;\
ObjectList *dirFiles, *listOfLinks;\
FtpFile *file, *
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 lnTarget
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 ;\

\fc0\cf0 const ListingEntry *entryInfo;\
\
	
\b0\i\fc1\cf1 // If we have a dirListing just return it
\b\i0\fc0\cf0 \
	if( listing != nil )\
		return listing;\
\
	
\b0\i\fc1\cf1 /* Get the text description of the directory contents.  Some ftp \
		hosts do not allow  a leading slash (ERNST.MACH.CS.CMU.EDU\
		for example). We remove any leading slash when sending the\
		pathname to the server. */
\b\i0\fc0\cf0 \
	tmpPtr = pathname;\
	if( *pathname == '/' )\
		tmpPtr = pathname + 1;\
	listingStream = [ftpd streamForCmd: "LIST" : tmpPtr];\
	if( listingStream == NULL )\
		return nil;\
	// Set the sprintf format for combining pathname & entry name\
	tmpPtr = strchr(pathname, 0);\
	if( tmpPtr[-1] == '/' )\
		
\fc1\cf1 pathFormat = "%s%.*s";\
	else\
		pathFormat = "%s/%.*s";\
	// Get buffer of the listingStream result
\fc0\cf0 \
	NXGetMemoryBuffer(listingStream, &listingBuf, &length, &maxLength);\
	listingBuf[length] = '\\0';\
\
	
\b0\i\fc1\cf1 /* Allocate an ObjectList that sorts its FtpFile objects using\
		ShellSort and file's sourceName method as the key method for\
		both the directory entries and list of unresolved links */
\b\i0\fc0\cf0 \
	dirFiles = [[ObjectList alloc] initCount: 0 sortAlgorithm: eShellSort\
		typeID: eStringType keyMethod: @selector(sourceName)];\
	listOfLinks = [[ObjectList alloc] initCount: 0 sortAlgorithm: eShellSort\
		typeID: eStringType keyMethod: @selector(sourceName)];\
\
	
\b0\i\fc1\cf1 // Parse the memory stream into the file entries
\b\i0\fc0\cf0 \
	sourcePathLen = strlen(sourcePath);\
	while( (entry = NextEntry(listingBuf)) != NULL )\
	\{	// Parse the entry into its components\
		entr
\fc1\cf1 yInfo = ParseAttributes(&entry, &length);\
		if( entryInfo == 0 )\
			break;\
\

\fc0\cf0 		// First look to the orphaned dir list\
		sprintf(path, 
\fc1\cf1 pathFormat
\fc0\cf0 , pathname, length, entry);\
		file = [ftpd removeOrphanDir: path];\
		if( file == nil )\
		\{	
\b0\i\fc1\cf1 /* Create a new FtpFile from the entry */
\b\i0\fc0\cf0 \
			file = [[FtpFile alloc] ftpInit: entryInfo host: sourceHost\
				parentDir: pathname info: ftpInfo];\
		\}\
		[file setDelegate: delegate];\
		if( file != nil )\
			[dirFiles insertObject: file];\
\
		
\b0\i\fc1\cf1 /* If the file is a link we need to resolve its target. If its\
			target is relative to the current directory the file has\
			been or will be parsed. First look in dirFiles for the target\
			and if not found, place the link in the listOfLinks for later\
			resolution. */
\b\i0\fc0\cf0 \
		if( [file isLink] == YES )\
	
\fc1\cf1 	\{\

\fc0\cf0 			listingBuf = 0;\

\fc1\cf1 			
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 lnTarget
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 Name = [file 
\fc0\cf0 linkTargetName:
\fc1\cf1  YES];\
			if( 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 lnTarget
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 N
\fc0\cf0 ame == NULL )\
			\{	
\b0\i /* Failed to find target name, set the target to self to\
					indicate that the target was unresolvable */
\b\i0 \
				[file setLinkTarget: file];\
				continue;
\b0\i \
			
\b\i0 \}\
			
\b0\i // First try to locate the file in the FTPObject cache
\b\i0 \
			lnTarget = [ftpd fileFromPath: lnTargetName];\
			if( lnTarget != nil )\
			\{	
\b0\i // Found the link target
\b\i0 \

\fc1\cf1 				[file setLinkTarget: 
\fc0\cf0 lnTarget
\fc1\cf1 ];\
				continue;\
			\}\

\fc0\cf0 			else if( strncmp(sourcePath, lnTargetName, sourcePathLen) == 0 )\
			\{	
\b0\i\fc1\cf1 // Look for target in previously parsed files
\b\i0\fc0\cf0 \
				lnTargetName += sourcePathLen;\
				if( *lnTargetName == '/' )\
					lnTargetName ++;\
				
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 lnTarget
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1  = [dirFiles findObjectWithData: 
\fc0\cf0 lnTargetName
\fc1\cf1 ];\
				if( lnTarget != nil )\
					[file setLinkTarget: 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 lnTarget
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 ];\
				else\
				\{	
\b0\i /* The link target has not been resolved. We add it\
					to the list of links needing targets in the hopes of\
					resolving among the remaing files. */
\b\i0\fc0\cf0 \
					[listOfLinks insertObject: file];\

\fc1\cf1 				\}\
			\}\

\fc0\cf0 		\}\
		listingBuf = 0;\
	\}\
	NXCloseMemory(listingStream, NX_FREEBUFFER);\
	
\b0\i // Try to resolve all remaing link targets
\b\i0 \
	index = 0;\
	while( (file = [listOfLinks objectAt: index]) != nil )\
	\{\

\fc1\cf1 		
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 lnTarget
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 Name = [file 
\fc0\cf0 linkTargetName:
\fc1\cf1  YES];\

\fc0\cf0 		lnTargetName += sourcePathLen;\
		if( *lnTargetName == '/' )\
			lnTargetName ++;\

\fc1\cf1 		lnTarget = [dirFiles findObjectWithData: 
\fc0\cf0 lnTargetName
\fc1\cf1 ];\
		if( lnTarget != nil )\
			[file setLinkTarget: lnTarget];\
		else\
		\{	
\b0\i /* This will happen happen if a link is to a file below\
				a directory below the current directory.  Delay until\
				the file is selected. */
\b\i0 \

\b0\fs20\fc0\cf0 [self debug: LOW_DEBUG method:_cmd, "Failed to resolve %s\\n", [file sourcePathname]];
\b\fs24\fc1\cf1 \

\fc0\cf0 			[file setLinkTarget: nil];\
		\}\
		index ++;\
	\}\
	[listOfLinks free];\
\
	return dirFiles;\
\} 
\b0\i // End dirListing:ftpd:
\b\i0 \

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 \
- resolveLink:(const char *) targetName ftpd:(FTPObject *) ftpd\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker16073 \markername resolveLink:ftpd:;}
,}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  An FtpFile for targetName if successful, nil if\
		we fail, and -1 if we loose track of the CWD and the user\
		asks to logout;\
	
\i0\ul Description:
\i\ulnone  This method obtains a listing for the fully qualified\
		path targetName and returns an FtpFile representation;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0 \
		targetName: The full pathname of the link's target;\
		ftpd: The FTPObject we use to retreive the listing;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\i0\fc0\cf0 char *tmpPtr, *pathname;\
FtpF
\fc1\cf1 ile *
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 lnTarget
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 , *parentDir;\

\fc0\cf0 \
	if( targetName == 0 )\
	\{	
\b0\i\fc1\cf1 /* A nil targetName indicates that we need to obtain\
			the full pathname using our linkTargetName: method */
\b\i0\fc0\cf0 \
		targetName = [self linkTargetName: YES];\
		if( targetName == 0 )\
			return nil;\
	\}\
\
	
\b0\i\fc1\cf1 // Get the target's parent directory
\b\i0\fc0\cf0 \
	path
\fc1\cf1 name = NXCopyStringBuffer(ta
\fc0\cf0 rgetName);\
	tmpPtr = 
\fc1\cf1 strrchr
\fc0\cf0 (pathname, '/');\
	if( tmpPtr == 0 || tmpPtr == pathname )\
	\{	
\b0\i /* This is a link to file in the same directory. It should\
			have been resolved in dirListing:ftpd:. Since it was not\
			This must be a bad link. We set the link target to self\
			to indicate the link cannot be resolved. */
\b\i0 \
		[self setLinkTarget: self];\
		return nil;\
	\}\
	
\b0\i // Terminate parent directory and advance to link name
\b\i0 \
	*tmpPtr = '\\0';\
	tmpPtr ++;\
\
	
\b0\i // See if the parent directory is in the FTPObject cache
\b\i0 \
	
\fc1\cf1 parentDir
\fc0\cf0  = [ftpd fileFromPath: pathname];\
	if( 
\fc1\cf1 parentDir
\fc0\cf0  != nil )\
	\{	
\b0\i\fc1\cf1 // We have the parent. Check its dir listing
\b\i0\fc0\cf0 \
	ObjectList *parentListing = [
\fc1\cf1 parentDir dirListing];\
		if( 
\fc0\cf0 parentListing
\fc1\cf1  == 0 )\
		\{	
\b0\i // Load the parent dir contents
\b\i0 \
			[parentDir ftpLoadDir: ftpd];\
		\}\
		
\b0\i // Get the link target from listing
\b\i0 \
		lnTarget = [parentDir fileFromPath: tmpPtr];
\fc0\cf0 \
		return lnTarget;\
	\}\
\
	
\b0\i /* Create an FtpFile for the parent dir and load its contents\
		to obtain the link target */
\b\i0 \
	parentDir = [[FtpFile alloc] ftpInitDir: pathname host: sourceHost\
		ftpd: ftpd info: ftpInfo];\

\fc1\cf1 	[
\fc0\cf0 parentDir
\fc1\cf1  s
\fc0\cf0 etDelegate: delegate];\

\fc1\cf1 	
\b0\i // Get the link target from listing
\b\i0 \
	lnTarget = [parentDir fileFromPath: tmpPtr];
\fc0\cf0 \
\
	
\b0\i\fc1\cf1 /* Add this orphan dir to the ftpd list. The orphan dir list is\
		composed of those directories that have been loaded to as\
		a result of link resolution that have not had their parent\
		located. */
\b\i0 \
	[ftpd addOrphanDir: parentDir];\

\fc0\cf0 \
	retur
\fc1\cf1 n 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 lnTarget
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 ;\

\pard\tx480\tx960\tx1440\tx1920\tx2400\tx2880\tx3360\tx3840\tx4320\tx4800\fc0\cf0 \} 
\b0\i\fc1\cf1 // End resolveLink: ftpd:
\b\i0\fc0\cf0 \
\
@end\

}
