/*
 * @(#)FTPSession.java	0.2 99/01/04
 *
 * Copyright 1998-1999 by Christian Ullenboom
 * All rights reserved.
 */

import java.io.*;
import java.util.*;
import java.net.*;

/**
 * Class for handling connections to a ftp server.
 * You can get information such as content of a directory,
 * change directory, upload and download files to the
 * local filesystem or save is into memory.
 *
 * @version 	0.2 99/01/04
 * @author 	Christian Ullenboom
 */
 
class FTPSession
{
  /**
   * Opening an anonymous connection to the FTP server.
   */
   
  public void
  openConnection( String aServer ) throws IOException
  {
    openConnection( aServer, "anonymous", "X.X@com" );
  }


  /**
   * Opening a connection to the FTP server.
   */

  public void
  openConnection( String aServer, String aUserName, String aPasswd )
    throws IOException
  {
    server = aServer;
    
    serverSocket = new Socket( aServer, 21 );
    in = new DataInputStream( serverSocket.getInputStream() );
    out = new DataOutputStream( serverSocket.getOutputStream() );

    getResponse( );   // get header
    sendCommand( "USER " + aUserName );

    getResponse( );   // "331 Give me your password, please": Request for password 
    sendCommand( "PASS " + aPasswd );
    getResponse( );   // "230 Logged in successfully"

    openPassiveMode();
  }
  

  /**
   * Print messages for debug mode.
   */
  
  public void  debug( boolean onOff )
  {
    debugMode = onOff;
  }
  
   
  /**
   * Return the contents of a directory.
   */
   
  public Vector list()
  {
    Vector v = new Vector();

    try {
      String response;
    
      sendCommand( "NLST" );
      getResponse();

      DataInputStream in = new DataInputStream( transIs );

      while ( (response=in.readLine()) != null )
        v.addElement( response );

      response = getResponse();

      if ( debugMode ) System.out.println( response );

    } catch ( IOException e ) { System.out.println( "NLIST: " + e ); }

    return v;
  }
  
  
  /**
   * Return true if the given filename is in the current directory.
   */
   
  public boolean findFileInCurrentDirectory( String filename )
  {
    Vector v = list();
    
    return v.contains( filename );
  }
  
  
  /**
   * Causes the name of the current working directory to be returned.
   */
   
  public String   printWorkingDirectory()
  {
    String response = null;
    
    try {
      sendCommand( "PWD" );
      response = getResponse();

      int start = response.indexOf( '"' ),
          end   = response.lastIndexOf( '"' );

      response = response.substring( start+1, end );

    } catch ( IOException e ) { System.out.println( "PWD: " + e ); }

    return response;
  }
  
  
  /**
   * Rename a file.
   */
   
  public String   renameFile( String from, String to )
  {
    String response = null;
    
    try {
      sendCommand( "RNFR " + from  );
      response = getResponse();
      sendCommand( "RNTO " + to );
      response = getResponse();

    } catch ( IOException e ) { System.out.println( "Rename: " + e ); }

    return response;
  }

  
  /**
   * Allows the user to work with a different
   * directory or dataset for file storage or retrieval without
   * altering his login or accounting information.  
   */
  
  public String   changeWorkingDirectory( String pathname )
  {
    String response = null;
    
    try {
      sendCommand( "CWD " + pathname );
      response = getResponse();

    } catch ( IOException e ) { System.out.println( "CWD: " + e ); }

    return response;
  }


  /**
   * Causes the directory specified in the pathname
   * to be created as a directory (if the pathname is absolute)
   * or as a subdirectory of the current working directory (if
   * the pathname is relative).  
   */

  public String
  makeDirectory( String pathname )
  {
   String response = null;
    
    try {
      sendCommand( "MKD " + pathname );
      response = getResponse();

    } catch ( IOException e ) { System.out.println( "MKD: " + e ); }

    return response; 
  }
  
  /**
   * Find out the type of operating system at the server.  
   */
  
  public String
  typeOfOperatingSystem()
  {
    String response = null;
    
    try {
      sendCommand( "SYST" );
      response = getResponse();

    } catch ( IOException e ) { System.out.println( "SYST: " + e ); }

    return response;
  }

  
  /**
   * Special case of CWD.
   */
   
  public String
  changeToParentDirectory( )
  {
    String response = null;
    
    try {
      sendCommand( "CDUP" );
      response = getResponse();

    } catch ( IOException e ) { System.out.println( "CDUP: " + e ); }

    return response;
  }
  

  /**
   * Download a file and save it on the local machine.
   */

  public String
  download( String from, String to )
  {
    try {
      openPassiveMode();
      sendCommand( "RETR " + from );
      String response = getResponse();

      BufferedOutputStream out = new BufferedOutputStream(
                                    new FileOutputStream(
                                        new File( to ) ) );

      final int blockSize = 1024;
      byte b[] = new byte[blockSize];
      int len;

      while ( (len = transIs.read( b, 0, blockSize )) > 0 )
      {
        out.write( b, 0, len );
        if ( debugMode ) System.out.print( "#" );
      }
      if ( debugMode ) System.out.println( "\n" );

      out.close();

    } catch ( IOException e ) { System.out.println( "Get: " + e ); }

    return getResponse();
  }


  /**
   * Download a file and copy it into memory.
   */
   
  public byte[]
  download( String aFileName )
  {
    try {
      openPassiveMode();
      sendCommand( "RETR " + aFileName );
      String response = getResponse();

      int fileLen = transIs.available();
      byte[] buffer = new byte[ fileLen ];
      transIs.read( buffer, 0, fileLen );
      out( ""+fileLen );

      getResponse();

      return buffer;
    
    } catch ( IOException e ) { System.out.println( "Get: " + e ); }

    return null;
  }


  /**
   * Upload a file.
   */
  
  public void   upload( String from, String to ) throws IOException
  {
	// ?
    openPassiveMode();

    sendCommand( "STOR " + to );

    String response = getResponse();

    DataOutputStream dos =  new DataOutputStream( transOut );

    RandomAccessFile infile = new RandomAccessFile( from, "r" );

    final int blockSize = 1024;
    byte buffer[] = new byte[blockSize];
    int len;

    while ( (len = infile.read( buffer, 0, blockSize ) ) > 0 ) {
      dos.write( buffer, 0, len );
      if ( debugMode ) System.out.print( "#" );
	  }
    
	  if ( debugMode ) System.out.println( "\n" );

  	infile.close();
    getResponse();
  }


  /**
   * Close the connection.
   */
   
  public void close() throws IOException
  {
    if ( serverSocket != null ) {
        serverSocket.close(); serverSocket = null;
    }
    if ( transServer != null ) {
        transServer.close(); transServer = null;
    }
  }


  // private methods

  /**
   * Opening port for transmission.
   */

  private void
  openPassiveMode()
  {
    try {
        sendCommand( "PASV" );
        String response = getResponse();
        
        // pasv looks like
        // 227 Entering Passive Mode (129,206,100,129,251,158)
        int parentStart = response.lastIndexOf( '(' ),
            parentEnd   = response.lastIndexOf( ')' );

        String pasv = response.substring( parentStart+1, parentEnd );
        StringTokenizer st = new StringTokenizer( pasv, "," );
        int IPs[] = new int[8];
        for ( int i = 0; st.hasMoreTokens(); i++ )
          IPs[i] = Integer.valueOf( st.nextToken() ).intValue();
        // Host Adress is the same, but port not
        int port = ( IPs[4] << 8 ) + IPs[5];

        if ( transServer != null )
          transServer.close();

        transServer = new Socket( server, port );
        
        transIs = transServer.getInputStream();
        transOut = transServer.getOutputStream();
    } catch ( IOException e ) { out( "PASV not Server" + e ); }
  }


  /**
   * Send command and wait for resonse.
   */

  private void
  sendCommand( String aCommand ) throws IOException
  {
    if ( debugMode ) System.out.println( "->: " + aCommand );
    out.writeBytes( aCommand + "\n" );
  }

  
  /**
   * Receive reply code and response from the server.
   */

  private String
  getResponse()
  {
    String response = "",
           newLine  = null;

  	try
  	{
	    do {
        newLine = in.readLine();
        response += newLine;
        if ( debugMode ) System.out.println( "<-: " + newLine );
      } while(!(Character.isDigit(newLine.charAt(0)) &&
                Character.isDigit(newLine.charAt(1)) &&
                Character.isDigit(newLine.charAt(2)) && 
                newLine.charAt(3) == ' ') );

    } catch ( IOException e ) {
      System.out.println( "No reply " + e );
      return null;
	}

  	replyCode = Integer.parseInt( newLine.substring(0, 1) );
  	
  	return response;
  }


  /**
   * Because System.out.println() is too much to write.
   */

  private void
  out( String s )
  {
    System.out.println( s );
  }
  

  // private fields
  
  private String server;
  
  private boolean debugMode = false;

  private Socket serverSocket;

  private Socket transServer = null;

  private int replyCode;

  private DataOutputStream out;
  
  private DataInputStream in;
  
  private InputStream transIs;

  private OutputStream transOut;
}