
import java.net.*;
import java.io.*;
import java.util.*;
import java.applet.*;

//-- the j2ssh tools
import com.sshtools.j2ssh.SshClient;
import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
import com.sshtools.j2ssh.io.UnsignedInteger32;
import com.sshtools.j2ssh.session.SessionChannelClient;
import com.sshtools.j2ssh.sftp.FileAttributes;
import com.sshtools.j2ssh.sftp.SftpFile;
import com.sshtools.j2ssh.sftp.SftpFileOutputStream;
import com.sshtools.j2ssh.SftpClient;
import com.sshtools.j2ssh.configuration.ConfigurationLoader;
import com.sshtools.j2ssh.configuration.ConfigurationException;
import com.sshtools.j2ssh.transport.IgnoreHostKeyVerification;



//--------------------------------------------
/**
 *  This class represents the attempted upload of a single
 *  file to an ssh server via sftp. It uses the j2ssh toolset
 *
 *  www.math.uni-klu.ac.at/alph/java/sshtools/
 *
 *  rather than the jsch ('java secure chanel') toolset. This was
 *  a decision taken without real understanding.
 *
 *  The class stores information about the upload attempt.
 *  The code design was based on the WebDownload class.
 *
 *  This was adapted from the SoundDownload class
 *  @see "SoundDownload", "WebDownload"
 *  @author matth3wbishop<at>yahoo<dot>com
 */ 

public class SftpUpload extends Object
{
  //--------------------------------------------
  /** the file to put  */
  private String fileName;
  //--------------------------------------------
  private long fileSize;
  //--------------------------------------------
  private boolean invalidFileName;
  //--------------------------------------------
  /** the sftp server  */
  private String serverName;
  //--------------------------------------------
  /** ssh account name */
  private String accountName;
  //--------------------------------------------
  /** the ssh account password */
  private String accountPassword;
  //--------------------------------------------
  /** ssh save path */
  private String serverPath;
  //--------------------------------------------
  /** The time at which the attempted upload was begun */
  private Date startTime;
  //--------------------------------------------
  /** The time at which the attempted upload was terminated
   *  whether successfully or unsuccessfully */
  private Date finishTime;

  //--------------------------------------------
  /** A directory in which to put files  */
  private String directoryName = "";
  //--------------------------------------------
  /** the reason the upload failed  */
  private String failureCause;
  //--------------------------------------------
  /** will store error messages produced when attempting the upload
   *  the file, such as those produced by the Url class in
   *  attempting to make a connection to the resource. */ 
  private StringBuffer errorMessage;

  //--------------------------------------------
  /** a chunk for reading from the target, in bytes */
  private static int CHUNK_SIZE = 10000;
  //--------------------------------------------
  /** the progress marker */
  private String PROGRESS_MARKER = ".";


  //--------------------------------------------
  private boolean wasSuccessful;
  //--------------------------------------------
  /** whether the download has started */
  private boolean hasStarted;
  //--------------------------------------------
  private boolean hasFinished;
  //--------------------------------------------
  private boolean showProgress = true;
  //--------------------------------------------
  public static String NEWLINE = System.getProperty("line.separator");

  //--------------------------------------------
  //-- CONSTRUCTORS

  public SftpUpload()
  {
    this.reset();
  } //-- constr: ()

  //--------------------------------------------
  public SftpUpload(String sServer, String sAccountName)
  {
    this.reset();
    this.serverName = sServer;
    this.accountName = sAccountName;
  } 

  //--------------------------------------------
  public SftpUpload(
    String sFileName, String sServer,
    String sAccountName, String sServerPath)
  {
    this.reset();
    this.setLocalFileName(sFileName);
    this.serverName = sServer;
    this.accountName = sAccountName;
    this.serverPath = sServerPath;
  } 

  //-- METHODS

  //--------------------------------------------
  /** this method sets most of the internal fields to
   *  nothing */
  public void reset()
  {

    this.fileName = "";
    this.serverName = "";
    this.serverPath = "";
    this.accountName = "";
    this.accountPassword = "";
    this.failureCause = "";
    this.errorMessage = new StringBuffer("");    
    this.hasStarted = false;
    this.wasSuccessful = false;
    this.hasFinished = false;
    this.startTime = new Date(0);
    this.finishTime = new Date(0);

    //-- show progress should not be reset
    //this.showProgress = false;

  } //-- method: reset


  //--------------------------------------------
  /* return the target to download */
  public String getLocalFileName()
  {
    return this.fileName;
  }

  //--------------------------------------------
  public void setLocalFileName(String sLocalFileName)
  {
    
    File fLocalFile;
    this.reset();
    fLocalFile = new File(sLocalFileName);

    if (!fLocalFile.exists())
    {
      this.failureCause = "Local file doesnt exist";
      this.errorMessage.append("The local file " + sLocalFileName);
      this.errorMessage.append(" does not appear to exist ");
      this.invalidFileName = true;
      return;  
    }

    if (!fLocalFile.canRead())
    {
      this.failureCause = "Local file cannot be read";
      this.errorMessage.append("The local file " + sLocalFileName);
      this.errorMessage.append(" does not appear to be readable ");
      this.invalidFileName = true;
      return;  
    }

    if (fLocalFile.isDirectory())
    {
      this.failureCause = "Local file path is a directory";
      this.errorMessage.append("The local file " + sLocalFileName);
      this.errorMessage.append(
       " appears to be a directory. This object does not attempt ");
      this.errorMessage.append(
       " to handle the uploading of entire directories. ");
      this.invalidFileName = true;
      return;  
    }


    this.invalidFileName = false;
    this.fileSize = fLocalFile.length();
    this.fileName = fLocalFile.getAbsolutePath();
  }

  //--------------------------------------------
  public void setAccountName(String sAccountName)
  {
    this.accountName = sAccountName;
  }

  //--------------------------------------------
  public String getAccountName()
  {
    return this.accountName;
  }


  //--------------------------------------------
  public void setPassword(String sPassword)
  {
    this.accountPassword = sPassword;
  }

  //--------------------------------------------
  public String getPassword()
  {
    return this.accountPassword;
  }

  //--------------------------------------------

  //--------------------------------------------
  /** tries to set the local directory. If a bad string is
   *  given it returns false. a legacy from WebDownload  */
  public boolean setLocalDirectory(String sLocalDirectoryName)
  {
    File fDirectory = new File(sLocalDirectoryName);


    if (!fDirectory.exists())
     { return false; }

    if (!fDirectory.isDirectory())
    {
      return false;
    }

    //-- remove a final path separator from the name
    if (sLocalDirectoryName.endsWith(File.separator))
    {
      this.directoryName =
       sLocalDirectoryName.substring(0, sLocalDirectoryName.length() - 1);
    }
    else
    {
      this.directoryName = sLocalDirectoryName;
    }

    return true;
  } //--

  //--------------------------------------------
  /* advises whether the attempted download was carried out successfully */
  public boolean wasSuccessful() 
    { return this.wasSuccessful; }


  //--------------------------------------------
  /** whether some indicator is printed to the console */
  public void showProgress() 
    { this.showProgress = true; }

  //--------------------------------------------
  public void showProgress(boolean bShow) 
    { this.showProgress = bShow; }

  //--------------------------------------------
  public void setProgressMarker(String sMarker) 
    { this.PROGRESS_MARKER = sMarker; }

  //--------------------------------------------
  /* informs of the average upload speed */
  public int getAverageSpeed() 
  {
    int iAverageSpeed = 0;
    long lUploadDuration = this.getUploadDuration();

    if (!this.wasSuccessful)
      { return 0; }

    if (lUploadDuration == 0)
      { return 0; }

    //-- 
    iAverageSpeed = (int)(this.fileSize/this.getUploadDuration());
    return iAverageSpeed;
  }

  //--------------------------------------------
  /** attempt the upload and fail with dignity and grace */
  public void upload()
  {
    this.hasStarted = true;
    this.startTime = new Date();

    if (this.invalidFileName)
     { return; }

    if (this.fileName.equals(""))
    {
      this.failureCause = "No local file name specified";
      this.errorMessage.append(
       "No local file was specified for uploading to the ");
      this.errorMessage.append("sftp server. ");
      return;
    }




   // try
   // {
      // Configure J2SSH (This will attempt to install the
      // bouncycastle provider under jdk 1.3.1)

    /*
    BufferedReader brConsole;
    brConsole = new BufferedReader(
      new InputStreamReader(System.out));

    String sMessageLine;
    */

      try
      {
        ConfigurationLoader.initialize(false);
      }
      catch (ConfigurationException e)
      {
        this.failureCause = "The ssh tools could not be configured";
        this.errorMessage.append(e);
        this.wasSuccessful = false;
        return;
      }

    /*
    sMessageLine = brConsole.readLine();
    while (sMessageLine != null)
    {
      System.out.println("**" + sMessageLine);
      sMessageLine.readLine();
    }
    */

      SshClient ssh = new SshClient();
      try
      {
        //-- the line below should be changed to allow the
        //-- user to choose whether to accept the hosts key
        //-- 
        ssh.connect(this.serverName, new IgnoreHostKeyVerification());
      }
      catch (IOException e)
      {
        this.failureCause =
          "A connection to the host could not be established";
        this.errorMessage.append(
         "A connection to the host " + this.serverName);
        this.errorMessage.append(
         " could not be established ");
        this.errorMessage.append(e);
        this.wasSuccessful = false;
        return;
      }
      
     

      // Create a password authentication instance

      PasswordAuthenticationClient pwd =
        new PasswordAuthenticationClient();
      pwd.setUsername(this.accountName);
      pwd.setPassword(this.accountPassword);


      int iAuthenticationResult;

      try
      {
        iAuthenticationResult = ssh.authenticate(pwd);
      }
      catch (IOException e)
      {
        this.failureCause =
          "A connection to the host for authentication could not be established";
        this.errorMessage.append(
         "A connection to the host " + this.serverName);
        this.errorMessage.append(
         " could not be established during authentication ");
        this.errorMessage.append(e);
        this.wasSuccessful = false;
        return;
      }


      if (iAuthenticationResult != AuthenticationProtocolState.COMPLETE)
      {
        this.failureCause =
          "The authentication failed ";
        this.errorMessage.append(
         "The authentication failed for account ");
        this.errorMessage.append(this.accountName +
         " on the server ");
        this.errorMessage.append(this.serverName);

        this.wasSuccessful = false;
        return;
      }


      SftpClient sftp;
      try
      {
        sftp = ssh.openSftpClient();
      }
      catch (IOException e)
      {
        this.failureCause =
          "IOException while opening sftp client ";
        this.errorMessage.append(
          "IOException while opening sftp client ");
        this.errorMessage.append(e);
        this.wasSuccessful = false;
        return;
      } 


      if (this.showProgress)
      {
         System.out.println(
          "Changing directory to " + this.serverPath);
      }

      try
      {
        sftp.cd(this.serverPath);
      }
      catch (IOException e)
      {
        this.failureCause =
          "IOException changing directory ";
        this.errorMessage.append(
          "IOException while changing directory ");
        this.errorMessage.append(e);
        this.wasSuccessful = false;
        return;
      } 

        if (this.showProgress)
        {
          System.out.println(
           "Current directory now " + sftp.pwd());
        }


        if (this.showProgress)
        {
          System.out.println("putting " + this.fileName);
        }

      try
      {
        sftp.put(this.fileName);
      }
      catch (IOException e)
      {
        this.failureCause =
          "IOException while putting the file ";
        this.errorMessage.append(
          "IOException while putting the file ");
        this.errorMessage.append(e);
        this.wasSuccessful = false;
        return;
      } 

      if (this.showProgress)
      {
          System.out.println("quitting ...");
          
      }

      try
      {
        sftp.quit();
      }
      catch (IOException e)
      {
        this.failureCause =
          "IOException while quiting the sftp connection ";
        this.errorMessage.append(
          "IOException while quiting the sftp connection ");
        this.errorMessage.append(e);
        this.wasSuccessful = false;
        return;
      } 

      if (this.showProgress)
      {
         System.out.println("disconnecting ...");
          
      }

      ssh.disconnect();

        //sftp.mkdir("j2ssh");
        //sftp.chmod(0777, "j2ssh");
        //sftp.lcd("c:/");
        //sftp.get("somefile.txt", "anotherfile.txt");
        //sftp.rm("j2ssh");

    this.finishTime = new Date();
    this.wasSuccessful = true;
    this.hasFinished = true;

  } //-- method: upload()

  //--------------------------------------------
  /** prints information such as the start time, the finish
   *  time, the duration and the success or failure of the 
   *  download. */
  public String printStatistics()
  { 
    StringBuffer sbOutput = new StringBuffer("");
    sbOutput.append(NEWLINE);
    sbOutput.append("Attempted to put file  >");
    sbOutput.append(this.fileName);
    sbOutput.append(NEWLINE);
    sbOutput.append("to server              >");
    sbOutput.append(this.serverName);
    sbOutput.append(NEWLINE);
    sbOutput.append("with account name      >");
    sbOutput.append(this.accountName);
    sbOutput.append(" (");
    sbOutput.append(this.accountPassword);
    sbOutput.append(")");
    sbOutput.append(NEWLINE);
    sbOutput.append("using server path      >");
    sbOutput.append(this.serverPath);
    sbOutput.append(NEWLINE);

    if (this.wasSuccessful)
    {
      sbOutput.append("Result of the download >");
      sbOutput.append("SUCCESS");
      sbOutput.append(NEWLINE);
    }
    else
    {
      sbOutput.append("Result of the download >");
      sbOutput.append("FAILURE");
      sbOutput.append(NEWLINE);
    }

    sbOutput.append("Download duration (s)  >");
    sbOutput.append((float)this.getUploadDuration()/1000);
    sbOutput.append(NEWLINE);
    sbOutput.append("File size              >");
    sbOutput.append(this.fileSize);
    sbOutput.append(NEWLINE);
    sbOutput.append("Start Time             >");
    sbOutput.append(this.startTime);
    sbOutput.append(NEWLINE);
    sbOutput.append("Finish Time            >");
    sbOutput.append(this.finishTime);
    sbOutput.append(NEWLINE);
    sbOutput.append("Average upload speed (kbyte/s) >");
    sbOutput.append(this.getAverageSpeed());
    sbOutput.append(NEWLINE);


    if (!this.failureCause.equals(""))
    {
      sbOutput.append("Cause of download failure      >");
      sbOutput.append(this.failureCause);
      sbOutput.append(NEWLINE);
    }


    if (!this.errorMessage.toString().equals(""))
    {
      sbOutput.append("Error Messages generated during the download >");
      sbOutput.append(NEWLINE);
      sbOutput.append(this.errorMessage);
      sbOutput.append(NEWLINE);
    }

    sbOutput.append("");
    sbOutput.append("");
    sbOutput.append("");
    return sbOutput.toString();    
  }
  

  //--------------------------------------------
  /* return the number of milliseconds taken to download the file. */
  public long getUploadDuration()
  {
    if (this.finishTime.getTime() == 0) { return 0; }
    if (this.startTime.getTime() == 0) { return 0; }
    return this.finishTime.getTime() - this.startTime.getTime();
  } 

  //--------------------------------------------
  /** print a message to reassure the user */ 
  public String printStartMessage() 
  {
    StringBuffer sbReturn = new StringBuffer("");
    sbReturn.append("Attempting to put " + this.fileName);
    if (this.showProgress)
    {
      //sbReturn.append(", each [" + this.PROGRESS_MARKER + "]");
      //sbReturn.append("=" + this.CHUNK_SIZE + " bytes");
    }
    else
    {
      sbReturn.append(" (no progress marker)");
    }
    sbReturn.append(NEWLINE);
    return sbReturn.toString(); 

  } //-- method: printStartMessage


  //--------------------------------------------
  /* how is this different from toString */
  public String print()
  { 
    return this.printStatistics();
  }

  //--------------------------------------------
  /** a concise summary of what happened */
  public String toString()
  {

    StringBuffer sbOutput = new StringBuffer("");
    sbOutput.append("sftp upload ");


    sbOutput.append("(file=");
    sbOutput.append(this.fileName);
    sbOutput.append(")");
    sbOutput.append(" --> ");
    sbOutput.append(this.serverName);

    sbOutput.append(" path:");
    sbOutput.append(this.serverPath);
    sbOutput.append(" account:");
    sbOutput.append(this.accountName);
    sbOutput.append("");

    if (this.wasSuccessful)
    {
      sbOutput.append(" -successful-");
    }
    else
    {
      sbOutput.append(" -failed-");
    }

    sbOutput.append(" took:");
    sbOutput.append((float)this.getUploadDuration()/1000);
    sbOutput.append("s");
    sbOutput.append(", size:");
    sbOutput.append(this.fileSize);
    //sbOutput.append(", speed (kb/s):");
    //sbOutput.append(this.getAverageSpeed());

    sbOutput.append(" -at- ");
    sbOutput.append(this.startTime);
    sbOutput.append(NEWLINE);

    sbOutput.append("");
    return sbOutput.toString();    

  } //-- method: toString()

  //--------------------------------------------
  /** to allow for testing and one-off downloads */
  public static void main(String[] args) throws Exception
  {
    String sUsageMessage =
      "usage: java SftpUpload file server account pwd path";

    String sFileName;
    String sServerName;
    String sAccountName;
    String sPassword;
    String sServerPath;

    if (args.length < 5)
    {	    
      System.out.println(sUsageMessage);
      System.exit(-1);
    }

    sFileName    = args[0];
    sServerName  = args[1];
    sAccountName = args[2];
    sPassword    = args[3];
    sServerPath  = args[4];


    SftpUpload suTest =
      new SftpUpload(sFileName, sServerName, sAccountName, sServerPath);
    suTest.setPassword(sPassword);
    suTest.showProgress();
   
    System.out.println(suTest.printStartMessage());
    suTest.upload();
    System.out.println(suTest.printStatistics());
    System.out.println(suTest.toString());

  } //-- main()
} //-- WebDownload class

