home *** CD-ROM | disk | FTP | other *** search
/ Super Net 1 / SUPERNET_1.iso / PC / OTROS / UNIX / ARCHIE / CLIENTS / NEXTARCH.TAR / NeXTArchie / FTPManager.m < prev    next >
Encoding:
Text File  |  1992-01-26  |  27.1 KB  |  1,005 lines

  1. #import <FTPManager.h>
  2. #import <ClockView.h>
  3. #import <PercentView.h>
  4. #import <ProsperoVLINK.h>
  5.  
  6. #import <objc/NXStringTable.h>
  7. #import <appkit/Application.h>
  8. #import <appkit/Matrix.h>
  9. #import <appkit/OpenPanel.h>
  10. #import <appkit/appkit.h>
  11. #import <appkit/nextstd.h>
  12. #import <ctype.h>
  13. #import <fcntl.h>
  14. #import <regex.h>
  15. #import <signal.h>
  16. #import <sys/param.h>
  17. #import <sys/types.h>
  18. #import <sys/stat.h>
  19.  
  20. #define DEFAULT_DIR "."
  21. #define NIB_PATH "FTPManager.nib"
  22. /* The owner of the application defaults */
  23. extern const char *NeXTArchieOwner;
  24.  
  25. /* Code returned when modal loop is aborted */
  26. #define ABORT_TRANSFER 1
  27.  
  28. /* Number of calls to the dirListingTE before we say quit */
  29. #define LISTING_TIMEOUT_COUNT 6    // = 30 seconds
  30.  
  31. /* Import the error message NXStringTable keys */
  32. #import "errMessages.keys"
  33.  
  34. /* Ftp command result codes */
  35. #define CONNECTED            220
  36. #define LOGIN_OK                 331
  37. #define PASSWORD_OK             230
  38. #define CWD_OK                 250
  39. #define CWD_OK2                 200
  40. #define TRANSFER_COMPLETE    226
  41. #define GOODBYE                221
  42. #define DATA_CONNECTION    150
  43. #define PORT_OK                200
  44. /* Ftp error codes I know of, most from looking at ftpd.c source */
  45. #define FTP_ERR0                500
  46. #define FTP_ERR1                502
  47. #define FTP_ERR2                503
  48. #define FTP_ERR3                530
  49. #define FTP_ERR4                550
  50. #define FTP_ERR5                551
  51. #define FTP_ERR6                553
  52. #define FTP_ERR7                421    // Some hosts send this if there are too many users
  53.  
  54. /* ftpMode flag defines */
  55. #define BEGIN_SESSION            10
  56. #define CONNECT_SUCCESS        11
  57. #define USER_LOGIN            20
  58. #define LOGIN_SUCCESS        21
  59. #define USER_PASSWORD        30
  60. #define PASSWORD_SUCCESS    31
  61. #define REMOTE_CHDIR        40
  62. #define CHDIR_SUCCESS        41
  63. #define GET_TARGET_SIZE        50
  64. #define SIZE_SUCCESS            51
  65. #define FILE_TRANSFER        60
  66. #define TRANSFER_SUCCESS    61
  67. #define FILE_LISTING            62
  68. #define LISTING_SUCCESS        63
  69. #define DISCONNECT            70
  70. #define DISCONNECT_SUCCESS    71
  71. #define FTP_ERROR                100
  72. #define FTP_SELF_ERROR        110
  73.  
  74. /* Debugging level defines */
  75. #define FTP_RESULT     4    // Write results from ftp program
  76. #define FTP_COMMAND    8    // Write command sent to ftp program
  77. #define LOW    10
  78. #define MED    20
  79. #define HIGH    30
  80. #define MEGA    40
  81.  
  82. /* Ftp output buffers and variables */
  83. static char *ftpFullErrMessage;
  84. static char ftpErrMessage[128];
  85. static char *listingBuffer;
  86. static int listingSize,listingBufferSize;
  87.  
  88. /* The code number of the last ftp output result */
  89. static int lastFtpResult;
  90. /* Regular expression structs for patterns of interest */
  91. static struct regex *ftpResultExpr;
  92. static struct regex *unreachableNetworkExpr;
  93. static struct regex *transferSizeExpr;
  94. static struct regex *unkownHostExpr;
  95. static struct regex *transferCompleteExpr;
  96.  
  97. /* Timed entry prototypes */
  98. void FtpDaemon(DPSTimedEntry te, double timeNow, FTPManager *self);
  99. void ListingDaemon(DPSTimedEntry te, double timeNow, FTPManager *self);
  100.  
  101. /* Import routines which parse the ftp dir command output into DirEntry's. */
  102. #import "ftpParse.c"
  103.  
  104. @implementation FTPManager
  105.  
  106. - init
  107. {
  108.     [super init];
  109.     getwd(ftpDirectory);
  110.     return self;
  111. }
  112.  
  113. - abortTransfer: sender
  114. {
  115.     kill(ftpProcessPID,SIGKILL);
  116.     [NXApp stopModal : ABORT_TRANSFER];
  117.  
  118.     return self;
  119. }
  120.  
  121. - displayErrOutput: sender
  122. {
  123. id textField;
  124.     /* Display the error panel and display the last ftp output in
  125.         its textfield */
  126.     textField = [ftpErrViewID docView];
  127.     [textField setText: ftpFullErrMessage];
  128.     [ftpErrPanelID makeKeyAndOrderFront: self];
  129.     return self;
  130. }
  131.  
  132. - setLocalDir: sender
  133. {
  134.     [self setLocalPath: ftpDirectory file: NULL panel: YES];
  135.     return self;
  136. }
  137.  
  138. - setLocalPath: (const char *) dirName file: (const char *) fileName panel: (BOOL) display;
  139. {
  140. int saveResult,fileLength;
  141.         
  142.     if(display == YES)
  143.     {
  144.         if(fileName == NULL)
  145.         {    // Set local directory
  146.             if(pwdPanelID == nil)
  147.             {
  148.                 pwdPanelID = [OpenPanel new];
  149.                 if(pwdPanelID == nil)
  150.                 return [self error: SORRY key: OPEN_PANEL_FAILED];
  151.             }
  152.             [pwdPanelID setTitle: "Set Local Directory"];
  153.             saveResult = [pwdPanelID runModalForDirectory:ftpDirectory file:NULL];
  154.             if(saveResult == 1)
  155.             {
  156.                 strcpy(ftpDirectory,[pwdPanelID directory]);
  157.                 return self;
  158.             }
  159.             return nil;
  160.         }
  161.         else
  162.         {    // Prompt the user for the filename
  163.             if(saveAsPanelID == nil)
  164.             {
  165.                 saveAsPanelID = [SavePanel new];
  166.                 if(saveAsPanelID == nil)
  167.                 return [self error: SORRY key: SAVE_PANEL_FAILED];
  168.             }
  169.             saveResult = [saveAsPanelID  runModalForDirectory: (const char *) ftpDirectory 
  170.                         file:    fileName];
  171.             if(saveResult == 1)
  172.             {
  173.                 strcpy(ftpDirectory,[saveAsPanelID directory]);
  174.                 fileName = strrchr([saveAsPanelID filename],'/');
  175.                 fileName ++;
  176.             }
  177.             else
  178.                 return nil;
  179.         }
  180.     }
  181.     
  182.     /* Copy the local file name to fileName */
  183.     fileLength = strlen(ftpDirectory) + strlen(fileName) + 1;
  184.     if(localFilePath)
  185.         NX_FREE(localFilePath);
  186.     NX_MALLOC(localFilePath,char, fileLength);
  187.     if(localFilePath == NULL)
  188.         return [self error: ALERT key: LOCALPATH_ALLOC_FAILED];
  189.  
  190.     sprintf(localFilePath,"%s/%s",ftpDirectory,fileName);
  191.  
  192.     return self;
  193. }
  194.  
  195. /* Write a command line to the ftp program */
  196. - writeFtp : sender
  197. {
  198.     [self writeFtpCommand: "%s", [sender stringValue]];
  199.     return self;
  200. }
  201. - writeFtpCommand : (char *) formatString, ...
  202. {
  203. va_list  ap;
  204.  
  205.     va_start(ap, formatString);
  206.     vfprintf(stdout, formatString,ap);
  207. if(ftpDebugLevel > FTP_COMMAND)
  208.     vfprintf(errFile,formatString,ap);
  209.     va_end(ap);
  210.  
  211.     return self;
  212. }
  213.  
  214. /* Cover methods for the full blown runModal::::: */
  215. - runModal: (const char *) hostName get: (const char *) targetPathName
  216. {
  217.     [self runModal: hostName get: targetPathName anon: YES
  218.         write: NO binary: YES vlink: nil];
  219.     return self;
  220. }
  221. - runModal: (const char *) hostName get: (const char *) targetPathName dir: (BOOL) listing
  222. {
  223.     [self runModal: hostName get: targetPathName anon: YES
  224.         write: NO binary: YES vlink: nil];
  225.     return self;
  226. }
  227. - runModal: (const char *) hostName get: (const char *) targetPathName anon: (BOOL) anonymous
  228. {
  229.     [self runModal: hostName get: targetPathName anon: anonymous
  230.         write: NO binary: YES vlink: nil];
  231.     return self;
  232. }
  233.  
  234. - runModal: (const char *) hostName get: (const char *) targetPathName anon: (BOOL) anonymous
  235.     write: (BOOL) overWrite binary: (BOOL) mode vlink: dirVLINK
  236. {
  237. /* pty channels as returned from Stevens pty code */
  238. int masterChannel,slaveChannel;
  239. int pty_master(), pty_slave();
  240. int currentFlags,lastSize, modalStatus = 0;
  241. float currentSize,percent;
  242. NXModalSession theSession;
  243. struct stat transferStatus;
  244. char localHostname[64],*localUsername;
  245. char *remoteDirectory,*remoteFilename,*tmpBuffer,sizeBuffer[16];
  246.  
  247.     /* Ignore the anonymous flag for now */
  248.  
  249.     /* Load my nib panel */
  250.     if(ftpPanelID == nil)
  251.     {
  252.         if([NXApp loadNibSection: NIB_PATH owner: self withNames: NO] == nil)
  253.             [self error: ALERT key: FTP_PANEL_FAILED];
  254.     }
  255.  
  256.     /* Set the errFile to stderr */
  257.     errFile = stderr;
  258.     setvbuf(errFile,NULL,_IOLBF,0);
  259.     /* Read the FtpDebugLevel defaults setting */
  260.     ftpDebugLevel = atoi(NXGetDefaultValue(NeXTArchieOwner, "FtpDebugLevel"));
  261.  
  262.     /* Write a message logging the start of the ftp session */
  263.     [self debug: 0, "Starting ftp session: UNIX clock = %d\n",time( (long *) 0)];
  264.  
  265.     /* Open the master pty and set raw mode */
  266.     masterChannel = pty_master();
  267.     currentFlags =  fcntl(masterChannel,F_GETFL,0);
  268.     fcntl(masterChannel,F_SETFL,currentFlags | O_NDELAY);
  269.     /* Fork the child process */
  270.     if( (ftpProcessPID = vfork()) < 0)
  271.         return [self error: ALERT key: FTP_FORK_FAILED];
  272.  
  273.     /* Reset the parents stdin and stdout to the pty channel */
  274.     if(ftpProcessPID > 0)
  275.     {
  276.         close(0);
  277.         close(1);
  278.         dup(masterChannel);    // Set this as stdin
  279.         dup(masterChannel);    // & to stdout
  280.         /* Setup line oriented communication */
  281.         setvbuf(stdin,NULL,_IOLBF,0);
  282.     }
  283.     else
  284.     {    /* The child process opens the slave pty */
  285.     int controlTerm;
  286.         slaveChannel = pty_slave(masterChannel);
  287.         if(slaveChannel < 0)
  288.         {
  289.             fprintf(stderr,"Failed to open pty slave\n");
  290.             _exit(1);
  291.         }
  292.         close(masterChannel);
  293.         /* Get rid of the controlling terminal so that password prompts
  294.             are redirected to the pty. */
  295.         if( (controlTerm = open("/dev/tty", O_RDWR)) >= 0 )
  296.         {
  297.             ioctl(controlTerm,TIOCNOTTY,NULL);
  298.             close(controlTerm);
  299.         }
  300. [self debug: LOW, "Starting ftp...\n"];
  301.  
  302.         /* Set std everything to the slave pty */
  303.         close(0);
  304.         close(1);
  305.         close(2);
  306.         dup(slaveChannel);    // Set this as stdin
  307.         dup(slaveChannel);    // & stdout
  308.         dup(slaveChannel);    // & stderr
  309.         /* Start the ftp program */
  310.         execlp("ftp","ftp", "-n", hostName,NULL);
  311.         fprintf(stderr,"Failed to exec ftp\n");
  312.         _exit(1);
  313.     }
  314.  
  315.     /* Determine the remote and local file names */
  316.     if(dirVLINK == nil)
  317.     {
  318.         remoteFilename = strrchr(targetPathName,'/');  remoteFilename ++;
  319.     [self debug: LOW, "remoteFilename: %s\n", remoteFilename];
  320.         [self setLocalPath: ftpDirectory file: remoteFilename panel: NO];
  321.     [self debug: LOW, "localFilePath: %s\n", localFilePath];
  322.         
  323.         NX_MALLOC(tmpBuffer,char,strlen(targetPathName)+1);
  324.         if(tmpBuffer == NULL)
  325.         {
  326.             kill(ftpProcessPID,SIGKILL);
  327.             return [self error: ALERT key: REMOTE_ALLOC_FAILED];
  328.         }
  329.         strcpy(tmpBuffer, targetPathName);
  330.         remoteDirectory = tmpBuffer;
  331.         remoteDirectory[strlen(targetPathName) - strlen(remoteFilename)] = '\0';
  332.     [self debug: LOW, "remoteDirectory: %s\n", remoteDirectory];
  333.     }
  334.     else
  335.     {
  336.         remoteDirectory = targetPathName;
  337.         remoteFilename = NULL;
  338.         localFilePath = NULL;
  339.         tmpBuffer = NULL;
  340.     }
  341.  
  342.     /* Add a timed entry for reading & displaying child's output */
  343.     ftpMode = BEGIN_SESSION;
  344.     ftpDaemonTE = DPSAddTimedEntry(1.0, FtpDaemon,self,
  345.                         NX_MODALRESPTHRESHOLD);
  346.  
  347.     /* Initialize some variables */
  348.     transferComplete = NO;
  349.     lastSize = 0;
  350.     lastFtpResult = 0;
  351.     listingTEStarted = NO;
  352.     listingTECalls = 0;
  353.     dirListingTE = (DPSTimedEntry) 0;
  354.     ftpResultExpr = re_compile("[1-5][0-9][0-9] ",0);
  355.     unreachableNetworkExpr = re_compile("Network.*unreachable",1);
  356.     transferSizeExpr = re_compile("[0-9]* bytes",1);
  357.     unkownHostExpr = re_compile("Unknown.*Host",1);
  358.     transferCompleteExpr =  re_compile("226.*Transfer",1);
  359.  
  360.     /* Here we go */
  361. [self debug: MEGA, "FTPM: Begging modal session\n"];
  362.     [statusBoxID removeFromSuperview];
  363.     [timerViewID startMinSecTimer: self];
  364.     [hostnameFieldID setStringValue: hostName];
  365.     [messageFieldID setStringValue: "Connecting to remote host..."];
  366.     [ftpPanelID display];
  367.     [NXApp beginModalSession: &theSession for: ftpPanelID];
  368.     while(transferComplete == NO)
  369.     {
  370.         modalStatus = [NXApp runModalSession: &theSession];
  371.         if(modalStatus != NX_RUNCONTINUES)
  372.             break;
  373.         switch(ftpMode)
  374.         {
  375.             case CONNECT_SUCCESS :
  376.                 ftpMode = USER_LOGIN;
  377. [self debug: MED, "FTPM: USER_LOGIN\n"];
  378.                 [messageFieldID setStringValue: "Connected to remote host..."];
  379.                 /* Initiate anonymous login */
  380.                 [self writeFtpCommand: "user anonymous\n"];
  381.                 break;
  382.             case LOGIN_SUCCESS :
  383.                 ftpMode = USER_PASSWORD;
  384. [self debug: MED, "FTPM: USER_PASSWORD\n"];
  385.                 /* Send user@hostname as password */
  386.                 gethostname(localHostname,63);
  387.                 localHostname[63] = '\0';
  388.                 localUsername = (char *) NXUserName();
  389.                 [self writeFtpCommand: "%s@%s\n",localUsername,localHostname];
  390.                 break;
  391.             case PASSWORD_SUCCESS :
  392.                 ftpMode = REMOTE_CHDIR;
  393. [self debug: MED, "FTPM: REMOTE_CHDIR\n"];
  394.                 [messageFieldID setStringValue: "Anonymous login successful..."];
  395.                 [self writeFtpCommand: "cd %s\n", remoteDirectory];
  396.                 if(mode == YES)
  397.                     [self writeFtpCommand: "binary\n"];
  398.                 else
  399.                     [self writeFtpCommand: "ascii\n"];
  400.                 break;
  401.             case CHDIR_SUCCESS :
  402.                 if(dirVLINK == nil)
  403.                 {
  404.                         ftpMode = FILE_TRANSFER;
  405.     [self debug: MED, "FTPM: FILE_TRANSFER\n"];
  406.                     [messageFieldID setStringValue: "Retrieving remote file..."];
  407.                     if( stat(localFilePath,&transferStatus) != -1)
  408.                     {
  409.                         if(overWrite == NO)
  410.                         {
  411.                         int alertRslt = NXRunAlertPanel(NULL,
  412.                             "The local file: %s exists.  Overwrite?",
  413.                                     "Yes","Save As...","Cancel",remoteFilename);
  414.                             switch(alertRslt)
  415.                             {
  416.                                 case NX_ALERTDEFAULT :
  417.     [self debug: HIGH, "FTPM: AlertPanel = NX_ALERTDEFAULT\n"];
  418.                                     truncate(localFilePath,0);
  419.                                     break;
  420.                                 case NX_ALERTALTERNATE :
  421.     [self debug: HIGH, "FTPM: AlertPanel = NX_ALERTALTERNATE\n"];
  422.                                     if([self setLocalPath: ftpDirectory file: remoteFilename 
  423.                                         panel: YES] == nil)
  424.                                     {
  425.                                         [self abortTransfer: self];
  426.                                         break;
  427.                                     }
  428.                                     /* In case the user changed their mind */
  429.                                     if( stat(localFilePath,&transferStatus) != -1)
  430.                                         truncate(localFilePath,0);
  431.                                     break;
  432.                                 case NX_ALERTOTHER :
  433.     [self debug: HIGH, "FTPM: AlertPanel = NX_ALERTOTHER\n"];
  434.                                 case NX_ALERTERROR :
  435.                                     [self abortTransfer: self];
  436.                                     break;
  437.                                 }
  438.                         }
  439.                     }
  440.                     else
  441.                         truncate(localFilePath,0);
  442.                     [self writeFtpCommand: "get %s %s\n", remoteFilename, localFilePath];
  443.                     [sizeFieldID setStringValue: "0"];
  444.                     [statusViewID displayPercent: 0.0];
  445.                     [[[hostnameFieldID window] contentView] addSubview: statusBoxID];
  446.                     [ftpPanelID display];
  447.                 }
  448.                 /* Else get the output from the dir command */
  449.                 else
  450.                 {
  451.                     [messageFieldID setStringValue: "Retrieving remote directory listing..."];
  452.                     listingSize = 0;
  453.                     listingBufferSize = 1024;
  454.                     NX_MALLOC(listingBuffer,char,listingBufferSize);
  455.                     if(listingBuffer == NULL)
  456.                         return [self error: ALERT key: DIRLISTING_ALLOC_FAILED];
  457.                     listingBuffer[0] = '\0';
  458.                     ftpMode = FILE_LISTING;
  459.                     [self writeFtpCommand: "dir\n"];
  460.                 }
  461.                 break;
  462.             case TRANSFER_SUCCESS :
  463.             case LISTING_SUCCESS :
  464.                 ftpMode = DISCONNECT;
  465. [self debug: MED, "FTPM: DISCONNECT\n"];
  466.                 if(dirVLINK == nil)
  467.                 {
  468.                     [messageFieldID setStringValue: "Transfer complete."];
  469.                     [statusViewID displayPercent: 1.0];
  470.                 }
  471.                 else
  472.                 {
  473.                     DPSRemoveTimedEntry(dirListingTE);
  474.                     dirListingTE = (DPSTimedEntry) 0;
  475.                     [messageFieldID setStringValue: "Listing complete."];
  476.                 }
  477.                 [self writeFtpCommand: "bye\n"];
  478.                 break;
  479.             case DISCONNECT_SUCCESS:
  480.             case FTP_ERROR :
  481.                 transferComplete = YES;
  482.                 break;
  483.             default:
  484.                 break;
  485.         }
  486.  
  487.         /* Compute the local files size and update the percent completion */
  488.         if(ftpMode >= FILE_TRANSFER && dirVLINK == nil)
  489.         {
  490.             if( stat(localFilePath,&transferStatus) != -1)
  491.             {
  492.             BOOL update;
  493.                 currentSize = transferStatus.st_size;
  494.                 sprintf(sizeBuffer,"%d bytes",(int)currentSize);
  495.                 [sizeFieldID setStringValue: sizeBuffer];
  496.                 if(targetFileSize > 0)
  497.                 {
  498.                     percent = currentSize / (float)targetFileSize;
  499. [self debug: MEGA,"FTPM: File transfer percent = %f\n",percent];
  500.                     if(currentSize > lastSize)
  501.                     {
  502.                         lastSize = currentSize;
  503.                         update = NO;
  504.                         if(percent <= 1.0)
  505.                         {    // Just in case the size was parsed incorrectly
  506.                             if([statusViewID displayPercent: percent] != nil)
  507.                                 update = YES;
  508.                         }
  509. [self conditionalDebug: update : HIGH, "size = %d; percent = %f\n", lastSize, percent];
  510.                     }
  511.                 }
  512.             }
  513.         }
  514.     }
  515.     /* Remove the timed entry */
  516.     DPSRemoveTimedEntry(ftpDaemonTE);
  517.     if(dirListingTE)
  518.         DPSRemoveTimedEntry(dirListingTE);
  519.     [timerViewID endTimer: self];
  520.  
  521.     /* Set the indicators to their final state */
  522.     if(ftpMode != FTP_ERROR && dirVLINK == nil)
  523.     {
  524.         if(modalStatus != ABORT_TRANSFER)
  525.         {
  526.             if( targetFileSize > 0)
  527.             {
  528.                 sprintf(sizeBuffer,"%d bytes", targetFileSize);
  529.                 [sizeFieldID setStringValue: sizeBuffer];
  530.             }
  531.         }
  532.         else if(modalStatus == ABORT_TRANSFER)
  533.         {
  534.             [statusBoxID removeFromSuperview];
  535.             [messageFieldID setStringValue: "Transfer Aborted."];
  536.             [ftpPanelID display];
  537.             /* Remove the trasfer file */
  538.             unlink(localFilePath);
  539.         }
  540.     }
  541.  
  542.     /* End the modal session */    
  543.     [NXApp endModalSession: &theSession];
  544.     /* Order out the panel after a slight delay to make sure the final
  545.         state was indicated */
  546.     if(dirVLINK == nil)
  547.         [ftpPanelID perform: @selector(orderOut:) with: self 
  548.             afterDelay: 2000 cancelPrevious: NO];
  549.     else
  550.         [ftpPanelID orderOut: self];
  551.  
  552.     /* Display an alert if the FTP_ERROR mode is set */
  553.     if(ftpMode == FTP_ERROR)
  554.     {
  555.     int alertRslt;
  556.         [self writeFtpCommand: "bye\n"];
  557.         alertRslt = NXRunAlertPanel(NULL,"An ftp error occurred, the error was:\n\t[%s]",
  558.             "Ok","Info...",NULL,ftpErrMessage);
  559.         if(alertRslt == NX_ALERTALTERNATE)
  560.             [self displayErrOutput: self];
  561.         /* Remove the trasfer file. Maybe I should prompt the user? */
  562.         unlink(localFilePath);
  563.     }
  564.     else if(dirVLINK != nil && modalStatus != ABORT_TRANSFER)
  565.     {
  566.         /* Parse the listingBuffer to generate the DirEntry linked list */
  567.         if( [dirVLINK setListing: ParseBuffer(listingBuffer)] == nil)
  568.             [self error: ALERT key: SETLISTING_FAILED];
  569.     }
  570.  
  571. [self debug: MEGA,"FTPM: Done.\n"];
  572.  
  573.     NX_FREE(tmpBuffer);
  574.     NX_FREE(listingBuffer);
  575.     NX_FREE(ftpFullErrMessage);
  576.     NX_FREE(localFilePath);
  577.     localFilePath = NULL;
  578.     ftpFullErrMessage = NULL;
  579.     listingBuffer = NULL;
  580.  
  581.     return self;
  582. }
  583.  
  584. void FtpDaemon(DPSTimedEntry te, double timeNow, FTPManager *self)
  585. {
  586. int n;
  587. char ftpRsltBuffer[1024],*bufferPtr,*tmpPtr,_150_RsltBuffer[128];
  588. BOOL condition;
  589. BOOL ValidFTPCode(int code);
  590. int GetSize(char *buffer,FTPManager *self);
  591. char *FTPResultCode(char *buffer,int *result);
  592. static double byeTimeOut;
  593.  
  594.     /* Don't care about anything after the ``221 Goodbye..'' code */
  595.      if(self->transferComplete == YES)
  596.         return;
  597.  
  598.     /* Read the ftp channel and display it in status TextField */
  599.     n = read(0, ftpRsltBuffer,1023);
  600.     if(n == -1) n ++;
  601.     ftpRsltBuffer[n] = '\0';
  602.     _150_RsltBuffer[0] = '\0';
  603.     condition = (n > 0);
  604. [self conditionalDebug: condition : FTP_RESULT, "FTPD: %s\n", ftpRsltBuffer];
  605.  
  606.     /* Check the output for the ``ftp: connect: Network is unreachable'' message,
  607.         and the ``... unkown host'' message since ftp does not return an
  608.         error code(or any) for this */
  609.     if(re_match(ftpRsltBuffer, unreachableNetworkExpr) == 1)
  610.     {
  611.         strcpy(ftpErrMessage,"Ftp claims this network is unreachable");
  612.         if(ftpFullErrMessage != NULL)
  613.             NX_FREE(ftpFullErrMessage);
  614.         NX_MALLOC(ftpFullErrMessage,char,strlen(ftpRsltBuffer)+1);
  615.         if(ftpFullErrMessage == NULL)
  616.         {
  617.             [self error: ALERT key: ERRBUFFER_ALLOC_FAILED];
  618.             // User chose not to abort
  619.             self->ftpMode = FTP_SELF_ERROR;
  620.             return;
  621.         }
  622.         strcpy(ftpFullErrMessage, ftpRsltBuffer);
  623.         self->ftpMode = FTP_ERROR;
  624.         return;
  625.     }
  626.     else if(re_match(ftpRsltBuffer, unkownHostExpr) == 1)
  627.     {
  628.         sprintf(ftpErrMessage,"Ftp claims the host:%s is unkown",
  629.             [self->hostnameFieldID stringValue]);
  630.         if(ftpFullErrMessage != NULL)
  631.             NX_FREE(ftpFullErrMessage);
  632.         NX_MALLOC(ftpFullErrMessage,char,strlen(ftpRsltBuffer)+1);
  633.         if(ftpFullErrMessage == NULL)
  634.         {
  635.             [self error: ALERT key: ERRBUFFER_ALLOC_FAILED];
  636.             // User chose not to abort
  637.             self->ftpMode = FTP_SELF_ERROR;
  638.             return;
  639.         }
  640.         strcpy(ftpFullErrMessage, ftpRsltBuffer);
  641.         self->ftpMode = FTP_ERROR;
  642.         return;
  643.     }
  644.  
  645.     /* See if we are looking for a directory listing, and if so,
  646.         copy the output  to the listing buffer.  Also, the only ftp result
  647.         code we are looking for is the ``226 Transfer complete'' message.
  648.         The simple approach used in the following while loop will match too many
  649.         things, so we do the regex matching here.  One problem with this is that if
  650.         the transfer does not complete, we can get stuck here so we start
  651.         another timed entry to make sure that the size of the listingBuffer continues
  652.         to grow.  All of these special case situations are beginning to make my parsing
  653.         approach rather inelegant.  I need to figure a more robust, yet simple method
  654.         of parsing the ftp output. */
  655.     if(self->ftpMode == FILE_LISTING && n > 0)
  656.     {
  657.         /* Start the listing TE if not already running */
  658.         if(self->listingTEStarted == NO)
  659.         {
  660.             self->dirListingTE  = DPSAddTimedEntry(5.0, ListingDaemon,self,
  661.                         NX_MODALRESPTHRESHOLD);
  662.             self->listingTEStarted = YES;
  663.         }
  664.  
  665.         /* Check to see that the listing buffer size is large enough */
  666.         listingSize += n;
  667.         if(listingSize >= listingBufferSize)
  668.         {
  669.             listingBufferSize = listingSize + 512;
  670.             NX_REALLOC(listingBuffer,char, listingBufferSize);
  671.             if(listingBuffer == NULL)
  672.             {
  673.                 [self error: ALERT key: DIRLISTING_REALLOC_FAILED];
  674.                 // User chose not to abort
  675.                 self->ftpMode = FTP_SELF_ERROR;
  676.                 return;
  677.             }
  678.         }
  679.         strcat(listingBuffer,ftpRsltBuffer);
  680.         
  681.         /* Check for the transfer complete code */
  682.         if(re_match(ftpRsltBuffer,transferCompleteExpr) == 1)
  683.             lastFtpResult = 226;
  684.  
  685.         /* Bypass the normal ftp code checking */
  686.         ftpRsltBuffer[0] = '\0';
  687.     }
  688.  
  689.     /* Scan the ftpRsltBuffer for the ftp result numbers */
  690.     bufferPtr = ftpRsltBuffer;
  691.     while( (tmpPtr = FTPResultCode(bufferPtr,&lastFtpResult)) != NULL)
  692.     {
  693.         bufferPtr = NULL;
  694.         /* Check for an error code and save the ftp output if there was one */
  695.         if(lastFtpResult >= 500)
  696.         {
  697.             sscanf((tmpPtr-4),"%[^\n]",ftpErrMessage);
  698.             if(ftpFullErrMessage != NULL)
  699.                 NX_FREE(ftpFullErrMessage);
  700.             NX_MALLOC(ftpFullErrMessage,char,strlen(ftpRsltBuffer)+1);
  701.             if(ftpFullErrMessage == NULL)
  702.             {
  703.                 [self error: ALERT key: ERRBUFFER_ALLOC_FAILED];
  704.                 // User chose not to abort
  705.                 self->ftpMode = FTP_SELF_ERROR;
  706.                 return;
  707.             }
  708.             strcpy(ftpFullErrMessage, ftpRsltBuffer);
  709.             self->ftpMode = FTP_ERROR;
  710.             return;
  711.         }
  712.     
  713.         /* See if this is the ``150 Opening..'' command that has the file size */
  714.         if(self->ftpMode == FILE_TRANSFER && lastFtpResult == DATA_CONNECTION)
  715.         {
  716.             if(_150_RsltBuffer[0] == '\0')    // True if not already filled
  717.                     sscanf(tmpPtr,"%[^\n]\n",_150_RsltBuffer);
  718. [self debug: MED,"FTPD: _150_RsltBuffer: %s\n", _150_RsltBuffer];
  719.         }
  720.  
  721. [self debug: HIGH, "FTPD_loop: lastFtpResult = %d\n", lastFtpResult];
  722.         condition = (lastFtpResult != 0);
  723. [self conditionalDebug: condition : MED, "FTPD_loop: lastFtpResult = %d\n", lastFtpResult];
  724.     }
  725.  
  726. [self debug: MEGA, "FTPD_last: lastFtpResult = %d\n", lastFtpResult];
  727.  
  728. [self debug: MEGA,"FTPD: ftpMode = %d\n", self->ftpMode];
  729.     switch(self->ftpMode)
  730.     {
  731.         case BEGIN_SESSION :
  732.             if(lastFtpResult == CONNECTED)
  733.             {
  734.                 lastFtpResult = 0;
  735.                 self->ftpMode = CONNECT_SUCCESS;
  736. [self debug: MED, "FTPD: CONNECT_SUCCESS \n"];
  737.             }
  738.             break;
  739.         case USER_LOGIN :
  740.             if(lastFtpResult == LOGIN_OK)
  741.             {
  742.                 lastFtpResult = 0;
  743.                 self->ftpMode = LOGIN_SUCCESS;
  744. [self debug: MED, "FTPD: LOGIN_SUCCESS \n"];
  745.             }
  746.             break;
  747.         case USER_PASSWORD :
  748.             if(lastFtpResult == PASSWORD_OK)
  749.             {
  750.                 lastFtpResult = 0;
  751.                 self->ftpMode = PASSWORD_SUCCESS;
  752. [self debug: MED, "FTPD: PASSWORD_SUCCESS \n"];
  753.             }
  754.             break;
  755.         case REMOTE_CHDIR :
  756.             if(lastFtpResult == CWD_OK || lastFtpResult == CWD_OK2)
  757.             {
  758.                 lastFtpResult = 0;
  759.                 self->ftpMode = CHDIR_SUCCESS;
  760. [self debug: MED, "FTPD: CHDIR_SUCCESS \n"];
  761.             }
  762.             break;
  763.         case FILE_TRANSFER :
  764.             if(lastFtpResult == DATA_CONNECTION && _150_RsltBuffer[0] != '\0')
  765.             {
  766.                 /* Parse the dir to get the file size */
  767.                 self->targetFileSize = GetSize(_150_RsltBuffer,self);
  768. [self debug: MED, "FTPD: targetSize = %d \n",self->targetFileSize];
  769.             }
  770.             else if(lastFtpResult == TRANSFER_COMPLETE)
  771.             {
  772.                 lastFtpResult = 0;
  773.                 self->ftpMode = TRANSFER_SUCCESS;
  774. [self debug: MED, "FTPD: TRANSFER_SUCCESS \n"];
  775.             }
  776.             break;
  777.         case FILE_LISTING :
  778.             if(lastFtpResult == TRANSFER_COMPLETE)
  779.             {
  780.                 lastFtpResult = 0;
  781.                 self->ftpMode = LISTING_SUCCESS;
  782.                 /* Prepare the disconnect time out variable */
  783.                 byeTimeOut = timeNow;
  784.             }
  785.             break;
  786.         case DISCONNECT :
  787.             if(lastFtpResult == GOODBYE || (timeNow - byeTimeOut) > 5.0 )
  788.             {
  789.                 lastFtpResult = 0;
  790.                 self->ftpMode = DISCONNECT_SUCCESS;
  791. [self debug: MED, "FTPD: DISCONNECT_SUCCESS \n"];
  792.                 self->transferComplete = YES;
  793. [self debug: HIGH, "FTPD: Set transferComplete\n"];
  794.             }
  795.             break;
  796.     }
  797. }
  798.  
  799. char *FTPResultCode(char *ftpRsltBuffer,int *result)
  800. {
  801. int ftpCode;
  802. char codeString[5];
  803. static char *searchBuffer;
  804. BOOL ValidFTPCode(int code);
  805.  
  806.     /* Go through the buffer looking for patterns "[1-5][0-9][0-9] " */
  807.     if(ftpRsltBuffer != NULL && ftpRsltBuffer[0] != '\0')
  808.         searchBuffer = ftpRsltBuffer;
  809.     else if(ftpRsltBuffer != NULL)
  810.         return NULL;
  811.  
  812.     if(re_match(searchBuffer, ftpResultExpr) == 1)
  813.     {
  814.         /* Move the search pointer past the code string */
  815.         searchBuffer = ftpResultExpr->start + 4;
  816.         strncpy(codeString, ftpResultExpr->start,4);
  817.         codeString[4] = '\0';
  818.         ftpCode = atoi(codeString);
  819.         /* Check the code number */
  820.         if(ValidFTPCode(ftpCode) == YES)
  821.         {
  822.             *result = ftpCode;
  823.             return searchBuffer;
  824.         }
  825.         else
  826.             return FTPResultCode(NULL,result);
  827.     }
  828.     searchBuffer = NULL;
  829.     return searchBuffer;
  830. }
  831.  
  832. BOOL ValidFTPCode(int code)
  833. {
  834.     /* Check this code against those I am interested in */
  835.     switch(code)
  836.     {
  837.         case CONNECTED:
  838.         case LOGIN_OK:
  839.         case PASSWORD_OK:
  840.         case CWD_OK:
  841.         case TRANSFER_COMPLETE:
  842.         case GOODBYE:
  843.         case DATA_CONNECTION:
  844.         case PORT_OK:
  845.             return YES;
  846.             break;
  847.         case FTP_ERR0:
  848.         case FTP_ERR1:
  849.         case FTP_ERR2:
  850.         case FTP_ERR3:
  851.         case FTP_ERR4:
  852.         case FTP_ERR5:
  853.         case FTP_ERR6:
  854.         case FTP_ERR7:
  855.             return YES;
  856.         default:
  857.             break;
  858.     }
  859.     return NO;
  860. }
  861.  
  862. /* Parse the ``150 ...'' output for the size */
  863. int GetSize(char *buffer,FTPManager *self)
  864. {
  865. int size = 0;
  866. char tmpBuffer[32];
  867.  
  868. [self debug: HIGH,"Begin size parsing\n"];
  869.     /* Find the end of the line */
  870.     if( re_match(buffer, transferSizeExpr) != 1)
  871.         return 0;
  872.     sscanf(transferSizeExpr->start,"%[0-9] ",tmpBuffer);
  873. [self debug: MED,"(%s bytes)\n",tmpBuffer];
  874.     size = atoi(tmpBuffer);
  875.     return size;
  876. }
  877.  
  878. /* The proc which tries to determine if a dir command fails to complete */
  879. void ListingDaemon(DPSTimedEntry te, double timeNow, FTPManager *self)
  880. {
  881. static int lastListingSize = 0;
  882.     if(listingBufferSize != lastListingSize)
  883.         lastListingSize = listingBufferSize;
  884.     else
  885.         self->listingTECalls ++;
  886.     
  887.     if(self->listingTECalls >= LISTING_TIMEOUT_COUNT)
  888.     {
  889.         /* Set the ftpFullErrMessage ptr to the incomplete dir listing
  890.             and set the ftpErrMessage */
  891.         strcpy(ftpErrMessage,"ftp ``dir'' command timed out");
  892.         if(ftpFullErrMessage != NULL)
  893.             NX_FREE(ftpFullErrMessage);
  894.         ftpFullErrMessage = listingBuffer;
  895.         listingBuffer = NULL;
  896.         self->ftpMode = FTP_ERROR;
  897.     }
  898. }
  899.  
  900. /* Report the error associated with the keyString and depending on
  901.     the continue depending on the severity */
  902. - error:(int) severity key:(const char *) keyString
  903. {
  904. const char *errMessage;
  905. const char *replyRequest;
  906.  
  907. int alertResult;
  908.  
  909.     errMessage = [errStringTable valueForStringKey: keyString];
  910.     switch(severity)
  911.     {
  912.         case SORRY :
  913.             replyRequest = [errStringTable valueForStringKey: REPLY_REQUEST2];
  914.             break;
  915.         case ALERT :
  916.             replyRequest = [errStringTable valueForStringKey: REPLY_REQUEST1];
  917.             break;
  918.         case SEVERE :
  919.         default:
  920.             replyRequest = [errStringTable valueForStringKey: REPLY_REQUEST0];
  921.             break;
  922.     }
  923.  
  924.     if(severity >= SEVERE)
  925.         alertResult = NXRunAlertPanel("Error","%s\n\t%s","Quit",NULL,NULL,
  926.             errMessage,replyRequest);
  927.     else
  928.         alertResult = NXRunAlertPanel("Error","%s\n\t%s","Ok","Quit",NULL,
  929.             errMessage,replyRequest);
  930.     if(severity >= SEVERE || alertResult == NX_ALERTALTERNATE)
  931.         [NXApp terminate: self];
  932.     
  933.     return self;
  934. }
  935.  
  936. /* My debugging methods */
  937. #import <stdarg.h>
  938.  
  939. - debug: (int) debugLevel, ...
  940. {
  941. va_list  ap;
  942. char *format;
  943.  
  944.     // Only those calls with levels less than the current debugging level are active
  945.     if(debugLevel > ftpDebugLevel)
  946.         return self;
  947.  
  948.     va_start(ap, debugLevel);
  949.     format = va_arg(ap,char *);
  950.     vfprintf(errFile,format,ap);
  951.     va_end(ap);
  952.     fflush(errFile);
  953.  
  954.     return self;
  955. }
  956.  
  957. - conditionalDebug: (BOOL) condition : (int) debugLevel, ...
  958. {
  959. va_list  ap;
  960. char *format;
  961.  
  962.     if(condition == NO)
  963.         return self;
  964.     if(debugLevel > ftpDebugLevel)
  965.         return self;
  966.  
  967.     va_start(ap, debugLevel);
  968.     format = va_arg(ap,char *);
  969.     vfprintf(errFile,format,ap);
  970.     va_end(ap);
  971.     fflush(errFile);
  972.  
  973.     return self;
  974. }
  975.  
  976. /* The sender must implement the [[sender selectedCell] tag] */
  977. - setDebugLevel: sender
  978. {
  979.     if([sender respondsTo: @selector(selectedCell)])
  980.         ftpDebugLevel = [[sender selectedCell] tag];
  981.     else
  982.         ftpDebugLevel = 0;    // Disable debugging
  983.     return self;
  984. }
  985.  
  986. - setErrFile: (FILE *) err
  987. {
  988.     errFile = err;
  989.     return self;
  990. }
  991.  
  992. /* Unimplemented routines */
  993. - runBatch: (const char *) hostName get: (const char *) targetPathName
  994. {
  995.     [self notImplemented: _cmd];
  996.     return self;
  997. }
  998. - runBatch: (const char *) hostName get: (const char *) targetPathName priority: (int) priority
  999. {
  1000.     [self notImplemented: _cmd];
  1001.     return self;
  1002. }
  1003.  
  1004. @end
  1005.