/*
 * Helma License Notice
 *
 * The contents of this file are subject to the Helma License
 * Version 2.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://adele.helma.org/download/helma/license.txt
 *
 * Copyright 1998-2007 Helma Software. All Rights Reserved.
 *
 * $RCSfile: Ftp.js,v $
 * $Author: zumbrunn $
 * $Revision: 9810 $
 * $Date: 2009-05-19 16:38:54 +0200 (Die, 19. Mai 2009) $
 */


/**
 * @fileoverview Default properties and methods of the FTP prototype.
 * <br /><br />
 * To use this optional module, its repository needs to be added to the 
 * application, for example by calling app.addRepository('modules/helma/Ftp.js')
 */

// requires helma.File
app.addRepository("modules/helma/File.js");

/**
 * Define the global namespace if not existing
 */
if (!global.helma) {
    global.helma = {};
}

/**
 * Constructor for FTP client objects, to send and receive files from an FTP server.
 * <br /><br />
 * @class This class represents a FTP client, providing 
 * access to an FTP server.
 * 
 * @example var ftp = new helma.Ftp("ftp.mydomain.com");
 * @param {String} server as String, the address of the FTP Server to connect to
 * @constructor
 */
helma.Ftp = function(server) {
   var OK      =  0;
   var SOCKET  =  1;
   var TIMEOUT =  2;
   var LOGIN   = 10;
   var LOGOUT  = 11;
   var BINARY  = 20;
   var ASCII   = 21;
   var ACTIVE  = 22;
   var PASSIVE = 23;
   var CD      = 30;
   var LCD     = 31;
   var PWD     = 32;
   var DIR     = 33;
   var MKDIR   = 34;
   var RMDIR   = 35;
   var GET     = 40;
   var PUT     = 41;
   var DELETE  = 42;
   var RENAME  = 43;

   var FTP                    = Packages.org.apache.commons.net.ftp.FTP;
   var FtpClient              = Packages.org.apache.commons.net.ftp.FTPClient;
   var BufferedInputStream    = java.io.BufferedInputStream;
   var BufferedOutputStream   = java.io.BufferedOutputStream;
   var FileInputStream        = java.io.FileInputStream;
   var FileOutputStream       = java.io.FileOutputStream;
   var ByteArrayInputStream   = java.io.ByteArrayInputStream;
   var ByteArrayOutputStream  = java.io.ByteArrayOutputStream;

   var self = this;
   var className = "helma.Ftp";

   var ftpclient = new FtpClient();
   var localDir;

   var error = function(methName, errMsg) {
      var tx = java.lang.Thread.currentThread();
      tx.dumpStack();
      app.log("Error in " + className + ":" + methName + ": " + errMsg);
      return;
   };

   var debug = function(methName, msg) {
      msg = msg ? " " + msg : "";
      app.debug(className + ":" + methName + msg);
      return;
   };

   var setStatus = function(status) {
      if (self.status === OK) {
         self.status = status;
      }
      return;
   };

   var getStatus = function() {
      return self.status;
   };

   this.server = server;
   this.status = OK;
   
   /** @ignore */
   this.toString = function() {
      return "[helma.Ftp " + server + "]";
   };

   /**
    * Set the default timeout in milliseconds to use when opening a socket.
    */
   this.setReadTimeout = function(timeout) {
      try {
         ftpclient.setDefaultTimeout(timeout);
         debug("setReadTimeout", timeout);
         return true;
      } catch(x) {
         error("setReadTimeout", x);
         setStatus(SOCKET);
      }
      return false;
   };

   /**
    * Sets the timeout in milliseconds to use when reading from the data connection.
    */
   this.setTimeout = function(timeout) {
      try {
         ftpclient.setDataTimeout(timeout);
         debug("setTimeout", timeout);
         return true;
      } catch(x) {
         error("setTimeout", x);
         setStatus(TIMEOUT);
      }
      return false;
   };

   /**
    * Logs in to the FTP server.
    * 
    * @param {String} username as String
    * @param {String} password as String
    * @return Boolean true if the login was successful, otherwise false
    * @type Boolean
    */
   this.login = function(username, password) {
      try {
         ftpclient.connect(this.server);
         var result = ftpclient.login(username, password);
         debug("login", username + "@" + server);
         return result;
      } catch(x) {
         error("login", x);
         setStatus(LOGIN);
      }
      return false;
   };

   /**
    * Sets transfer mode to binary for transmitting images and other non-text files.
    * 
    * @example ftp.binary();
    */
   this.binary = function() {
      try {
         var result = ftpclient.setFileType(FTP.BINARY_FILE_TYPE);
         debug("binary");
         return result;
      } catch(x) {
         error("binary", x);
         setStatus(BINARY);
      }
      return false;
   };

   /**
    * Sets transfer mode to ascii for transmitting text-based data.
    * 
    * @example ftp.ascii();
    */
   this.ascii = function() {
      try {
         var result = ftpclient.setFileType(FTP.ASCII_FILE_TYPE);
         debug("ascii");
         return result;
      } catch(x) {
         error("ascii", x);
         setStatus(ASCII);
      }
      return false;
   };

   /**
    * Switches the connection to use active mode.
    * 
    * @example ftp.active();
    */
   this.active = function() {
      try {
         ftpclient.enterLocalActiveMode();
         debug("active");
         return true;
      } catch(x) {
         error("active", x);
         setStatus(ACTIVE);
      }
      return false;
   };

   /**
    * Switches the connection to use passive mode.
    * 
    * @example ftp.passive();
    */
   this.passive = function() {
      try {
         ftpclient.enterLocalPassiveMode();
         debug("passive");
         return true;
      } catch(x) {
         error("passive", x);
         setStatus(PASSIVE);
      }
      return false;
   };

   /**
    * Returns the path of the current working directory.
    * 
    * @example var remotepath = ftp.pwd();
    * @type String
    * @return String containing the current working directory path
    */
   this.pwd = function() {
      try {
         debug("pwd");
         return ftpclient.printWorkingDirectory();
      } catch(x) {
         error("pwd", x);
         setStatus(PWD);
      }
      return;
   };

   /**
    * Returns a listing of the files contained in a directory on the FTP server.
    * <br /><br />
    * Lists the files contained in the current working 
    * directory or, if an alternative path is specified, the
    * files contained in the specified directory.
    * 
    * @example var filelist = ftp.dir();
    * @param {String} path as String, optional alternative directory
    * @return Array containing the list of files in that directory
    * @type Array
    */
   this.dir = function(path) {
      try {
         debug("dir", path);
         return ftpclient.listNames(path ? path : ".");
      } catch(x) {
         error("dir", x);
         setStatus(DIR);
      }
      return;
   };

   /**
    * Creates a new directory on the server.
    * <br /><br />
    * The name of the directory is determined as the function's 
    * string parameter. Returns false when an error occured 
    * (e.g. due to access restrictions, directory already 
    * exists etc.), otherwise true.
    * 
    * @param {String} dir as String, the name of the directory to be created
    * @return Boolean true if the directory was successfully created, false if there was an error
    * @type Boolean
    */
   this.mkdir = function(dir) {
      try {
         var result = ftpclient.makeDirectory(dir);
         debug("mkdir", dir);
         return result;
      } catch(x) {
         error("mkdir", x);
         setStatus(MKDIR);
      }
      return false;
   };

   /**
    * Deletes a directory on the FTP server.
    * 
    * @param {String} dir as String, the name of the directory to be deleted
    * @return Boolean true if the deletion was successful, false otherwise
    * @type Boolean
    */
   this.rmdir = function(dir) {
      try {
         var result = ftpclient.removeDirectory(dir);
         debug("rmdir", dir);
         return result;
      } catch(x) {
         error("rmdir", x);
         setStatus(RMDIR);
      }
      return false;
   };

   /**
    * Changes the working directory on the FTP server.
    * 
    * @example ftp.cd("/home/users/fred/www"); // use absolute pathname
    * @example ftp.cd(".."); // change to parent directory
    * @example ftp.cd("images"); // use relative pathname
    * @param {String} dir as String, the path that the remote working directory should be changed to
    */
   this.cd = function(path) {
      try {
         var result = ftpclient.changeWorkingDirectory(path);
         debug("cd", path);
         return result;
      } catch(x) {
         error("cd", x);
         setStatus(CD);
      }
      return false;
   };

   /**
    * Changes the working directory of the local machine when being connected to an FTP server.
    * 
    * @example ftp.lcd("/home/users/fred/www"); // use absolute pathname
    * @example ftp.lcd(".."); // change to parent directory
    * @example ftp.lcd("images"); // use relative pathname
    * @param {String} dir as String, the path that the local working directory should be changed to
    */
   this.lcd = function(dir) {
      try {
         localDir = new helma.File(dir);
         if (!localDir.exists()) {
            localDir.mkdir();
            debug("lcd", dir);
         }
         return true;
      } catch(x) {
         error("lcd", x);
         setStatus(LCD);
      }
      return false;
   };

   /**
    * Transfers a file from the local file system to the remote server.
    * <br /><br />
    * Returns true if the transmission was successful, otherwise false.
    * 
    * @param {String} localFile as String, the name of the file to be uploaded
    * @param {String} remoteFile as String, the name of the remote destination file
    * @return Boolean true if the file was successfully uploaded, false if there was an error
    * @type Boolean
    */
   this.putFile = function(localFile, remoteFile) {
      try {
         if (localFile instanceof File || localFile instanceof helma.File) {
            var f = localFile;
         } else if (typeof localFile == "string") {
            if (localDir == null)
               var f = new helma.File(localFile);
            else
               var f = new helma.File(localDir, localFile);
         }
         var stream = new BufferedInputStream(
            new FileInputStream(f.getPath())
         );
         if (!remoteFile) {
            remoteFile = f.getName();
         }
         var result = ftpclient.storeFile(remoteFile, stream);
         stream.close();
         debug("putFile", remoteFile);
         return result;
      } catch(x) {
         error("putFile", x);
         setStatus(PUT);
      }
      return false;
   };

   /**
    * Transfers text from a string to a file on the FTP server.
    * 
    * @example ftp.putString("Hello, World!", "message.txt");
    * @param {String} str as String, the text content that should be uploaded 
    * @param {String} remoteFile as String, the name of the remote destination file
    * @param {String} charset as String, optional
    * @return Boolean true if the file was successfully uploaded, false if there was an error
    * @type Boolean
    */
   this.putString = function(str, remoteFile, charset) {
      try {
         str = new java.lang.String(str);
         var bytes = charset ? str.getBytes(charset) : str.getBytes();
         var stream = ByteArrayInputStream(bytes);
         var result = ftpclient.storeFile(remoteFile, stream);
         debug("putString", remoteFile);
         return result;
      } catch(x) {
         error("putString", x);
         setStatus(PUT);
      }
      return false;
   };

   /**
    * Transfers a byte array to a file on the FTP server.
    * @param {Array} bytes The byte array that should be uploaded 
    * @param {String} remoteFile The name of the remote destination file
    * @return Boolean True if the file was successfully uploaded, false if there was an error
    * @type Boolean
    */
   this.putBytes = function(bytes, remoteFile) {
      try {
         var stream = ByteArrayInputStream(bytes);
         var result = ftpclient.storeFile(remoteFile, stream);
         debug("putBytes", remoteFile);
         return result;
      } catch(x) {
         error("putBytes", x);
         setStatus(PUT);
      }
      return false;
   };

   /**
    * Transfers a file from the FTP server to the local file system.
    * 
    * @example ftp.getFile(".htaccess", "htaccess.txt");
    * @param {String} remoteFile as String, the name of the file that should be downloaded
    * @param {String} localFile as String, the name which the file should be stored under
    * @see #cd
    * @see #lcd
    */
   this.getFile = function(remoteFile, localFile) {
      try {
         if (localDir == null)
            var f = new helma.File(localFile);
         else
            var f = new helma.File(localDir, localFile);
         var stream = new BufferedOutputStream(
            new FileOutputStream(f.getPath())
         );
         var result = ftpclient.retrieveFile(remoteFile, stream);
         stream.close();
         debug("getFile", remoteFile);
         return result;
      } catch(x) {
         error("getFile", x);
         setStatus(GET);
      }
      return false;
   };

   /**
    * Retrieves a file from the FTP server and returns it as string.
    * 
    * @example var str = ftp.getString("messages.txt");
    * @param {String} remoteFile as String, the name of the file that should be downloaded
    * @return String containing the data of the downloaded file
    * @type String
    * @see #cd
    */
   this.getString = function(remoteFile) {
      try {
         var stream = ByteArrayOutputStream();
         ftpclient.retrieveFile(remoteFile, stream);
         debug("getString", remoteFile);
         return stream.toString();
      } catch(x) {
         error("getString", x);
         setStatus(GET);
      }
      return;
   };

   /**
    * Deletes a file on the FTP server.
    * 
    * @example var str = ftp.deleteFile("messages.txt");
    * @param {String} remoteFile as String, the name of the file to be deleted
    * @return Boolean true if the deletion was successful, false otherwise
    * @type Boolean
    */
   this.deleteFile = function(remoteFile) {
      try {
         var result = ftpclient.deleteFile(remoteFile);
         debug("deleteFile", remoteFile);
         return result;
      } catch(x) {
         error("deleteFile", x);
         setStatus(DELETE);
      }
      return false;
   };
   
   /**
    * Renames a file on the FTP server.
    * 
    * @example var success = ftp.renameFile("messages.tmp", "messages.txt");
    * @param {String} from the name of the original file
    * @param {String} to the new name the original file should get
    * @return Boolean true if renaming the remote file was successful, false otherwise
    * @type Boolean
    */
   this.renameFile = function(from, to) {
      try {
         var result = ftpclient.rename(from, to);
         debug("renameFile", from + "->" + to);
         return result;
      } catch(x) {
         error("renameFile", x);
         setStatus(RENAME);
      }
      return false;
   };

   /**
    * Terminates the current FTP session.
    */
   this.logout = function() {
      try {
         var result = ftpclient.logout();
         ftpclient.disconnect();
         debug("logout");
         return result;
      } catch(x) {
         error("logout", x);
         setStatus(LOGOUT);
      }
      return false;
   };

   for (var i in this)
      this.dontEnum(i);

   return this;
}


/** @ignore */
helma.Ftp.toString = function() {
   return "[helma.Ftp]";
};


helma.lib = "Ftp";
helma.dontEnum(helma.lib);
for (var i in helma[helma.lib])
   helma[helma.lib].dontEnum(i);
for (var i in helma[helma.lib].prototype)
   helma[helma.lib].prototype.dontEnum(i);
delete helma.lib;
