/**
 * @fileoverview Contains {@link FTPClient} - the main interface for the Integral FTP JavaScript library.
 * @author Enterprise Distributed Technologies
 * @version %major_ver%.%middle_ver%.%minor_ver%
 */

// FTPClient =============================================================================

/**
 * @ignore
 */
var _ftpClient = null;		// global reference	to FTPClient

/**
 * @ignore
 */
var FTPClient_sortColumn = "name";

/**
 * @ignore
 */
var FTPClient_sortAscending = true;

// FTPClient Methods -------------------------------------------------------------

/**
 * Constructs a new FTPClient object.
 * @class FTPClient is the main interface for the Integral FTP library.  It contains
 * methods for connecting to FTP servers, downloading and uploading files, listing
 * local and remote directories, and renaming and deleting local and remote files.
 * Please refer to the <a href="../devguide.html" target="_top">Integral FTP Developer's Guide</a>
 * for a broad description of how this class may be used.
 * @constructor
 * @return A new FTPClient
 */
function FTPClient()
{
	// set global reference
	_ftpClient = this;
}

// CONSTANTS -----------------------------------------------------------

/**
 * Constant used in {@link #downloadFile} and {@link #uploadFile} to 
 * direct FTPClient to overwrite any existing file with the same name.
 * @see #uploadFile
 * @see #downloadFile
 * @final
 */
FTPClient.prototype.WRITEMODE_OVERWRITE = 0;

/**
 * Constant used in {@link #downloadFile} and {@link #uploadFile} to 
 * direct FTPClient to resume any previous transfer from the point
 * at which is was stopped last time.
 * @see #uploadFile
 * @see #downloadFile
 * @final
 */
FTPClient.prototype.WRITEMODE_RESUME = 1;

/**
 * Constant used in {@link #downloadFile} and {@link #uploadFile} to 
 * direct FTPClient to append to the existing file (if any).
 * @see #uploadFile
 * @see #downloadFile
 * @final
 */
FTPClient.prototype.WRITEMODE_APPEND = 2;

// PROPERTIES ----------------------------------------------------------

/**
 * @ignore
 */
FTPClient.prototype._initStarted = false;

/**
 * @ignore
 */
FTPClient.prototype._initComplete = false;

/**
 * @ignore
 */
FTPClient.prototype._isConnected = false;

/**
 * @ignore
 */
FTPClient.prototype._tags = [];

/**
 * File transfer protocol to use (FTP, FTPS or SFTP).
 * @type string
 */
FTPClient.prototype.protocol = null;

/**
 * Domain name or IP address (and optionally port) of FTP server.
 * Host-names should be of the form "{subdomain}.{domain}:{port}.
 * @type string
 */
FTPClient.prototype.remoteHost = null;

/**
 * User-name of account on FTP server
 * @type string
 */
FTPClient.prototype.userName = null;

/**
 * Password of account on FTP server
 * @type string
 */
FTPClient.prototype.password = null;

/**
 * Language(s) to use when attempting parse dates in directory listings.
 * By default an attempt will first be made to parse the listings using
 * English and then in the default language of the Java installation
 * (if it is not English).
 * 
 * If this field is set then it should contain a comma-separated list of
 * languages.  The languages should be specified using Locale Identifiers 
 * such as "en_US" for US English, "fr_FR" for French and "es_ES" for Spanish.  
 * Thus if dateLanguages is "en_US,fr_FR,es_ES" then the parser will first 
 * try to parse the dates in Englsih, then French and finally in Spanish.
 * If none of these languages yield valid dates then dates will be returned
 * as null.
 * 
 * Note that this field must be set before {@link #connect} is called.
 */
FTPClient.prototype.dateLanguages = null;

/**
 * Minimum number of milliseconds between progress updates.
 * There is no upper limit since it depends on the number
 * of bytes transferred and the block-size of the transfers.
 * In most cases, however, the actualy period should be just
 * a little more than this value.  The default is 1000 milliseconds.
 * @type int
 */
FTPClient.prototype.minTransferNotifyPeriod = 1000;

/**
 * Flag controlling the sort-order (ascending or descending) of files returned 
 * by {@link #localDirectoryList}.
 * The attribute by which the list is controlled by {@link #localSortColumn}.
 * @type boolean
 * @see #localDirectoryList
 */
FTPClient.prototype.localSortAscending = true;

/**
 * Attribute by which the list files returned by {@link #localDirectoryList} are
 * sorted.  Possible values are "name", "size" or "date".  The order (ascending
 * or descending) is controlled by {@link #localSortAscending}.
 * @type string
 * @see #localDirectoryList
 */
FTPClient.prototype.localSortColumn = "name";		// should be "name", "size" or "date"

/**
 * Flag controlling the sort-order (ascending or descending) of files returned 
 * by {@link #remoteDirectoryList}.
 * The attribute by which the list is controlled by {@link #remoteSortColumn}.
 * @type boolean
 * @see #remoteDirectoryList
 */
FTPClient.prototype.remoteSortAscending = true;

/**
 * Attribute by which the list files returned by {@link #remoteDirectoryList} are
 * sorted.  Possible values are "name", "size" or "date".  The order (ascending
 * or descending) is controlled by {@link #remoteSortAscending}.
 * @type string
 * @see #remoteDirectoryList
 */
FTPClient.prototype.remoteSortColumn = "name";		// should be "name", "size" or "date"

/**
 * Controls whether or not the EnterpriseDT splash screen is displayed on start-up.
 * This property must be set prior to calling the {@link #initialize} method.
 * It can also be set by passing an argument to {@link #initialize}.
 * @type boolean
 * @see #initialize
 */
FTPClient.prototype.showSplash = true;

/**
 * Minimum number of connections that the client makes with the server.
 * This property must be set prior to calling the {@link #initialize} method.
 * It can also be set by passing an argument to {@link #initialize}.
 * @type int
 * @see #initialize
 */
FTPClient.prototype.minPoolSize = 1;

/**
 * Maximum number of connections that the client makes with the server.
 * This property must be set prior to calling the {@link #initialize} method.
 * It can also be set by passing an argument to {@link #initialize}.
 * @type int
 * @see #initialize
 */
FTPClient.prototype.maxPoolSize = 5;


// CALLBACKS -----------------------------------------------------------

/**
 * Callback for the {@link #initialize} method.
 * This field should be set to a function if notification is required when
 * the {@link #initialize} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @type callback
 * @see #initialize
 */
FTPClient.prototype.onInitialize = function (callbackStatus) {};

/**
 * Callback for the {@link #connect} method.
 * This field should be set to a function if notification is required when
 * the {@link #connect} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @type callback
 * @see #connect
 */
FTPClient.prototype.onConnect = function (callbackStatus) {};

/**
 * Callback for the {@link #disconnect} method.
 * This field should be set to a function if notification is required when
 * the {@link #disconnect} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {int} reasonCode Code describing the reason for the disconnection
 * (0=Normal disconnection, 1=Inactive for too long, 2=Lost connection, other=Unknown vause).
 * @param {string} reasonMessage Message describing the reason for the disconnection
 * ("Normal disconnection", "Inactive for too long", "Lost connection", "Unknown cause").
 * @type callback
 * @see #disconnect
 */
FTPClient.prototype.onDisconnect = function (callbackStatus, reasonCode, reasonMessage) {};

/**
 * Callback for the {@link #directoryList} method.
 * This field should be set to a function if notification is required when
 * the {@link #directoryList} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteDirectory Name of the directory being listed.
 * @param {FTPFileList} fileList {@link FTPFileList} containing the files.
 * @type callback
 * @see #directoryList
 */
FTPClient.prototype.onDirectoryList = function (callbackStatus, remoteDirectory, fileList) {};

/**
 * Callback for the {@link #downloadFile} method.
 * This field should be set to a function if notification is required when
 * the {@link #downloadFile} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file that was downloaded.
 * @type callback
 * @see #downloadFile
 */
FTPClient.prototype.onDownloadFile = function (callbackStatus, remoteFileName) {};

/**
 * Callback for the {@link #downloadText} method.
 * This field should be set to a function if notification is required when
 * the {@link #downloadText} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file that was downloaded (as text).
 * @param {string} text String containing the contents of the file.
 * @type callback
 * @see #downloadText
 */
FTPClient.prototype.onDownloadText = function (callbackStatus, remoteFileName, text) {};

/**
 * Callback for the {@link #uploadFile} method.
 * This field should be set to a function if notification is required when
 * the {@link #uploadFile} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file that was uploaded
 * @type callback
 * @see #uploadFile
 */
FTPClient.prototype.onUploadFile = function (callbackStatus, remoteFileName) {};

/**
 * Callback for the {@link #uploadText} method.
 * This field should be set to a function if notification is required when
 * the {@link #uploadText} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file that the text was uploaded to.
 * @type callback
 * @see #uploadText
 */
FTPClient.prototype.onUploadText = function (callbackStatus, remoteFileName) {};

/**
 * Callback for the {@link #size} method.
 * This field should be set to a function if notification is required when
 * the {@link #size} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file.
 * @param {int} size Size of the file in bytes.
 * @type callback
 * @see #size
 */
FTPClient.prototype.onSize = function (callbackStatus, remoteFileName, size) {};

/**
 * Callback for the {@link #modifiedTime} method.
 * This field should be set to a function if notification is required when
 * the {@link #modifiedTime} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file.
 * @param {Date} modTime Last modified time of the file.
 * @type callback
 * @see #modifiedTime
 */
FTPClient.prototype.onModifiedTime = function (callbackStatus, remoteFileName, modTime) {};

/**
 * Callback for the {@link #rename} method.
 * This field should be set to a function if notification is required when
 * the {@link #rename} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} fromName Previous name of file.
 * @param {string} toName New name of file.
 * @type callback
 * @see #rename
 */
FTPClient.prototype.onRename = function (callbackStatus, fromName, toName) {};

/**
 * Callback for the {@link #deleteFile} method.
 * This field should be set to a function if notification is required when
 * the {@link #deleteFile} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file.
 * @type callback
 * @see #deleteFile
 */
FTPClient.prototype.onDeleteFile = function (callbackStatus, remoteFileName) {};

/**
 * Callback for the {@link #deleteDirectory} method.
 * This field should be set to a function if notification is required when
 * the {@link #deleteDirectory} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteDirectoryName Name of the directory.
 * @type callback
 * @see #deleteDirectory
 */
FTPClient.prototype.onDeleteDirectory = function (callbackStatus, remoteDirectoryName) {};

/**
 * Callback for the {@link #exists} method.
 * This field should be set to a function if notification is required when
 * the {@link #exists} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file.
 * @param {boolean} exists true if the file exists and false otherwise.
 * @type callback
 * @see #exists
 */
FTPClient.prototype.onExists = function (callbackStatus, remoteFileName, exists) {};

/**
 * Callback for the {@link #changeDirectory} method.
 * This field should be set to a function if notification is required when
 * the {@link #changeDirectory} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteDirectory Name of remote working directory.
 * @type callback
 * @see #changeDirectory
 */
FTPClient.prototype.onChangeDirectory = function (callbackStatus, remoteDirectory) {};

/**
 * Callback for the {@link #createDirectory} method.
 * This field should be set to a function if notification is required when
 * the {@link #createDirectory} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteDirectory Name of the directory that was created.
 * @type callback
 * @see #createDirectory
 */
FTPClient.prototype.onCreateDirectory = function (callbackStatus, remoteDirectory) {};

/**
 * Callback for the {@link #localDirectoryList} method.
 * This field should be set to a function if notification is required when
 * the {@link #localDirectoryList} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} localDirectory Name of the directory being listed.
 * @param {FTPFileList} fileList {@link FTPFileList} containing the files.
 * @type callback
 * @see #localDirectoryList
 */
FTPClient.prototype.onLocalDirectoryList = function (callbackStatus, localDirectory, fileList) {};

/**
 * Callback for the {@link #localRename} method.
 * This field should be set to a function if notification is required when
 * the {@link #localRename} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} fromName Previous name of file.
 * @param {string} toName New name of file.
 * @type callback
 * @see #localRename
 */
FTPClient.prototype.onLocalRename = function (callbackStatus, fromName, toName) {};

/**
 * Callback for the {@link #localDeleteFile} method.
 * This field should be set to a function if notification is required when
 * the {@link #localDeleteFile} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file.
 * @type callback
 * @see #localDeleteFile
 */
FTPClient.prototype.onLocalDeleteFile = function (callbackStatus, remoteFileName) {};

/**
 * Callback for the {@link #localCreateDirectory} method.
 * This field should be set to a function if notification is required when
 * the {@link #localCreateDirectory} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} localDirectory Name of the directory that was created.
 * @type callback
 * @see #localCreateDirectory
 */
FTPClient.prototype.onLocalCreateDirectory = function (callbackStatus, localDirectory) {};

/**
 * Callback for the {@link #localSize} method.
 * This field should be set to a function if notification is required when
 * the {@link #localSize} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} localFileName Name of the file.
 * @param {int} size Size of the file in bytes.
 * @type callback
 * @see #localSize
 */
FTPClient.prototype.onLocalSize = function (callbackStatus, localFileName, size) {};

/**
 * Callback for the {@link #localModifiedTime} method.
 * This field should be set to a function if notification is required when
 * the {@link #localModifiedTime} method completes.  See the <a href="../devguide.html">Integral FTP
 * Developer's Guide</a> for details and examples.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} localFileName Name of the file.
 * @param {Date} modTime Last modified time of the file.
 * @type callback
 * @see #localModifiedTime
 */
FTPClient.prototype.onLocalModifiedTime = function (callbackStatus, localFileName, modTime) {};

/**
 * Callback for all errors.
 * This field should be set to a function if notification is required when
 * an error occurs.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @type callback
 */
FTPClient.prototype.onError = function (callbackStatus) {};

/**
 * Callback for transfer-progress updates.  This callback is invoked during
 * uploads and downloads at intervals of at least {@link #minTransferNotifyPeriod}
 * milliseconds.  It may be used to provide feedback for the user on the progress
 * of the transfer.
 * @param {FTPCallbackStatus} callbackStatus Status information (see {@link FTPCallbackStatus}).
 * @param {string} remoteFileName Name of the file being transferred.
 * @param {int} byteCount Number of bytes that have been transferred so far.
 * @type callback
 * @see #minTransferNotifyPeriod
 * @see #uploadFile
 * @see #downloadFile
 * @see #uploadText
 * @see #downloadText
 */
FTPClient.prototype.onTransferProgress = function (callbackStatus, remoteFileName, byteCount) {};


// METHOD DECLARATIONS -------------------------------------------------

FTPClient.prototype.initialize = FTPClient_initialize;
FTPClient.prototype.isInitialized = FTPClient_isInitialized;
FTPClient.prototype.getAllLogLevels = FTPClient_getAllLogLevels;
FTPClient.prototype.getLogLevel = FTPClient_getLogLevel;
FTPClient.prototype.setLogLevel = FTPClient_setLogLevel;
FTPClient.prototype.getConnectMode = FTPClient_getConnectMode;
FTPClient.prototype.setConnectMode = FTPClient_setConnectMode;
FTPClient.prototype.getTransferType = FTPClient_getTransferType;
FTPClient.prototype.setTransferType = FTPClient_setTransferType;
FTPClient.prototype.setTemplateURLs = FTPClient_setTemplateURLs;
FTPClient.prototype.isViewCachingEnabled = FTPClient_isViewCachingEnabled;
FTPClient.prototype.setViewCachingEnabled = FTPClient_setViewCachingEnabled;
FTPClient.prototype.isRemoteHostValid = FTPClient_isRemoteHostValid;
FTPClient.prototype.alert = FTPClient_alert;
FTPClient.prototype.ask = FTPClient_ask;
FTPClient.prototype.connect = FTPClient_connect;
FTPClient.prototype.isConnected = FTPClient_isConnected;
FTPClient.prototype.disconnect = FTPClient_disconnect;
FTPClient.prototype.changeDirectory = FTPClient_changeDirectory;
FTPClient.prototype.createDirectory = FTPClient_createDirectory;
FTPClient.prototype.directoryList = FTPClient_directoryList;
FTPClient.prototype.downloadFile = FTPClient_downloadFile;
FTPClient.prototype.downloadText = FTPClient_downloadText;
FTPClient.prototype.uploadFile = FTPClient_uploadFile;
FTPClient.prototype.uploadText = FTPClient_uploadText;
FTPClient.prototype.cancelTransfer = FTPClient_cancelTransfer;
FTPClient.prototype.size = FTPClient_size;
FTPClient.prototype.modifiedTime = FTPClient_modifiedTime;
FTPClient.prototype.deleteFile = FTPClient_deleteFile;
FTPClient.prototype.deleteDirectory = FTPClient_deleteDirectory;
FTPClient.prototype.rename = FTPClient_rename;
FTPClient.prototype.exists = FTPClient_exists;
FTPClient.prototype.localDirectoryList = FTPClient_localDirectoryList;
FTPClient.prototype.localDeleteFile = FTPClient_localDeleteFile;
FTPClient.prototype.localRename = FTPClient_localRename;
FTPClient.prototype.localCreateDirectory = FTPClient_localCreateDirectory;
FTPClient.prototype.localSize = FTPClient_localSize;
FTPClient.prototype.localModifiedTime = FTPClient_localModifiedTime;
FTPClient.prototype.getLocalHomeDirectory = FTPClient_getLocalHomeDirectory;
FTPClient.prototype.getLocalTempDirectory = FTPClient_getLocalTempDirectory;
FTPClient.prototype.getLocalAcceptorPort = FTPClient_getLocalAcceptorPort;
FTPClient.prototype.getRemoteAcceptorPort = FTPClient_getRemoteAcceptorPort;
FTPClient.prototype.getWorkingDirectory = FTPClient_getWorkingDirectory;
FTPClient.prototype.registerMimeType = FTPClient_registerMimeType;

/**
 * @ignore
 */
FTPClient.prototype._getAppletHTML = FTPClient__getAppletHTML;

/**
 * @ignore
 */
FTPClient.prototype._checkInitialization = FTPClient__checkInitialization;

/**
 * @ignore
 */
FTPClient.prototype._addHTML = FTPClient__addHTML;

/**
 * @ignore
 */
FTPClient.prototype._setTag = FTPClient__setTag;

/**
 * @ignore
 */
FTPClient.prototype._getTag = FTPClient__getTag;


// METHOD IMPLEMENTATIONS ----------------------------------------------

/**
 * Initializes the FTPClient instance [ASYNCHRONOUS - calls {@link #onInitialize} when complete].
 * This method must be called before any FTP operations are performed.  The main purpose
 * is to load the invisible Integral FTP applet onto the page.  The arguments determine
 * whether or not the EnterpriseDT splash will be displayed and the nature of the logging (if any).
 * @param {string} jarURL The absolute or relative URL of the IntegralFTP.jar file.
 * @param {boolean} showSplash Determines whether or not the EnterpriseDT splash will be displayed.
 * @param {string} logLevel Level of logging (Possible values are
 * "OFF", "ERROR", "INFO", "DEBUG"a nd "ALL").  Logging is written to the Java console
 * and optionally to a log-file (see below).
 * @param {boolean} logToFile If true then logging will be written to a file called
 * "IntegralFTP.log" in the machine's temporary directory.
 * @param {int} minPoolSize Minimum number of connections that the client will make to the server.
 * @param {int} maxPoolSize Maximum number of connections that the client may make to the server.
 * @see #onInitialize
 * @see #isInitialized
 */
function FTPClient_initialize(jarURL, showSplash, logLevel, logToFile, minPoolSize, maxPoolSize) 
{
	if (this._initStarted) {
		return;
	}
	this._initStarted = true;
	
	// applet positioning
	var w = 150;
	var h = 150;
	var divTop = (document.body.clientHeight - w)/3;
	var divLeft = (document.body.clientWidth - h)/2;
	var appletStyle = "width:" + w + "px;height:" + h + "px;";
	if (!showSplash)
		appletStyle = "visibility:hidden;";
    var divStyle = "position:absolute;top:" + divTop + "px;left:" + divLeft + "px;width:0px;height:0px"
	
	// applet parameters
	var appletParams = {};
	if (showSplash!==undefined && showSplash!==null) {
		appletParams.showSplash = showSplash;
		this.showSplash = showSplash;
	}
	if (minPoolSize!==undefined && minPoolSize!==null) {
		appletParams.minPoolSize = minPoolSize;
		this.minPoolSize = minPoolSize;
	}
	if (maxPoolSize!==undefined && maxPoolSize!==null) {
		appletParams.maxPoolSize = maxPoolSize;
		this.maxPoolSize = maxPoolSize;
	}
	if (logLevel!==undefined && logLevel!==null)
		appletParams.logLevel = logLevel;
	if (logToFile!==undefined && logToFile!==null)
		appletParams.logToFile = logToFile;
    var appletHTML = this._getAppletHTML("ftpApplet", "com/enterprisedt/net/ftp/applet/FTPApplet.class", jarURL, appletParams, appletStyle);
	this._addHTML(document, "<div style='" + divStyle + "'>" + appletHTML + "</div>");
}

/**
 * @ignore
 */
function FTPClient__getAppletHTML(appletName, classFile, jarURL, appletParams, appletStyle) 
{
    var appletHTML = "";
	var _info = navigator.userAgent; 
    var _ie = (_info.indexOf("MSIE") > 0 && _info.indexOf("Win") > 0 && _info.indexOf("Windows 3.1") < 0);
    var _ns = (navigator.appName.indexOf("Netscape") >= 0 && ((_info.indexOf("Win") > 0 && _info.indexOf("Win16") < 0) || (_info.indexOf("Sun") > 0) || (_info.indexOf("Linux") > 0) || (_info.indexOf("AIX") > 0) || (_info.indexOf("OS/2") > 0) || (_info.indexOf("IRIX") > 0)));
    var _ns6 = ((_ns == true) && (_info.indexOf("Mozilla/5") >= 0));
    if (_ie == true) {
		appletHTML += "<object \n";
		appletHTML += "    style='" + appletStyle + "'";
		appletHTML += "    classid = 'clsid:8AD9C840-044E-11D1-B3E9-00805F499D93'\n";
		appletHTML += "    codebase = 'http://java.sun.com/update/1.4.2/jinstall-1_4-windows-i586.cab#Version=1,4,0,0'\n";
		appletHTML += "    id = '" + appletName + "' >\n";
		appletHTML += "    	<param name=code value='" + classFile + "' >\n";
		appletHTML += "    	<param name=archive value='" + jarURL + "' >\n";
		appletHTML += "    	<param name=id value='" + appletName + "' >\n";
		appletHTML += "    	<param name=mayscript value='true' >\n";
		appletHTML += "    	<param name=type value='application/x-java-applet;version=1.4'>\n";
		appletHTML += "    	<param name=scriptable value='true'>\n";
		for (var p in appletParams)
			appletHTML += "    	<param name=" + p + " value='" + appletParams[p] + "'>\n";
		appletHTML += "</object>\n";
    } else if (_ns == true && _ns6 == false) {
		appletHTML += "<embed \n";
		appletHTML += "    style='" + appletStyle + "'";
		appletHTML += "    type = 'application/x-java-applet;version=1.4' \n";
		appletHTML += "	   code = '" + classFile + "' \n";
		appletHTML += "	   archive = '" + jarURL + "' \n";
		appletHTML += "	   id = '" + appletName + "' \n";
		appletHTML += "	   mayscript = 'true' \n";
		for (var p in appletParams)
			appletHTML += "  " + p + "='" + appletParams[p] + "'\n";
		appletHTML += "	   scriptable = true \n";
		appletHTML += "	   pluginspage = 'http://java.sun.com/products/plugin/index.asp#download'>\n";
		appletHTML += "</embed>\n";
	} else {
		appletHTML += "<applet code='" + classFile + "'";
		appletHTML += " archive='" + jarURL + "'";
		appletHTML += " id='" + appletName + "'";
		appletHTML += " mayscript='true'";
		appletHTML += " style='" + appletStyle + "'>\n";
		appletHTML += "    <param name=code value='" + classFile + "' >\n";
		appletHTML += "    <param name=archive value='" + jarURL + "' >\n";
		appletHTML += "    <param name=id value='" + appletName + "' >\n";
		appletHTML += "    <param name=mayscript value='yes' >\n";
		appletHTML += "    <param name=type value='application/x-java-applet;version=1.4'>\n";
		appletHTML += "    <param name=scriptable value='true'>\n";
		for (var p in appletParams)
			appletHTML += "    <param name=" + p + " value='" + appletParams[p] + "'>\n";
		appletHTML += "</applet>\n";
	}
	return appletHTML;
}

/**
 * @ignore
 */
function FTPClient__checkInitialization() 
{
	if (!this._initStarted) {
		throw "FTP client has not been initialized.  " +
			"FTPClient.initialize() must be called.";
	}
	else if (!this._initComplete) {
		throw "FTP client has finished initializing.  " +
			"Please wait for FTPClient.onInitialize() to be called.";
	}
}

/**
 * Returns true if the FTPClient has been initialized.
 * @return True if the FTPClient has been initialized.
 * @type int
 * @see #initialize
 */
function FTPClient_isInitialized() 
{
	return this._initComplete;
}
	
/**
 * Returns an array of strings containing the names of all the valid logging levels.
 * The names are "OFF", "ERROR", "WARNING", "INFO", "DEBUG" and "ALL".
 * @return An array of strings containing all available logging levels.
 * @type string[]
 * @see #getLogLevel
 * @see #setLogLevel
 */
function FTPClient_getAllLogLevels () 
{
	this._checkInitialization();
	var levels = this.applet.getAllLogLevels();
	for (var i=0; levels.length; i++) {
		levels[i] = ""+levels[i];		// convert to JavaScript string
	}
	return levels;
}

/**
 * Returns the current logging level.
 * @return The current logging level.
 * @type string
 * @see #setLogLevel
 * @see #getAllLogLevels
 */
function FTPClient_getLogLevel () 
{
	this._checkInitialization();
	return this.applet.getLogLevel();
}

/**
 * Sets the current logging level.
 * @param {string} level Desired logging level.
 * @see #getLogLevel
 * @see #getAllLogLevels
 */
function FTPClient_setLogLevel (level) 
{
	this._checkInitialization();
	this.applet.setLogLevel(level);
}

/**
 * Returns the current connection-mode.  This will be either "active" or
 * "passive".
 * @return The current connection-mode.
 * @type string
 * @see #setConnectMode
 */
function FTPClient_getConnectMode()
{
    this._checkInitialization();
    return "" + this.applet.getConnectMode();
}

/**
 * Sets the connection-mode of data-channels to "active" or "passive".
 * This value will be used in subsequent uploads, downloads and directory
 * listings. 
 * @param {string} mode Connection mode (must be "active" or "passive").
 * @see #getConnectMode
 */
function FTPClient_setConnectMode(mode)
{
    this._checkInitialization();
    try {
        this.applet.setConnectMode(mode);
    } catch (ex) {
        throw "Invalid connection-mode - must be 'active' or 'passive'.";
    }
}

/**
 * Returns the current retry-count, which determines the number of times
 * the client will retry a failing transfer before giving up.
 * @return The retry-count.
 * @type int
 * @see #setRetryCount
 * @see #getRetryDelay
 * @see #setRetryDelay
 */
function FTPClient_getRetryCount()
{
    this._checkInitialization();
    return this.applet.getRetryCount();
}

/**
 * Sets the current retry-count, which determines the number of times
 * the client will retry a failing transfer before giving up.
 * @param {int} count The retry-count.
 * @see #getRetryCount
 * @see #getRetryDelay
 * @see #setRetryDelay
 */
function FTPClient_setRetryCount(count)
{
    this._checkInitialization();
    this.applet.setRetryCount(count);
}

/**
 * Returns the current retry-delay (in milliseconds), which is the amount of time to wait
 * before retrying a failed transfer.
 * @return The retry-delay in milliseconds.
 * @type int
 * @see #getRetryCount
 * @see #setRetryCount
 * @see #setRetryDelay
 */
function FTPClient_getRetryDelay()
{
    this._checkInitialization();
    return this.applet.getRetryDelay();
}

/**
 * Sets the current retry-delay (in milliseconds), which is the amount of time to wait
 * before retrying a failed transfer.
 * @param {int} delay The retry-delay in milliseconds.
 * @see #getRetryCount
 * @see #setRetryCount
 * @see #getRetryDelay
 */
function FTPClient_setRetryDelay(delay)
{
    this._checkInitialization();
    this.applet.setRetryDelay(delay);
}

/**
 * Returns the current transfer-type.  This will be either "ascii" or
 * "passive".
 * @return The current transfer-type.
 * @type string
 * @see #setTransferType
 */
function FTPClient_getTransferType()
{
    this._checkInitialization();
    return "" + this.applet.getTransferType();
}

/**
 * Sets the connection-mode of data-channels to "ascii" or "passive".
 * This value will be used in subsequent uploads and downloads. 
 * @param {string} mode Connection mode (must be "ascii" or "passive").
 * @see #getTransferType
 */
function FTPClient_setTransferType(type)
{
    this._checkInitialization();
    try {
        this.applet.setTransferType(type);
    } catch (ex) {
        throw "Invalid connection-mode - must be 'ascii' or 'binary'.";
    }
}

/**
 * Sets the view and editing templates.
 * @param {string} localViewURL URL Template for viewing local files.
 * @param {string} localEditorURL URL Template for editing local files.
 * @param {string} remoteViewURL URL Template for viewing remote files.
 * @param {string} removeEditorURL URL Template for editing remote files.
 * @see #isViewCachingEnabled
 * @see #setViewCachingEnabled
 */
function FTPClient_setTemplateURLs(localViewURL, localTextEditorURL, localHTMLEditorURL, 
	remoteViewURL, remoteTextEditorURL, remoteHTMLEditorURL) 
{
	this._checkInitialization();
	this.applet.setTemplateURLs(localViewURL, localTextEditorURL, localHTMLEditorURL, 
		remoteViewURL, remoteTextEditorURL, remoteHTMLEditorURL);
}

/**
 * Returns true if view-caching is enabled.
 * @return True if view-catching is enabled.
 * @type boolean
 * @see #setViewCachingEnabled
 * @see #setTemplateURLs
 */
function FTPClient_isViewCachingEnabled() 
{
	this._checkInitialization();
	return this.applet.isViewCachingEnabled();
}

/**
 * Controls whether view caching is enabled (the default is true).
 * @param {boolean} enable Set to true is view caching is desired.
 * @see #isViewCachingEnabled
 * @see #setTemplateURLs
 */
function FTPClient_setViewCachingEnabled(enable) 
{
	this._checkInitialization();
	this.applet.setViewCachingEnabled(enable);
}

/**
 * Returns true is the given host-name can be parsed.
 * Host-names should be of the form "{subdomain}.{domain}:{port}.
 * @param (string) hostName Host-name to be checked.
 * @see #remoteHost
 */
function FTPClient_isRemoteHostValid (hostName) 
{
	try {
		if (hostName===undefined || hostName===null) {
			hostName = this.remoteHost;
		}
		new FTPEndPoint(hostName);
		return true;
	} catch (e) {
		return false;
	}
}

/**
 * Shows a message-box with an OK button [ASYNCHRONOUS - no callback].
 * This method is similar to the standard JavaScript alert function, but
 * it uses a Java dialog.  It is recommended that this method is used
 * in place of the JavaScript alert function for increase
 * stability.  If the JavaScript alert function is used then instability
 * can occur if the Integral FTP applet calls back to the browser's
 * JavaScript engine while the message-box is being shown.
 * @param {string} message Message to be displayed.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 * @see #ask
 */
function FTPClient_alert(message) 
{
	this._checkInitialization();
	return this.applet.alert(message);
}

/**
 * Prompts the user with a message and allows them to press one of 
 * two or three buttons [ASYNCHRONOUS - calls user-specified method when complete].
 * The callback method should have two arguments: <i>askResponse</i> ({@link FTPAskResponse})
 * and <i>tag</i> (object).
 * @param {string} message Message to be displayed as a prompt.
 * @param {callback} callbackFunction Function to be called when the user has pressed a button.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @param {string} button1Text Text of first button.
 * @param {string} button2Text Text of second button.
 * @param {string} button3Text Text of third button.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_ask(message, callbackFunction, tag, button1Text, button2Text, button3Text) 
{
	this._checkInitialization();
	if (typeof(callbackFunction)=="function") {
		callbackFunction = "" + callbackFunction;
		callbackFunction = callbackFunction.substring(0, callbackFunction.indexOf("("));
		callbackFunction = callbackFunction.replace("function", "");
		callbackFunction = callbackFunction.trim();
	}
	if (button1Text===undefined || button1Text===null) button1Text = "OK";
	if (button2Text===undefined || button2Text===null) button2Text = "Cancel";
	if (button3Text===undefined || button3Text===null) button3Text = null;
	var taskID = this.applet.ask(message, callbackFunction, button1Text, button2Text, button3Text);
	this._setTag(taskID, tag);
	return taskID
}
	
/**
 * Connect to an FTP server [ASYNCHRONOUS - calls {@link #onConnect} when complete].
 * The following actions must be done before calling this method:
 * <ul>
 * <li>Initialize the FTPClient by calling {@link #initialize}.</li>
 * <li>{@link #remoteHost} must be set to the host-name and (optionally) port of the FTP server.</li>
 * <li>{@link #userName} must be set to the user-name of user's account on the FTP server.</li>
 * <li>{@link #password} must be set to the password of user's account on the FTP server.</li>
 * </ul>
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 * @see #initialize
 * @see #remoteHost
 * @see #userName
 * @see #password
 * @see #disconnect
 */
function FTPClient_connect (tag) 
{
	this._checkInitialization();
    if (this.protocol!=null)
        this.applet.setProtocol(this.protocol);
	var endPoint = new FTPEndPoint(this.remoteHost);
	this.applet.setRemoteHost(endPoint.hostName);
	if (endPoint.portNumber!=null) {
		this.applet.setRemotePort(endPoint.portNumber);
	} else {
		this.applet.setRemotePort(-1);
	}
	this.applet.setUserName(this.userName);
	this.applet.setPassword(this.password);
	//this.applet.setMinTransferNotifyPeriod(this.minTransferNotifyPeriod);
	try {
		if (this.dateLanguages!==null)
		  this.applet.setDateLanguages(this.dateLanguages);
	} catch (e1) {
        throw (e1.message!==undefined) ? e1.message : e2;
	}
	try {
		var taskID = this.applet.connectAsync("FTPClient_onConnect");
		if (tag!==undefined && tag!==null) {
			this._setTag(taskID, tag);
		}
		return taskID;
	} catch (e2) {
		throw (e2.message!==undefined) ? e2.message : e2;
	}
}

/**
 * Returns true if this FTPClient is currently connected to a server.
 * @return True if this FTPClient is connected.
 * @type boolean
 * @see #connect
 * @see #disconnect
 */
function FTPClient_isConnected () 
{
	return this._isConnected;
}

/**
 * Disconnects from the server [ASYNCHRONOUS - calls {@link #onDisconnect} when complete].
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 * @see #connect
 */
function FTPClient_disconnect (tag) {
	this._checkInitialization();
	var taskID = this.applet.disconnectAsync("FTPClient_onDisconnect");
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Changes the working directory on the server
 * [ASYNCHRONOUS - calls {@link #onChangeDirectory} when complete].
 * @param {string} directory Directory to change into.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_changeDirectory(directory, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.changeDirectoryAsync("FTPClient_onChangeDirectory", directory);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Creates a new directory on the server
 * [ASYNCHRONOUS - calls {@link #onCreateDirectory} when complete].
 * @param {string} directory Name of the directory to create.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_createDirectory (directory, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.createDirectoryAsync("FTPClient_onCreateDirectory", directory);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * List the contents of a remote directory 
 * [ASYNCHRONOUS - calls {@link #onDirectoryList} when complete].
 * If no directory is specified then the current working directory is listed.
 * @param {string} directory Name of directory to list.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_directoryList (directory, tag) 
{
	this._checkInitialization();
	if (directory===undefined || directory===null || directory=="") {
		directory = "";
	}
	var taskID = this.applet.directoryListAsync(directory, "FTPClient_onDirectoryList");
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Downloads a file [ASYNCHRONOUS - calls {@link #onDownloadFile} when complete].
 * @param {string} localFileName Name of local file to download to.
 * @param {string} remoteFileName Name of remote file to download.
 * @param {int} writeMode Specifies whether a file is overwritten 
 * ({@link #WRITEMODE_OVERWRITE}), resumed ({@link #WRITEMODE_RESUME}) or 
 * appended to ({@link #WRITEMODE_APPEND}).
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_downloadFile (localFileName, remoteFileName, writeMode, tag) 
{
	this._checkInitialization();
	if (writeMode===undefined) {
		writeMode = 0;
	}
	var taskID = this.applet.downloadFileAsync("FTPClient_onDownloadFile", 
		"FTPClient_onTransferProgress", localFileName, remoteFileName, writeMode);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Downloads a file into a JavaScript string 
 * [ASYNCHRONOUS - calls {@link #onDownloadText} when complete].
 * @param {string} remoteFileName Name of the file to download.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * Note that since this is an asynchronous method, the result will be returned
 * its corresponding callback {@link #onDownloadText}.
 * @type int
 */
// writeMode must be 0 (overwrite), 1 (resume) or 2 (append)
function FTPClient_downloadText(remoteFileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.downloadTextAsync("FTPClient_onDownloadText", remoteFileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Uploads a file [ASYNCHRONOUS - calls {@link #onUploadFile} when complete].
 * @param {string} localFileName Name of the local file to upload.
 * @param {string} remoteFileName Name of the remote file to upload to.
 * @param {int} writeMode Specifies whether a file is overwritten 
 * ({@link #WRITEMODE_OVERWRITE}), resumed ({@link #WRITEMODE_RESUME}) or 
 * appended to ({@link #WRITEMODE_APPEND}).
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
// writeMode must be 0 (overwrite), 1 (resume) or 2 (append)
function FTPClient_uploadFile (localFileName, remoteFileName, writeMode, tag) 
{
	this._checkInitialization();
	if (writeMode===undefined) {
		writeMode = 0;
	}
	var taskID = this.applet.uploadFileAsync("FTPClient_onUploadFile", 
		"FTPClient_onTransferProgress", localFileName, remoteFileName, writeMode);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Uploads a JavaScript string to a file on the server 
 * [ASYNCHRONOUS - calls {@link #onUploadText} when complete].
 * @param {string} text String to upload.
 * @param {string} remoteFileName Name of the file to write on the remote server.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_uploadText (text, remoteFileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.uploadTextAsync("FTPClient_onUploadText", text, remoteFileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Cancels the transfer specified by the given identifier.
 * The identifier should be the one returned by one of the asynchronous
 * download or upload methods.
 * @param {int} transferID Identifier of the transfer to be cancelled.
 * @see #uploadFile
 * @see #uploadText
 * @see #downloadFile
 * @see #downloadText
 */
function FTPClient_cancelTransfer (transferID) 
{
	this._checkInitialization();
	this.applet.cancelTransferAsync(transferID);
}

/**
 * Gets the size of a file [ASYNCHRONOUS - calls {@link #onSize} when complete].
 * <b>IMPORTANT</b>: The integer returned by this method is NOT the size of the
 * file.  The size of the file will be provided to the onSize callback.
 * @param {string} remoteFileName Name of the remote file.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * Note that since this is an asynchronous method, the result will be returned
 * its corresponding callback {@link #onSize}.
 * @type int
 */
function FTPClient_size (remoteFileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.getSizeAsync("FTPClient_onSize", remoteFileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Gets the last-modified time of a file 
 * [ASYNCHRONOUS - calls {@link #onModifiedTime} when complete].
 * @param {string} remoteFileName Name of the file.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * Note that since this is an asynchronous method, the result will be returned
 * its corresponding callback {@link #onModifiedTime}.
 * @type int
 */
function FTPClient_modifiedTime (remoteFileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.getModifiedTimeAsync("FTPClient_onModifiedTime", remoteFileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Delete a file [ASYNCHRONOUS - calls {@link #onDeleteFile} when complete].
 * @param {string} remoteFileName Name of the file to delete.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_deleteFile (remoteFileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.deleteFileAsync("FTPClient_onDeleteFile", remoteFileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Delete a directory [ASYNCHRONOUS - calls {@link #onDeleteDirectory} when complete].
 * @param {string} remoteDirectoryName Name of the directory to delete.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_deleteDirectory (remoteDirectoryName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.deleteDirectoryAsync("FTPClient_onDeleteDirectory", remoteDirectoryName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Rename a file [ASYNCHRONOUS - calls {@link #onRename} when complete].
 * @param {string} fromFileName File to rename.
 * @param {string} toFileName New file-name.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_rename (fromFileName, toFileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.renameAsync("FTPClient_onRename", fromFileName, toFileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Checks for the existence of a file 
 * [ASYNCHRONOUS - calls {@link #onExists} when complete].
 * @param {string} remoteFileName Name of the file to find.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * Note that since this is an asynchronous method, the result will be returned
 * its corresponding callback {@link #onExists}.
 * @type int
 */
function FTPClient_exists (remoteFileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.existsAsync("FTPClient_onExists", remoteFileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * List the contents of a local directory  
 * [ASYNCHRONOUS - calls {@link #onLocalDirectoryList} when complete].
 * @param {string} dirPath Path of local directory to be listed.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * Note that since this is an asynchronous method, the result will be returned
 * its corresponding callback {@link #onLocalDirectoryList}.
 * @type int
 */
function FTPClient_localDirectoryList (dirPath, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.localDirectoryListAsync("FTPClient_onLocalDirectoryList", dirPath);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Delete a local file [ASYNCHRONOUS - calls {@link #onLocalDeleteFile} when complete].
 * @param {string} fileName Name of local file to delete.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_localDeleteFile (fileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.localDeleteAsync("FTPClient_onLocalDeleteFile", fileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Rename a local file [ASYNCHRONOUS - calls {@link #onLocalRename} when complete].
 * @param {string} oldFileName Path of file to rename.
 * @param {string} newFileName New name of file.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_localRename (oldFileName, newFileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.localRenameAsync("FTPClient_onLocalRename", oldFileName, newFileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Creates a directory on the local disk 
 * [ASYNCHRONOUS - calls {@link #onLocalCreateDirectory} when complete].
 * @param {string} directoryName Path of the directory to create.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * @type int
 */
function FTPClient_localCreateDirectory (directoryName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.localCreateDirectoryAsync("FTPClient_onLocalCreateDirectory", directoryName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Get the size of a local file 
 * [ASYNCHRONOUS - calls {@link #onLocalSize} when complete].
 * @param {string} fileName Path of the file.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * Note that since this is an asynchronous method, the result will be returned
 * its corresponding callback {@link #onLocalSize}.
 * @type int
 */
function FTPClient_localSize (fileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.localSizeAsync("FTPClient_onLocalSize", fileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Get the last-modified time of a local file 
 * [ASYNCHRONOUS - calls {@link #onLocalModifiedTime} when complete].
 * @param {string} fileName Path of the local file.
 * @param {object} tag The value of this argument is not used by FTPClient, but is simply
 * passed to the callback when the operation is complete.  From there is may be accessed
 * via the {@link FTPCallbackStatus#tag} field.
 * @return A unique identifier for this asynchronous operation.
 * Note that since this is an asynchronous method, the result will be returned
 * its corresponding callback {@link #onLocalModifiedTime}.
 * @type int
 */
function FTPClient_localModifiedTime (fileName, tag) 
{
	this._checkInitialization();
	var taskID = this.applet.localModifiedTimeAsync("FTPClient_onLocalModifiedTime", fileName);
	if (tag!==undefined && tag!==null) {
		this._setTag(taskID, tag);
	}
	return taskID;
}

/**
 * Gets the name of the user's home directory.
 * @return Name of the directory.
 * @type string
 */
function FTPClient_getLocalHomeDirectory () 
{
	this._checkInitialization();
	return ""+this.applet.getLocalHomeDirectory();
}

/**
 * Gets the name of the user's TEMP directory.
 * @return Name of the directory.
 * @type string
 */
function FTPClient_getLocalTempDirectory () 
{
	this._checkInitialization();
	return ""+this.applet.getLocalTempDirectory();
}

/**
 * Gets the number of the port to which HTTP requests may be directed
 * when viewing the contents of a local file.
 * @return Number of the port.
 * @type int
 */
function FTPClient_getLocalAcceptorPort () 
{
	this._checkInitialization();
	return this.applet.getLocalAcceptorPort();
}

/**
 * Gets the number of the port to which HTTP requests may be directed
 * when viewing the contents of a remote file.
 * @return Number of the port.
 * @type int
 */
function FTPClient_getRemoteAcceptorPort () 
{
	this._checkInitialization();
	return this.applet.getRemoteAcceptorPort();
}

/**
 * Get the current remote working directory.
 * @return the remote working directory.
 * @type string
 */
function FTPClient_getWorkingDirectory () 
{
	this._checkInitialization();
	return "" + this.applet.getWorkingDirectory();
}

/**
 * Registers an extension as a particular MIME type.
 * MIME types are used when viewing local or remote files
 * using Integral FTP's embedded HTTP proxy.
 * @param {string} extension File-extension
 * @param {string} mimeType MIME type
 */
function FTPClient_registerMimeType (extension, mimeType) 
{
	this._checkInitialization();
	this.applet.registerMimeType(extension, mimeType);
}

/**
 * @ignore
 */
function FTPClient__addHTML(doc, html) 
{
	if (doc.all) {
		doc.body.insertAdjacentHTML('beforeEnd', html);
	} else if (doc.createRange) {
		var range = doc.createRange();
		range.setStartAfter(doc.body.lastChild);
		var docFrag = range.createContextualFragment(html);
		doc.body.appendChild(docFrag);
	} else if (doc.layers) {
		var l = new Layer(1);
		l.doc.open();
		l.doc.write(html);
		l.doc.close();
		l.top = doc.height;
		doc.height += l.doc.height;
		l.visibility = 'show';
	}
}

/**
 * @ignore
 */
function FTPClient__setTag(taskID, tag) 
{
	this._tags.push(new FTPTagItem(taskID, tag))
}

/**
 * @ignore
 */
function FTPClient__getTag(taskID) 
{
	var index = -1;
	for (var i in this._tags) {
		if (this._tags[i].taskID===taskID) {
			index = i;
			break;
		}
	}
	var tag = null;
	if (index>=0) {
		tag = this._tags[index].tag;
		this._tags.splice(index, 1);
	}
	return tag;
}

// FTPClient Callbacks -------------------------------------------------------

/**
 * @ignore
 */
function FTPClient_onInitialize(taskID, errorMessage)
{
//	if (licenseDomain===null || licenseDomain===undefined
//		|| licenseKey===null || licenseKey===undefined) {
//		alert("No license found.  Make sure 'edtlicense.js' is included");
//		return;
//	}
	
	_ftpClient.applet = document.getElementById("ftpApplet");
	_ftpClient.separator = ""+_ftpClient.applet.getLocalSeparator();
	_ftpClient._initComplete = true;
//	_ftpClient.applet.setLicense(licenseDomain, licenseKey);
	_ftpClient.applet.setErrorCallback("FTPClient_onError");
	if (_ftpClient.showSplash)
		setTimeout("FTPClient_hideApplet();", 2000);
	if (_ftpClient.onInitialize!==null)
		_ftpClient.onInitialize(new FTPCallbackStatus(errorMessage===null, errorMessage, taskID));
}

/**
 * @ignore
 */
function FTPClient_hideApplet()
{
	_ftpClient.applet.style.width = 0;
	_ftpClient.applet.style.height = 0;
}

/**
 * @ignore
 */
function FTPClient_onError(taskID, errorMessage)
{
	if (_ftpClient.onError!==null) {
		_ftpClient.onError(new FTPCallbackStatus(errorMessage===null, errorMessage, taskID, _ftpClient._getTag(taskID)));
	}
}

/**
 * @ignore
 */
function FTPClient_onConnect(taskID)
{
	if (_ftpClient.onConnect!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		if (asyncResult!=null) {
			_ftpClient._isConnected = asyncResult.isSuccessful();
			_ftpClient.onConnect(FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID)));
		}
	}
}

/**
 * @ignore
 */
function FTPClient_onDisconnect(taskID)
{
	if (_ftpClient.onDisconnect!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		_ftpClient._isConnected = false;
		var reasonCode = asyncResult.getReason();
		var reasonMessage;
		switch (reasonCode) {
			case 0:
				reasonMessage = "Normal disconnection.";
				break;
			case 1:
				reasonMessage = "Inactive for too long.";
				break;
			case 2:
				reasonMessage = "Lost connection.";
				break;
			default:
				reasonMessage = "Unknown cause.";
				break;
		}
		_ftpClient.onDisconnect(FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID)), reasonCode, reasonMessage);
	}
}

/**
 * @ignore
 */
function FTPClient_onChangeDirectory(taskID)
{
	if (_ftpClient.onChangeDirectory!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var remoteDirectory = status.success ? (""+asyncResult.getRemoteDirectory()) : (""+asyncResult.getDirectory());
		_ftpClient.onChangeDirectory(status, remoteDirectory);
	}
}

/**
 * @ignore
 */
function FTPClient_onCreateDirectory(taskID)
{
	if (_ftpClient.onCreateDirectory!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var remoteDirectory = status.success ? (""+asyncResult.getRemoteDirectory()) : (""+asyncResult.getDirectory());
		_ftpClient.onCreateDirectory(status, remoteDirectory);
	}
}

/**
 * @ignore
 */
function FTPClient_onDirectoryList(taskID)
{
	if (_ftpClient.onDirectoryList!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		if (!status.success) {
			_ftpClient.onDirectoryList(status, null, null);
		} else {
			var dirPath = (""+asyncResult.getDirectory());
			var fileList = new FTPFileList();
			// Changed via recommendation of EnterpriseDT to correct error received by user
			//var files = eval(asyncResult.getListingAsJavascript());
			var files = eval("" + asyncResult.getListingAsJavascript());
			for (var f in files) {
				var file = files[f];
				if (file.name!=="." && file.name!=="..") {
					var fileDate = new Date();
					fileDate.setFromJavaDateString(file.modifiedDate);
					fileList.add(file.name, file.name, file.size, fileDate, file.isDirectory, file.isTextFile);
				}
			}
			if (dirPath!="/") {
				fileList.insert(0, null, "..", 0, null, true, false);
			}
		
			FTPClient_sortColumn = _ftpClient.remoteSortColumn;
			FTPClient_sortAscending = _ftpClient.remoteSortAscending;
			fileList.files.sort(FTPClient_compareFiles);
			
			_ftpClient.onDirectoryList(status, dirPath, fileList);
		}
	}
}

/**
 * @ignore
 */
function FTPClient_onTransferProgress(taskID, progressID, count)
{
	if (_ftpClient.onTransferProgress!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(progressID);
		var status = new FTPCallbackStatus(true, null, taskID, _ftpClient._getTag(taskID));
		var remoteFileName = (""+asyncResult.getRemoteFileName());
		_ftpClient.onTransferProgress(status, remoteFileName, count);
	}
}

/**
 * @ignore
 */
function FTPClient_onDownloadFile(taskID)
{
	if (_ftpClient.onDownloadFile!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var remoteFileName = status.success ? (""+asyncResult.getRemoteFileName()) : null;
		_ftpClient.onDownloadFile(status, remoteFileName);
	}
}

/**
 * @ignore
 */
function FTPClient_onDownloadText(taskID)
{
	if (_ftpClient.onDownloadText!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var remoteFileName = status.success ? (""+asyncResult.getRemoteFileName()) : null;
		var text = status.success ? (""+asyncResult.getText()) : null;
		_ftpClient.onDownloadText(status, remoteFileName, text);
	}
}

/**
 * @ignore
 */
function FTPClient_onUploadFile(taskID)
{
	if (_ftpClient.onUploadFile!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var remoteFileName = status.success ? (""+asyncResult.getRemoteFileName()) : null;
		_ftpClient.onUploadFile(status, remoteFileName);
	}
}

/**
 * @ignore
 */
function FTPClient_onUploadText(taskID)
{
	if (_ftpClient.onUploadText!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var remoteFileName = status.success ? (""+asyncResult.getRemoteFileName()) : null;
		_ftpClient.onUploadText(status, remoteFileName);
	}
}

/**
 * @ignore
 */
function FTPClient_onSize(taskID)
{
	if (_ftpClient.onSize!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var fileName = status.success ? (""+asyncResult.getRemoteFileName()) : null;
		var size = status.success ? asyncResult.getSize() : null;
		_ftpClient.onSize(status, fileName, size);
	}
}

/**
 * @ignore
 */
function FTPClient_onModifiedTime(taskID)
{
	if (_ftpClient.onModifiedTime!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var fileName = status.success ? (""+asyncResult.getRemoteFileName()) : null;
		var modTime = status.success ? asyncResult.getModifiedTime() : null;
		_ftpClient.onModifiedTime(status, fileName, modTime);
	}
}
	
/**
 * @ignore
 */
function FTPClient_onDeleteFile(taskID)
{
	if (_ftpClient.onDeleteFile!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var fileName = (""+asyncResult.getRemoteFileName());
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		_ftpClient.onDeleteFile(status, fileName);
	}
}
	
/**
 * @ignore
 */
function FTPClient_onDeleteDirectory(taskID)
{
	if (_ftpClient.onDeleteDirectory!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var dirName = (""+asyncResult.getRemoteDirectory());
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		_ftpClient.onDeleteDirectory(status, dirName);
	}
}
	
/**
 * @ignore
 */
function FTPClient_onRename(taskID)
{
	if (_ftpClient.onRename!==null) {
		var fromName = null;
		var toName = null;
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		if (status.success) {
			fromName = (""+asyncResult.getFromFileName());
			toName = (""+asyncResult.getToFileName());
		}
		_ftpClient.onRename(status, fromName, toName);
	}
}

/**
 * @ignore
 */
function FTPClient_onExists(taskID)
{
	if (_ftpClient.onExists!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		var fileName = status.success ? (""+asyncResult.getRemoteFileName()) : null;
		var exists = status.success ? asyncResult.exists() : null;
		_ftpClient.onExists(status, fileName, exists);
	}
}

/**
 * @ignore
 */
function FTPClient_onLocalDirectoryList(taskID)
{
	if (_ftpClient.onLocalDirectoryList!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		if (!status.success) {
			_ftpClient.onLocalDirectoryList(status, null, null);
		} else {
			var files = eval(asyncResult.getListingAsJavascript());
			var isRoot = false;
			var dirPath = (""+asyncResult.getDirectory());
			if (dirPath.length===0) {
				isRoot = true;
			}
			
			var fileList = new FTPFileList();
			if (!isRoot) {
				// chop off trailing separator
				var parentDir = dirPath.substring(0, dirPath.length-1);
				
				// now cut off last directory-name
				parentDir = parentDir.substring(0, parentDir.lastIndexOf(_ftpClient.separator));
				
				fileList.add(parentDir, "..", 0, null, true, false);
			}
			for (var f in files) {
				var file = files[f];
				var fileDate = new Date();
				fileDate.setFromJavaDateString(file.modifiedDate);
				fileList.add(file.path, file.name, file.size, fileDate, isRoot || file.isDirectory, 
						!file.isDirectory && file.isTextFile);
//				var javaFile = javaFiles.get(i);
//				var path = null;
//				var name = null;
//				var length = null;
//				var modifiedDate = null;
//				var isDirectory = null;
//				var isTextFile = null;
//				try {
//					path = javaFile.getRaw();
//					name = javaFile.getName();
//					length = parseInt(javaFile.size().toString());
//					modifiedDate = new Date();
//                    modifiedDate.setFromJavaDate(javaFile.lastModified());
//					isDirectory = isRoot || javaFile.isDir();
//					isTextFile = !isDirectory && _ftpClient.applet.isTextFile(name);
//				} catch (e) {
//					//error(e.message);
//				}
//				if (path!==null && name!==null && length!==null && modifiedDate!==null 
//					&& isDirectory!==null) {
//					fileList.add(path, name, length, modifiedDate, isDirectory, isTextFile);
//				}
			}
			
			FTPClient_sortColumn = _ftpClient.localSortColumn;
			FTPClient_sortAscending = _ftpClient.localSortAscending;
			fileList.files.sort(FTPClient_compareFiles);
			
			_ftpClient.onLocalDirectoryList(status, dirPath, fileList);
		}
	}	
}
	
/**
 * @ignore
 */
function FTPClient_onLocalRename(taskID)
{
	if (_ftpClient.onLocalRename!==null) {
		var fromName = null;
		var toName = null;
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		if (status.success) {
			fromName = (""+asyncResult.getFromFileName());
			toName = (""+asyncResult.getToFileName());
		}
		_ftpClient.onLocalRename(status, fromName, toName);
	}
}
	
/**
 * @ignore
 */
function FTPClient_onLocalDeleteFile(taskID)
{
	if (_ftpClient.onLocalDeleteFile!==null) {
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var fileName = (""+asyncResult.getFileName());
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		_ftpClient.onLocalDeleteFile(status, fileName);
	}
}
	
/**
 * @ignore
 */
function FTPClient_onLocalSize(taskID)
{
	if (_ftpClient.onLocalSize!==null) {
		var size = -1;
		var fileName = null;
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		if (status.success) {
			size = asyncResult.getSize();
			fileName = (""+asyncResult.getFileName());
		}
		_ftpClient.onLocalSize(status, fileName, size);
	}
}

/**
 * @ignore
 */
function FTPClient_onLocalModifiedTime(taskID)
{
	if (_ftpClient.onLocalModifiedTime!==null) {
		var modifiedTime = null;
		var fileName = null;
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		if (status.success) {
			modifiedTime = new Date();
            modifiedDate.setFromJavaDate(asyncResult.getModifiedTime());
			fileName = (""+asyncResult.getFileName());
		}
		_ftpClient.onLocalModifiedTime(status, fileName, modifiedTime);
	}
}

/**
 * @ignore
 */
function FTPClient_onLocalCreateDirectory(taskID)
{
	if (_ftpClient.onLocalCreateDirectory!==null) {
		var directoryPath = null;
		var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
		var status = FTPClient_createStatus(asyncResult, taskID, _ftpClient._getTag(taskID));
		if (status.success) {
			directoryPath = asyncResult.getDirectoryPath();
		}
		_ftpClient.onLocalCreateDirectory(status, directoryPath);
	}
}

/**
 * @ignore
 */
function FTPClient_onDialogResult(taskID)
{
	var asyncResult = _ftpClient.applet.getAsyncResult(taskID);
	var callbackMethod = asyncResult.getCallbackMethod();
	if (callbackMethod!==null && callbackMethod!==undefined) {
		var tag = _ftpClient._getTag(taskID);
		var callback = eval(""+callbackMethod);
		var message = asyncResult.getMessage();
		var button1Text = asyncResult.getButton1Text();
		var button2Text = asyncResult.getButton2Text();
		var button3Text = asyncResult.getButton3Text();
		var response = asyncResult.getResponse();
		callback(new FTPAskResponse(message, button1Text, button2Text, button3Text, response), tag);
	}
}

// FTPClient global method -------------------------------------------------------

/**
 * @ignore
 */
function FTPClient_compareFiles (file1, file2) {
	var result = 0;
	
	if (file1.isDirectory && !file2.isDirectory) {
		result = -1;
	} else if (!file1.isDirectory && file2.isDirectory) {
		result = 1;
	} else {
		if (FTPClient_sortColumn=="size") {
			result = file1.size - file2.size;
		}
		if (FTPClient_sortColumn=="date") {
			if (file1.modifiedDate===null && file2.modifiedDate!==null) {
				result = -1;
			} else if (file1.modifiedDate!==null && file2.modifiedDate===null) {
				result = 1;
			} else if (file1.modifiedDate!==null && file2.modifiedDate!==null) {
				result = file1.modifiedDate.getTime() - file2.modifiedDate.getTime();
			}
		}
		if (result===0) {
			var name1 = file1.name.toLowerCase();
			var name2 = file2.name.toLowerCase();
			result = name1 > name2 ? 1 : (name1 < name2 ? -1 : 0);
		}
	}
	
	if (FTPClient_sortAscending) {
		return result;
	} else {
		return result * -1;
	}
}


// FTPClient Callback Status ====================================================

/**
 * Construct an FTPCallbackStatus object.
 * @class Reports on the status of the an FTP operation.
 * FTPCallbackStatus objects are passed as the first argument of every
 * FTPClient callback.  It informs the callback of the success or failure
 * of the operations and the error-message (in case of failure).  It also
 * contains the unique identifier of the operation (returned by the
 * FTPClient method that launched the operation, as well as the tag that
 * was passed in when the operation was launched.  Please refer to the
 * <a href="../devguide.html">Integral FTP Developer's Guide</a> for details.
 * @param {boolean} success True if the operation succeeded.
 * @param {string} errorMessage Error message (in case of failure).
 * @param {int} taskID Task identifier.
 * @param {object} tag User-defined tag that was passed into the method.
 * that launched the operation.
 * @constructor
 */
function FTPCallbackStatus(success, errorMessage, taskID, tag)
{
	/**
	 * Flag indicating whether or not the operation succeeded.
	 * @type boolean
	 */
	this.success = success;
	
	/**
	 * Error message (in case of failure)
	 * @type string
	 */
	this.errorMessage = errorMessage!==null ? (""+errorMessage) : null;
	
	/**
	 * Unique identifier that identifies the task.
	 * @type int
	 */
	this.taskID = Number(taskID);
	
	/**
	 * User-defined tag that was passed into the method
	 * @type object
	 */
	this.tag = tag;
}

/**
 * @ignore
 */
function FTPClient_createStatus(asyncResult, taskID, tag)
{
	if (asyncResult===null)
		return new FTPCallbackStatus(false, "Internal Error: No result available.", taskID, tag);
		
	var success = asyncResult.isSuccessful();
	var exception = asyncResult.getThrowable();
	var errorMessage = exception!==undefined && exception!==null ? exception.getMessage() : null;
	return new FTPCallbackStatus(success, errorMessage, taskID, tag);
}


// FTPAskResponse =========================================================================

/**
 * Constructs an FTPAskResponse.
 * @class Represents a response to an {@link FTPClient@ask} invocation.  The
 * {@link #response} member contains the text of the button that the user pressed.
 * @constructor
 * @param {string} message Message that was displayed.
 * @param {string} button1Text Text of the first button.
 * @param {string} button2Text Text of the second button.
 * @param {string} button3Text Text of the third button.
 * @param {string} response Text of button that the user pressed.
 */
function FTPAskResponse(message, button1Text, button2Text, button3Text, response)
{
	/**
	 * Message that was displayed.
	 * @type string
	 */
	this.message = message;
	
	/**
	 * Text of the first button.
	 * @type string
	 */
	this.button1Text = button1Text;
	
	/**
	 * Text of the second button.
	 * @type string
	 */
	this.button2Text = button2Text;
	
	/**
	 * Text of the third button.
	 * @type string
	 */
	this.button3Text = button3Text;
	
	/**
	 * Text of button that the user pressed.
	 * @type string
	 */
	this.response = response;
}


// FTPFileSize ============================================================================

/**
 * @ignore
 */
FTPFileSize_byteUnits = [ "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"];

/**
 * Constructs an FTPFileSize object.
 * @class Represents a file-size.  This contains the size of the file in bytes as
 * an integer, a string-representation and the units of the string representation.
 * @param {int} numBytes Number of bytes in the file.
 * @constructor
 */
function FTPFileSize(numBytes) {
	/**
	 * Number of bytes in the file.
	 * @type int
	 */
	this.numBytes = numBytes;
	
	/**
	 * Units of the string-representation, {@link #numberString}.
	 * @type string
	 */
	this.units = FTPFileSize_byteUnits[0];
	
	/**
	 * String representation of the number.  The units 
	 * of this representation are given in {@link @units}.
	 * @type string
	 */
	this.numberString = "";
	
	var number = numBytes;
	var m = 1;
	while (number>=1024 && m<FTPFileSize_byteUnits.length) {
		number = Math.floor(number / 1024);
		this.units = FTPFileSize_byteUnits[m++];
	}
	number = ""+number;
	for (var i=0; i<number.length; i++) {
		this.numberString = this.numberString + number.charAt(i);
		if (i<number.length-1 && (number.length-i-1)%3===0) {
			this.numberString = this.numberString + ",";
		}
	}
}

// FTPRenderer ============================================================================

/**
 * Constructs an FTPRenderer object that may be used to write HTML to the given document.
 * @class An FTPRenderer may be used to produce an HTML representation of
 * an {@link FTPFileList}.  The main method is renderFileList, which will write the 
 * entire list to HTML.  If changes are required for particular elements of the list
 * then the FTPRenderer method that corresponds to that element may be overridden.
 * For example, by default names are rendered as
 * <tt >&lt;td class='ftpFile ftpName'&gt;fileName&lt;/td&gt;</tt>, but this may be changed
 * by overriding the {@link #renderFileName} method and changing it to write something
 * else.
 * @constructor
 * @type FTPRenderer
 */
function FTPRenderer(document)
{
	this.document = document;
	this.showSeconds = true;
}
	
FTPRenderer.prototype.renderFileList = FTPRenderer_renderFileList;
FTPRenderer.prototype.renderNameHeading = FTPRenderer_renderNameHeading;
FTPRenderer.prototype.renderSizeHeading = FTPRenderer_renderSizeHeading;
FTPRenderer.prototype.renderDateHeading = FTPRenderer_renderDateHeading;
FTPRenderer.prototype.renderFile = FTPRenderer_renderFile;
FTPRenderer.prototype.renderFileName = FTPRenderer_renderFileName;
FTPRenderer.prototype.renderFileSize = FTPRenderer_renderFileSize;
FTPRenderer.prototype.renderFileDate = FTPRenderer_renderFileDate;
FTPRenderer.prototype.renderDirectory = FTPRenderer_renderDirectory;
FTPRenderer.prototype.renderDirectoryName = FTPRenderer_renderDirectoryName;
FTPRenderer.prototype.renderDirectoryDate = FTPRenderer_renderDirectoryDate;
FTPRenderer.prototype.renderNameFooter = FTPRenderer_renderNameFooter;
FTPRenderer.prototype.renderSizeFooter = FTPRenderer_renderSizeFooter;
FTPRenderer.prototype.renderDateFooter = FTPRenderer_renderDateFooter;

/**
 * Writes the given file list to the FTPRenderer's document as a table.
 * The table will have the CSS style <tt>ftpList</tt>
 * @param {FTPFileList} fileList File-list to be renderered
 * @see FTPFileList
 */
function FTPRenderer_renderFileList(fileList) 
{
	this.document.writeln("<table class='ftpList'>");
	this.document.write("<tr>");
	this.document.writeln("</tr>");
	for (var i=0; i<fileList.files.length; i++)
	{
		var file = fileList.files[i];
		if (file.isDirectory) {
			this.renderDirectory(file);
		} else {
			this.renderFile(file);
		}
	}
	this.document.write("<tr>");
	this.renderNameFooter();
	this.document.writeln("</tr>");
	this.document.writeln("</table>");
}

/**
 * Called when writing the heading of the Name column.
 * The cell will have the CSS styles <tt>ftpHead ftpNameHead</tt>.
 */
function FTPRenderer_renderNameHeading() 
{
	this.document.write("<th class='ftpHead ftpNameHead'>Name</th>");
}

/**
 * Called when writing the heading of the Size column.
 * The cell will have the CSS styles <tt>ftpHead ftpSizeHead</tt>.
 */
function FTPRenderer_renderSizeHeading() 
{
	this.document.write("<th class='ftpHead ftpSizeHead' colspan='2'>Size</th>");
}

/**
 * Called when writing the heading of the Date column.
 * The cell will have the CSS styles <tt>ftpHead ftpDateHead</tt>.
 */
function FTPRenderer_renderDateHeading() 
{
	this.document.write("<th class='ftpHead ftpDateHead'>Date</th>");
}
	
/**
 * Called when rendering a file.
 * This method calls {@link #renderFileName}, {@link #renderFileSize}
 * and  {@link #renderFileDate}.
 * @param {FTPFile} file File to render.
 */
function FTPRenderer_renderFile(file) 
{
	this.document.writeln("<tr>");
	this.renderFileName(file.name, file.path, file.isTextFile);
	this.document.writeln("</tr>");
}

/**
 * Called when rendering a file-name.
 * @param {FTPFile} file File to render.
 */
function FTPRenderer_renderFileName(fileName, filePath, isTextFile) 
{
	this.document.writeln("<td class='ftpFile ftpName'>" + fileName + "</td>");
}

/**
 * Called when rendering a file-size.
 * @param {FTPFileSize} fileSize File-size to render.
 */
function FTPRenderer_renderFileSize(fileSize) 
{
	this.document.writeln("<td class='ftpFile ftpSizeNumber'>" + fileSize.numberString + "</td>");
	this.document.writeln("<td class='ftpFile ftpSizeUnits'>" + fileSize.units + "</td>");
}

/**
 * Called when rendering a file-date.
 * @param {Date} fileDate Date to render.
 */
function FTPRenderer_renderFileDate(fileDate) 
{
	this.document.write("<td class='ftpFile ftpDate'>" + fileDate.toFTPString(this.showSeconds) + "</td>");
}

/**
 * Called when rendering a directory.
 * @param {FTPFile} directory Directory to render.
 */
function FTPRenderer_renderDirectory(directory) 
{
	this.document.writeln("<tr>");
	this.renderDirectoryName(directory.name, directory.path);
	this.document.writeln("</tr>");
}

/**
 * Called when rendering a directory-name.
 * @param {string} directoryName Name of the directory.
 * @param {string} directoryPath Path of the directory.
 */
function FTPRenderer_renderDirectoryName(directoryName, directoryPath) 
{
	this.document.writeln("<td class='ftpFile ftpName'>" + directoryName + "</td>");
}

/**
 * Called when rendering a directory's date.
 * @param {Date} directoryDate Date of the directory.
 */
function FTPRenderer_renderDirectoryDate(directoryDate) 
{
	this.document.write("<td class='ftpFile ftpDate'>");
	if (directoryDate!==null) {
		this.document.write(directoryDate.toFTPString(this.showSeconds));
	}
	this.document.writeln("</td>");
}

/**
 * Called when rendering the Name column's footer.
 */
function FTPRenderer_renderNameFooter() 
{
	this.document.write("<td class='ftpFooter ftpNameFooter'>&nbsp;</td>");
}

/**
 * Called when rendering the Size column's footer.
 */
function FTPRenderer_renderSizeFooter() 
{
	this.document.write("<td class='ftpFooter ftpSizeFooter' colspan='2'>&nbsp;</td>");
}

/**
 * Called when rendering the Date column's footer.
 */
function FTPRenderer_renderDateFooter() 
{
	this.document.write("<td class='ftpFooter ftpDateFooter'>&nbsp;</td>");
}


// FTPFile ================================================================================

/**
 * Constructs an FTPFile object.
 * @class FTPFile contains the information describing the attributes of a
 * particular file, namely its path, name, size, last-modified date,
 * whether or not it's a directory and whether or not it's likely to be
 * a text-file.
 * @param {string} filePath Full path of the file.
 * @param {string} fileName Name of the file.
 * @param {FTPFileSize} size Size of the file.
 * @param {Date} modifiedDate Date when the file was last modified.
 * @param {boolean} isDirectory True if the object represents a directory.
 * @param {boolean} isTextFile True if the file is likely to be a text-file.
 * @constructor
 */
function FTPFile(filePath, fileName, size, modifiedDate, isDirectory, isTextFile)
{
	/**
	 * Full path of the file.
	 * @type string
	 */
	this.path = filePath!==null ? (""+filePath) : null;	// convert from Java to JS
	
	/**
	 * Name of the file.
	 * @type string
	 */
	this.name = fileName!==null ? (""+fileName) : null;	// convert from Java to JS
	
	/**
	 * Size of the file.
	 * @type FTPFileSize
	 */
	this.size = size;
	
	/**
	 * Date when the file was last modified.
	 * @type Date
	 */
	this.modifiedDate = modifiedDate;
	
	/**
	 * True if the object represents a directory.
	 * @type boolean
	 */
	this.isDirectory = (isDirectory===true);
	
	/**
	 * True if the file is likely to be a text-file.
	 * @type boolean
	 */
	this.isTextFile = isTextFile;
}

// FTPEndPoint ===========================================================================
	
/**
 * @ignore
 */
function FTPEndPoint(text) {
	text = text.trim();
	if (text.length===0) {
		throw "No server address supplied.";
	}
	var invalidMessage = "Invalid server address: must be of the form 'address[:port]'";
	var tokens = text.split(':');
	if (tokens.length>2) {
		throw invalidMessage;
	}
	var port = tokens.length>1 ? parseInt(tokens[1]): null;
	
	/**
	 * @ignore
	 */
	this.hostName = tokens[0];
	/**
	 * @ignore
	 */
	this.portNumber = port!==NaN ? port : null;
}

// FTPFileList ===========================================================================

/**
 * Constructs a file-list object.
 * @class An instance of this class represents a collection of files - usually
 * those in a particular directory.  It's important to note that FTPFileList
 * is not itself an array - the member {@link #files} is an array of {@link FTPFile}s.
 * @constructor
 */
function FTPFileList()
{
	/**
	 * An array of files.
	 * @type FTPFile
	 */
	this.files = [];
	
	/**
	 * Add a file to the end of the {@link #files} array.
	 * @param {string} filePath Full path of the file.
	 * @param {string} fileName Name of the file.
	 * @param {FTPFileSize} size Size of the file.
	 * @param {Date} modifiedDate Date when the file was last modified.
	 * @param {boolean} isDirectory True if the object represents a directory.
	 * @param {boolean} isTextFile True if the file is likely to be a text-file.
	 */
	this.add = function(path, name, size, date, isDirectory, isTextFile) {
		var file = new FTPFile(path, name, size, date, isDirectory, isTextFile);
		this.files.push(file);
	};
	
	/**
	 * Inserts a file at the given index of the {@link #files} array.
	 * @param {int} index Index at which to insert the file.
	 * @param {string} filePath Full path of the file.
	 * @param {string} fileName Name of the file.
	 * @param {FTPFileSize} size Size of the file.
	 * @param {Date} modifiedDate Date when the file was last modified.
	 * @param {boolean} isDirectory True if the object represents a directory.
	 * @param {boolean} isTextFile True if the file is likely to be a text-file.
	 */
	this.insert = function(index, path, name, size, date, isDirectory, isTextFile) {
		var file = new FTPFile(path, name, size, date, isDirectory, isTextFile);
		this.files.splice(0, 0, file);
	};
	
	/**
	 * Returns true of a file with the give path is in the list.
	 * @param {string} path Path to search for.
	 * @return True if the list contains a file with the given path.
	 * @type boolean
	 */
	this.containsFilePath = function(path) {
		for (file in this.files) {
			if (this.files[i].path==path) {
				return true;
			}
		}
		return false;
	};
	
	/**
	 * Returns true of a file with the give name is in the list.
	 * @param {string} name Name to search for.
	 * @return True if the list contains a file with the given name.
	 * @type boolean
	 */
	this.containsFileName = function(name) {
		return this.findByFileName(name)!==null;
	};
	
	/**
	 * Returns the file with the give name or null if there isn't one in the list.
	 * @param {string} name Name to search for.
	 * @return The file or null.
	 * @type FTPFile
	 */
	this.findByFileName = function(name) {
		for (i in this.files) {
			if (this.files[i].name==name) {
				return this.files[i];
			}
		}
		return null;
	};
}

// FTPTagItem ---------------------------------------------------------------------

/**
 * @ignore
 */
function FTPTagItem(taskID, tag) {
	this.taskID = taskID;
	this.tag = tag;
}

// String methods -----------------------------------------------------------------

/**
 * @ignore
 */
String.prototype.trim = function() { 
	return this.replace(/^\s+|\s+$/, ''); 
};

/**
 * @ignore
 */
String.prototype.startsWith = function(sub) {
	return sub!==null && this.substring(0, sub.length)===sub;
};

// Date methods -----------------------------------------------------------------
	
/**
 * @ignore
 */
Date.prototype.toFTPString = function(showSeconds) {
	var dateString = this.getFullYear() + 
		"-" + this._pad(this.getMonth()+1, "0", 2, true) + 
		"-"  + this._pad(this.getDate(), "0", 2, true) + 
		" " + this._pad(this.getHours(), "0", 2, true) + 
		":"  + this._pad(this.getMinutes(), "0", 2, true);
	if (showSeconds) {
		dateString = dateString + ":" + this._pad(this.getSeconds(), "0", 2, true);
	}
	return dateString;
};

Date.prototype.setFromJavaDateString = function(javaDateString) {
    this.setTime(Date.parse(javaDateString));
};

Date.prototype.setFromJavaDate = function(javaDate) {
	this.setFromJavaDateString(javaDate.toGMTString());
};

/**
 * @ignore
 */
Date.prototype._pad = function(obj, padChar, width, padLeft) {
	var text = (""+obj);
	for (var i=text.length; i<width; i++) {
		text = padLeft ? padChar + text : text + padChar;
	}
	return text;
};

