/*
 * @(#)FtpTransfer.java
 *
 * Copyright (c) 1999-2007 JSCAPE LLC.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */
package com.jscape.filetransfer;

import com.jscape.inet.ftp.*;
import com.jscape.util.Parameters;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicReference;

import static com.jscape.filetransfer.FileTransferException.wrap;

/**
 * Implements the basic functionality of a FTP client.
 * <p/>
 * <p/> Example Usage:
 * <p/>
 * <p/>
 * <p/>
 * <pre>
 *  // create new FtpTransfer instance
 *  FileTransfer ftp = new FtpTransfer(&quot;ftp.myserver.com&quot;,&quot;anonymous&quot;,&quot;user@myserver.com&quot;);
 *  try {
 *  // connect to FTP server
 *  ftp.connect();
 *  &lt;p/&gt;
 *  // print out directory listing
 *  System.out.println(ftp.getDirListingAsString());
 *  &lt;p/&gt;
 *  // disconnect from FTP server
 *  ftp.disconnect();
 *  } catch(Exception e) {
 *  System.out.println(e);
 *  }
 * </pre>
 */
@SuppressWarnings("unchecked")
public class FtpTransfer
        implements FileTransfer {

    private final Ftp ftp;
    private final Set<FileTransferListener> listeners;
    private boolean mlsdSupported;
    private boolean useMLSDListing;
    private boolean useExtendedDirectoryListing;
    private boolean checksumVerificationRequired;
    private final FtpEventConverter eventConverter;

    public FtpTransfer(String host, int port, String user, String password, File localDirectory, Set<FileTransferListener> listeners) {
        this.ftp = new Ftp(host, user, password, port);

        this.listeners = new CopyOnWriteArraySet<>(listeners);

        this.useMLSDListing = true;

        this.eventConverter = new FtpEventConverter();

        this.ftp.setLocalDir(localDirectory);
        this.ftp.addFtpListener(this.eventConverter);
    }

    /**
     * Creates FtpTransfer instance.
     *
     * @param host           the hostname of FTP server
     * @param user           the username of FTP user
     * @param pwd            the password of FTP user
     * @param localDirectory the local directory to be used for uploading / downloading
     *                       files
     */
    public FtpTransfer(String host, String user, String pwd, File localDirectory) {
        this(host, 21, user, pwd, localDirectory, Collections.<FileTransferListener>emptySet());
    }

    /**
     * Creates FtpTransfer instance.
     *
     * @param host the hostname of FTP server
     * @param user the username of FTP user
     * @param pwd  the password of FTP user
     * @param port the port of FTP server
     */
    public FtpTransfer(String host, String user, String pwd, int port) {
        this(host, port, user, pwd, new File("."), Collections.<FileTransferListener>emptySet());
    }

    /**
     * Creates FtpTransfer instance.
     *
     * @param host the hostname of FTP server
     * @param user the username of FTP user
     * @param pwd  the password of FTP user
     * @param port the port of FTP server
     */
    public FtpTransfer(String host, String user, String pwd, Integer port) {
        this(host, user, pwd, port.intValue());
    }

    /**
     * Creates FtpTransfer instance.
     *
     * @param host the hostname of FTP server
     * @param user the username of FTP user
     * @param pwd  the password of FTP user
     */
    public FtpTransfer(String host, String user, String pwd) {
        this(host, user, pwd, new File("."));
    }

    /**
     * Creates FtpTransfer instance.
     */
    public FtpTransfer() {
        this("", "", "");
    }

    /**
     * @throws FileTransferException FileTransferException instance
     */
    public FtpTransfer connect()
            throws FileTransferException {
        try {
            this.ftp.connect();

            try {
                this.mlsdSupported = ftp.isFeatureSupported("MLSD");
            } catch (FtpException e) {
                this.mlsdSupported = false;
            }

            return this;
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * Establishes a connection with FTP server with optional login
     *
     * @param login true to login, false otherwise
     *
     * @throws FileTransferException
     */
    public void connect(boolean login)
            throws FileTransferException {
        try {
            ftp.connect(login);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * Logs into FTP server after establishing a connection. This method should
     * only be used if login is not performed automatically during connect.
     *
     * @throws FileTransferException if an I/O or protocol error occurs
     */
    public void login()
            throws FileTransferException {
        try {
            ftp.login();
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    public FileTransfer setPreserveFileUploadTimestamp(boolean value) {
        this.ftp.setPreserveUploadTimestamp(value);
        return this;
    }

    public FileTransfer setPreserveFileDownloadTimestamp(boolean value) {
        this.ftp.setPreserveDownloadTimestamp(value);
        return this;
    }

    @Override
    public boolean isChecksumVerificationRequired() {
        return this.checksumVerificationRequired;
    }

    @Override
    public FileTransfer setChecksumVerificationRequired(boolean value) {
        this.checksumVerificationRequired = value;
        return this;
    }

    public FileTransfer setTimeZone(TimeZone timeZone) {
        this.ftp.setTimezone(timeZone);
        return this;
    }

    /**
     * Issues command to the server.
     *
     * @param command a command
     *
     * @throws FileTransferException if an error occurs
     */
    public void issueCommand(String command)
            throws FileTransferException {
        try {
            this.ftp.issueCommand(command);
        } catch (Exception e) {
            throw wrap(e);
        }
    }


    /**
     * Changes the password.
     *
     * @param oldPassword the old password.
     * @param newPassword the new password.
     *
     * @throws FileTransferException if I/O or protocol error occurs.
     */
    public void changePassword(String oldPassword, String newPassword)
            throws FileTransferException {
        try {
            ftp.changePassword(oldPassword, newPassword);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#deleteDir(String)
     */
    public void deleteDir(String name)
            throws FileTransferException {
        try {
            ftp.deleteDir(name);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#deleteDir(String, boolean)
     */
    public void deleteDir(String name, boolean recursive)
            throws FileTransferException {
        try {
            ftp.deleteDir(name, recursive);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    @Override
    public boolean exists(String path)
            throws FileTransferException {
        try {
            getFilesize(path);
            return true;
        } catch (Exception e1) {
            try {
                getFileTimestamp(path);
                return true;
            } catch (Exception e2) {
                return isDirectory(path);
            }
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#deleteFile(String)
     */
    public void deleteFile(String remoteFile)
            throws FileTransferException {
        try {
            ftp.deleteFile(remoteFile);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#disconnect()
     */
    public void disconnect()
            throws FileTransferException {
        try {
            ftp.disconnect();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    @Override
    public void close() {
        try {
            disconnect();
        } catch (Exception e) {
            // ignore
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#download(OutputStream, String)
     */
    public void download(OutputStream out, String remoteFile)
            throws FileTransferException {
        try {
            ftp.download(out, remoteFile);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#download(String)
     */
    public File download(String remoteFile)
            throws FileTransferException {
        try {
            File file = ftp.download(remoteFile);
            safeCheckChecksum(file, remoteFile);
            this.eventConverter.raiseDownloadEvent();

            return file;
        } catch (Exception e) {
//            if (!ftp.interrupted()) {
//                try {
//                    raiseErrorEvent(new FileTransferErrorEvent(this,
//                                                               remoteFile,
//                                                               ftp.getDir(),
//                                                               ftp.getLocalDir() + File.separator + remoteFile,
//                                                               FileTransferProgressEvent.DOWNLOAD));
//                } catch (Exception x) {
//                    // todo: ignore?
//                }
//            }

            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#download(String, String)
     */
    public File download(String localFile, String remoteFile)
            throws FileTransferException {
        try {
            File file = ftp.download(localFile, remoteFile);
            safeCheckChecksum(file, remoteFile);
            this.eventConverter.raiseDownloadEvent();

            return file;
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#downloadDir(String)
     */
    public void downloadDir(String remoteDir)
            throws FileTransferException {
        try {
            ftp.downloadDir(remoteDir);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * Downloads remote directory and contents.
     * Downloads are stored relative to current local directory.
     *
     * @param remoteDir       the directory to download
     * @param retryLimit      Retry number when an error occurs
     * @param performChecksum Checksum perform indicator
     *
     * @throws FtpException if an I/O or protocol error occurs.
     */
    public void downloadDir(String remoteDir, int retryLimit, boolean performChecksum)
            throws FileTransferException {
        try {
            this.ftp.downloadDir(remoteDir, retryLimit, performChecksum);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getDebug()
     */
    public boolean getDebug() {
        return ftp.getDebug();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getDebugStream()
     */
    public PrintStream getDebugStream() {
        return ftp.getDebugStream();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getDir()
     */
    public String getDir()
            throws FileTransferException {
        try {
            return ftp.getDir();
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    private Vector createRemoteFileElements(Enumeration entries)
            throws FileTransferException {
        Vector results = new Vector();
        try {
            while (entries.hasMoreElements()) {
                FtpFile file = (FtpFile) entries.nextElement();

                if (".".equals(file.getFilename()) || "..".equals(file.getFilename())) {
                    continue;
                }

                FileTransferRemoteFile rf = new FileTransferRemoteFile();
                rf.setFilename(file.getFilename());
                rf.setDirectory(file.isDirectory());
                rf.setLink(file.isLink());
                rf.setFilesize(file.getFilesize());
                rf.setLinkTarget(file.getLinkTarget());
                Date fileDate = this.ftp.getFtpFileParser().getDateTime(file);

                rf.setFileDate(fileDate);
                results.addElement(rf);
            }

            return results;
        } catch (ParseException e) {
            throw wrap(e);
        }
    }

    /**
     * Get MLSD directory listing.
     *
     * @return boolean value
     */
    public boolean getPerformMLSDListing() {
        return this.useMLSDListing;
    }

    /**
     * Set MLSD directory listing.
     *
     * @param mlsdListing - boolean value
     */
    public FtpTransfer setPerformMLSDListing(boolean mlsdListing) {
        this.useMLSDListing = mlsdListing;
        return this;
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getDirListing()
     */
    public Enumeration<FileTransferRemoteFile> getDirListing()
            throws FileTransferException {
        try {
            Enumeration entries = null;
            FtpFileParser oldParser = null;

            if (this.mlsdSupported && this.useMLSDListing) {
                entries = ftp.getMachineDirListing("");
                oldParser = ftp.getFtpFileParser();
                ftp.setFtpFileParser(new MLSDParser());
            } else {
                if (useExtendedDirectoryListing) {
                    entries = ftp.getDirListing("-a");
                } else {
                    entries = ftp.getDirListing();
                }
            }

            Vector results = createRemoteFileElements(entries);
            if (oldParser != null) {
                ftp.setFtpFileParser(oldParser);
            }

            return results.elements();
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getDirListing(String)
     */
    public Enumeration<FileTransferRemoteFile> getDirListing(String filter)
            throws FileTransferException {
        try {
            Enumeration entries = null;
            FtpFileParser oldParser = null;

            if (mlsdSupported) {
                entries = ftp.getMachineDirListing("", filter);
                oldParser = ftp.getFtpFileParser();
                ftp.setFtpFileParser(new MLSDParser());
            } else {
                entries = ftp.getDirListingRegex(filter);
            }
            Vector results = createRemoteFileElements(entries);

            if (oldParser != null) {
                ftp.setFtpFileParser(oldParser);
            }
            return results.elements();
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getDirListingAsString()
     */
    public String getDirListingAsString()
            throws FileTransferException {
        try {
            return ftp.getDirListingAsString();
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getDirListingAsString(String)
     */
    public String getDirListingAsString(String filter)
            throws FileTransferException {
        try {
            return ftp.getDirListingRegexAsString(filter);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    @Override
    public boolean isDirectory(String remotePath)
            throws FileTransferException {
        String currentDir = getDir();
        try {
            setDir(remotePath);
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            try {
                setDir(currentDir);
            } catch (Exception e) {
                // ignore
            }
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getFilesize(String)
     */
    public long getFilesize(String remoteFile)
            throws FileTransferException {
        try {
            return ftp.getFilesize(remoteFile);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getFileTimestamp(String)
     */
    public Date getFileTimestamp(String remoteFile)
            throws FileTransferException {
        try {
            return ftp.getFileTimestamp(remoteFile);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setFileTimestamp(String, Date)
     */
    public FileTransfer setFileTimestamp(String remoteFile, Date timestamp)
            throws FileTransferException {
        try {
            ftp.setFileTimestamp(remoteFile, timestamp);
            return this;
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * Set modification time.
     */
    public FileTransfer setFileModificationTime(String remoteFile, Date dateFile)
            throws FileTransferException {
        try {
            ftp.setFileModificationTime(remoteFile, dateFile);
            return this;
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    public boolean sameChecksum(File localFile, String remoteFile)
            throws FileTransferException {
        try {
            return this.ftp.checksum(localFile, remoteFile);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getHostname()
     */
    public String getHostname() {
        return ftp.getHostname();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getLocalDir()
     */
    public File getLocalDir() {
        return ftp.getLocalDir();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getLocalDirListing()
     */
    public Enumeration getLocalDirListing() {
        return ftp.getLocalDirListing();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getMode()
     */
    public int getMode() {
        return ftp.getMode();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getNameListing()
     */
    public Enumeration getNameListing()
            throws FileTransferException {
        try {
            return ftp.getNameListing();
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getNameListing(String)
     */
    public Enumeration getNameListing(String filter)
            throws FileTransferException {
        try {
            return ftp.getNameListing(filter);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getPassive()
     */
    public boolean getPassive() {
        return ftp.getPassive();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getPassword()
     */
    public String getPassword() {
        return ftp.getPassword();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getPort()
     */
    public int getPort() {
        return ftp.getPort();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getTimeout()
     */
    public long getTimeout() {
        return ftp.getTimeout();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#getUsername()
     */
    public String getUsername() {
        return ftp.getUsername();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#interrupt()
     */
    public void interrupt() {
        ftp.interrupt();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#interrupted()
     */
    public boolean interrupted() {
        return ftp.interrupted();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#isConnected()
     */
    public boolean isConnected() {
        return ftp.isConnected();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#makeDir(String)
     */
    public void makeDir(String name)
            throws FileTransferException {
        try {
            ftp.makeDir(name);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#makeDirRecursive(String)
     */
    public void makeDirRecursive(String name)
            throws FileTransferException {
        try {
            ftp.makeDirRecursive(name);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * Deletes multiple files matching filter to FTP server's current directory.
     * Wildcards may be used to specify multiple files e.g. .*\.txt
     *
     * @param filter the regular expression filter to use
     *
     * @throws FileTransferException if an I/O or protocol error occurs.
     */
    public void mdelete(String filter)
            throws FileTransferException {
        try {
            ftp.mdelete(filter);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#makeLocalDir(String)
     */
    public File makeLocalDir(String name) {
        return ftp.makeLocalDir(name);
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#mdownload(Enumeration)
     */
    public void mdownload(Enumeration fileNames)
            throws FileTransferException {
        try {
            ftp.mdownload(fileNames);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#mdownload(String)
     */
    public void mdownload(String filter)
            throws FileTransferException {
        try {
            ftp.mdownload(filter);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#mupload(Enumeration)
     */
    public void mupload(Enumeration fileNames)
            throws FileTransferException {
        try {
            ftp.mupload(fileNames);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#mupload(String)
     */
    public void mupload(String filter)
            throws FileTransferException {
        try {
            ftp.mupload(filter);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#renameFile(String, String)
     */
    public void renameFile(String remoteFile, String newFile)
            throws FileTransferException {
        try {
            ftp.renameFile(remoteFile, newFile);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#reset()
     */
    public void reset() {
        ftp.reset();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#resumeDownload(String, long)
     */
    public void resumeDownload(String remoteFile, long offset)
            throws FileTransferException {
        try {
            File file = ftp.resumeDownload(remoteFile, offset);
            safeCheckChecksum(file, remoteFile);
            this.eventConverter.raiseDownloadEvent();
        } catch (Exception e) {
//            if (!ftp.interrupted()) {
//                try {
//                    raiseErrorEvent(new FileTransferErrorEvent(this,
//                                                               remoteFile,
//                                                               ftp.getDir(),
//                                                               ftp.getLocalDir() + File.separator + remoteFile,
//                                                               FileTransferProgressEvent.DOWNLOAD));
//                } catch (Exception x) {
//                    // todo: ignore?
//                }
//            }

            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#resumeDownload(String, String, long)
     */
    public void resumeDownload(String localFile, String remoteFile, long offset)
            throws FileTransferException {
        try {
            File file = ftp.resumeDownload(localFile, remoteFile, offset);
            safeCheckChecksum(file, remoteFile);
            this.eventConverter.raiseDownloadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#resumeUpload(File, long)
     */
    public void resumeUpload(File file, long offset)
            throws FileTransferException {
        try {
            ftp.resumeUpload(file, offset);
            safeCheckChecksum(file, file.getName());
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#resumeUpload(String, long)
     */
    public void resumeUpload(String file, long offset)
            throws FileTransferException {
        try {
            ftp.resumeUpload(file, offset);
            safeCheckChecksum(new File(getLocalDir(), file), file);
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }

    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setAscii()
     */
    public FileTransfer setAscii() {
        try {
            ftp.setAscii();
            return this;
        } catch (FtpException fex) {
            return this;
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setAuto(boolean)
     */
    public FileTransfer setAuto(boolean auto) {
        ftp.setAuto(auto);
        return this;
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setBinary()
     */
    public FileTransfer setBinary() {
        try {
            ftp.setBinary();
            return this;
        } catch (FtpException fex) {
            return this;
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setDebug(boolean)
     */
    public FileTransfer setDebug(boolean debug) {
        ftp.setDebug(debug);
        return this;
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setDebugStream(PrintStream)
     */
    public FileTransfer setDebugStream(PrintStream ps) {
        ftp.setDebugStream(ps);
        return this;
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setDir(String)
     */
    public FileTransfer setDir(String name)
            throws FileTransferException {
        try {
            ftp.setDir(name);
            return this;
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setDirUp()
     */
    public FileTransfer setDirUp()
            throws FileTransferException {
        try {
            ftp.setDirUp();
            return this;
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setHostname(String)
     */
    public FileTransfer setHostname(String host) {
        ftp.setHostname(host);
        return this;
    }
    
    public boolean getTcpNoDelay() {
        return this.ftp.getTcpNoDelay();
    }
    
    public FtpTransfer setTcpNoDelay(boolean value) {
        this.ftp.setTcpNoDelay(value);
        return this;
    }
    
    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setLocalDir(File)
     */
    public FileTransfer setLocalDir(File dir) {
        ftp.setLocalDir(dir);
        return this;
    }

    /**
     * Sets the indicator value which will be used by methods that invokes
     * getFileSize.
     *
     * @param error Error indicator value
     */
    public FtpTransfer setErrorOnSizeCommand(boolean error) {
        this.ftp.setErrorOnSizeCommand(error);
        return this;
    }

    /**
     * Gets the error on size command indicator.
     *
     * @return <code>true</code> then method which invokes getFileSize will throw
     *         an exception when it caused an error, <code>false</code> the error will be ignored.
     */
    public boolean getErrorOnSizeCommand() {
        return this.ftp.getErrorOnSizeCommand();
    }

    /**
     * Sets a NAT address which will be used after PASV command.
     *
     * @param ipAddress The NAT IP address.
     *
     * @throws FtpException If an error occurs.
     */
    public FtpTransfer setNATAddress(String ipAddress)
            throws FileTransferException {
        try {
            this.ftp.setNATAddress(ipAddress);
            return this;
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setPassive(boolean)
     */
    public FileTransfer setPassive(boolean passive) {
        ftp.setPassive(passive);
        return this;
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setPassword(String)
     */
    public FileTransfer setPassword(String password) {
        ftp.setPassword(password);
        return this;
    }

    /**
     * Sets the proxy authentication username and password to use with
     * proxy server.
     * To clear these settings invoke the #clearProxySettings method.
     *
     * @param proxyUsername the proxy username
     * @param proxyPassword the proxy password
     *
     * @see #clearProxySettings
     */
    public FileTransfer setProxyAuthentication(String proxyUsername, String proxyPassword) {
        this.ftp.setProxyAuthentication(proxyUsername, proxyPassword);
        return this;
    }

    /**
     * Sets the proxy hostname and port for this connection.
     * To clear these settings invoke the #clearProxySettings method.
     *
     * @param proxyHostname the hostname or ip address of the proxy server
     * @param proxyPort     the port of the proxy server to connect to
     *
     * @see #clearProxySettings
     */
    public FileTransfer setProxyHost(String proxyHostname, int proxyPort) {
        this.ftp.setProxyHost(proxyHostname, proxyPort);
        return this;
    }

    /**
     * Sets the proxy type will be used for this connection.
     *
     * @param proxyType The proxy type. Values allowed: HTTP, SOCKS5
     */
    public FileTransfer setProxyType(String proxyType) {
        this.ftp.setProxyType(proxyType);
        return this;
    }

    /**
     * Clears proxy server values.
     */
    public void clearProxySettings() {
        this.ftp.clearProxySettings();
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setTimeout(long)
     */
    public FileTransfer setTimeout(long timeout) {
        ftp.setTimeout((int) timeout);
        return this;
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setUsername(String)
     */
    public FileTransfer setUsername(String username) {
        ftp.setUsername(username);
        return this;
    }

    /**
     * Sets use EPSV indicator. When it is true then will be sent EPSV.
     *
     * @param useEPSV The use EPSV value
     */
    public FtpTransfer setUseEPSV(boolean useEPSV) {
        ftp.setUseEPSV(useEPSV);
        return this;
    }

    /**
     * Set auto detect Ipv6.
     *
     * @param autoDetect
     */
    public FtpTransfer setAutoDetectIpv6(boolean autoDetect) {
        ftp.setAutoDetectIpv6(autoDetect);
        return this;
    }

    /**
     * Sets use EPRT indicator.
     *
     * @param useEPRT
     */
    public FtpTransfer setUseEPRT(boolean useEPRT) {
        ftp.setUseEPRT(useEPRT);
        return this;
    }

    /**
     * Sets the use extended directory listing field.
     *
     * @param useExtendedDirectoryListing The use extended directory listing indicator.
     */
    public void setUseExtendedDirectoryListing(boolean useExtendedDirectoryListing) {
        this.useExtendedDirectoryListing = useExtendedDirectoryListing;
    }

    /**
     * Gets use EPSV value.
     *
     * @return <code>true</code> the pasive mode will be sent EPSV command
     */
    public boolean getUseEPSV() {
        return this.ftp.getUseEPSV();
    }

    /**
     * Sets encoding set used when issuing commands and transferring files. The
     * default encoding is UTF8
     *
     * @param encoding the character encoding to be used
     */
    public FileTransfer setWireEncoding(String encoding) {
        this.ftp.setWireEncoding(encoding);
        return this;
    }

    /**
     * Sets the connectBefore property value
     *
     * @param before It indicates if the connection will be established before send
     *               the command in passive mode
     */
    public FtpTransfer setConnectBeforeCommand(boolean before) {
        this.ftp.setConnectBeforeCommand(before);
        return this;
    }

    /**
     * Gets encoding set used when issuing commands and transferring files
     *
     * @return Returns the encoding is being used
     */
    public String getWireEncoding() {
        return this.ftp.getWireEncoding();
    }

    /**
     * Returns remote file checksum.
     *
     * @param remoteFile remote file
     *
     * @return remote file checksum.
     *
     * @throws FtpException if an error occurs
     */
    public String getRemoteFileChecksum(String remoteFile)
            throws FtpException {
        return this.ftp.getRemoteFileChecksum(remoteFile);
    }

    /**
     * Returns local checksum.
     *
     * @param localFile File which will be calculate the checksum
     *
     * @return checksum Compute value
     *
     * @throws FtpException if an error occurs
     */
    public String getLocalChecksum(File localFile)
            throws FtpException {
        return this.ftp.getLocalChecksum(localFile);
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(byte[], String)
     */
    public void upload(byte[] data, String name)
            throws FileTransferException {
        try {
            ftp.upload(data, name);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(byte[], String, boolean)
     */
    public void upload(byte[] data, String name, boolean append)
            throws FileTransferException {
        try {
            ftp.upload(data, name, append);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(File)
     */
    public void upload(File file)
            throws FileTransferException {
        String remoteDir = null;

        try {
            remoteDir = ftp.getDir(); //in case we need it for error event
            ftp.upload(file);
        } catch (FtpException e) {
            if (!ftp.interrupted()) {
                raiseErrorEvent(new FileTransferErrorEvent(this,
                                                           file.getName(),
                                                           remoteDir,
                                                           file.getPath(),
                                                           FileTransferProgressEvent.UPLOAD));
            }

            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(File, boolean)
     */
    public void upload(File file, boolean append)
            throws FileTransferException {
        try {
            ftp.upload(file, append);
            safeCheckChecksum(file, file.getName());
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * Uploads a file to server with the extension specified by the argument.
     * After the file has been uploaded then the file name is renamed to the
     * original file name.
     *
     * @param extension The extension file will be used to upload the file.
     * @param file      The file will be uploaded
     *
     * @throws FileTransferException
     */
    public void upload(String extension, File file)
            throws FileTransferException {
        String remoteDir = null;

        try {
            remoteDir = ftp.getDir(); //in case we need it for error event
            ftp.upload(extension, file);
        } catch (FtpException e) {
            if (!ftp.interrupted()) {
                raiseErrorEvent(new FileTransferErrorEvent(this,
                                                           file.getName(),
                                                           remoteDir,
                                                           file.getPath(),
                                                           FileTransferProgressEvent.UPLOAD));
            }

            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(File, String)
     */
    public void upload(File file, String remoteName)
            throws FileTransferException {
        try {
            ftp.upload(file, remoteName);
            safeCheckChecksum(file, remoteName);
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(File, String, boolean)
     */
    public void upload(File file, String remoteName, boolean append)
            throws FileTransferException {
        try {
            ftp.upload(file, remoteName, append);
            safeCheckChecksum(file, remoteName);
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(String)
     */
    public void upload(String name)
            throws FileTransferException {
        try {
            ftp.upload(name);
            safeCheckChecksum(new File(getLocalDir(), name), name);
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(String, boolean)
     */
    public void upload(String name, boolean append)
            throws FileTransferException {
        try {
            ftp.upload(name, append);
            safeCheckChecksum(new File(getLocalDir(), name), name);
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(String, String)
     */
    public void upload(String name, String remoteName)
            throws FileTransferException {
        try {
            ftp.upload(name, remoteName);
            safeCheckChecksum(new File(getLocalDir(), name), remoteName);
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#upload(String, String, boolean)
     */
    public void upload(String name, String remoteName, boolean append)
            throws FileTransferException {
        try {
            ftp.upload(name, remoteName, append);
            safeCheckChecksum(new File(getLocalDir(), name), remoteName);
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#uploadUnique(String)
     */
    public String uploadUnique(String fileName)
            throws FileTransferException {
        try {
            return this.ftp.uploadUnique(fileName);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#uploadUnique(File)
     */
    public String uploadUnique(File file)
            throws FileTransferException {
        try {
            return this.ftp.uploadUnique(file);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#uploadUnique(InputStream, String)
     */
    public String uploadUnique(InputStream in, String remoteFilePrefix)
            throws FileTransferException {
        try {
            return this.ftp.uploadUnique(in, remoteFilePrefix);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#uploadDir(File)
     */
    public void uploadDir(File localDir)
            throws FileTransferException {
        try {
            ftp.uploadDir(localDir);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * Uploads local directory and contents recursively to server.
     * Uploads are stored relative to current remote directory.
     * Files are uploaded with the extension specified in the argument extension but
     * when the upload has finished the remote file name is renamed to the original file name.
     *
     * @param localDir  the directory to upload
     * @param extension The extension file name used to upload each file
     *
     * @throws FileTransferException if an I/O or protocol error occurs.
     */
    public void uploadDir(File localDir, String extension)
            throws FileTransferException {
        try {
            ftp.uploadDir(localDir, extension);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * Uploads local directory and contents.
     * Uploads are stored relative to current remote directory.
     *
     * @param localDir        the directory to upload
     * @param retryLimit      Retry number when an error occurs
     * @param performChecksum Checksum perform indicator
     * @param extension       The extension which will be used to upload the files
     *
     * @throws FtpException if an I/O or protocol error occurs.
     */
    public void uploadDir(File localDir, int retryLimit,
                          boolean performChecksum, String extension)
            throws FileTransferException {
        this.uploadDir(localDir, retryLimit, 0, performChecksum, extension);
    }


    /**
     * Uploads local directory and contents.
     * Uploads are stored relative to current remote directory.
     *
     * @param localDir        the directory to upload
     * @param retryLimit      Retry number when an error occurs
     * @param retryInterval   the retry interval in seconds
     * @param performChecksum Checksum perform indicator
     * @param extension       The extension which will be used to upload the files
     *
     * @throws FtpException if an I/O or protocol error occurs.
     */
    public void uploadDir(File localDir, int retryLimit, int retryInterval,
                          boolean performChecksum, String extension)
            throws FileTransferException {
        try {
            ftp.uploadDir(localDir, retryLimit, retryInterval, performChecksum, extension);

        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#addFileTransferListener(FileTransferListener)
     */
    public void addFileTransferListener(FileTransferListener listener) {
        Parameters.assertNotNull(listener);
        this.listeners.add(listener);
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#removeFileTransferListener(FileTransferListener)
     */
    public void removeFileTransferListener(FileTransferListener listener) {
        this.listeners.remove(listener);
    }

    /**
     * See FileTransfer interface for method description.
     */
    public InputStream getInputStream(String remoteFile, long pos)
            throws FileTransferException {
        try {
            return this.ftp.getInputStream(remoteFile, pos);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @return underlying file transfer implementation
     */
    public Object getImplementation() {
        return this.ftp;
    }

    /**
     * See FileTransfer interface for method description.
     */
    public OutputStream getOutputStream(String fileName, long off)
            throws FileTransferException {
        try {
            return this.ftp.getOutputStream(fileName, off, false);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    public FileTransfer copy() {
        FtpTransfer transfer = new FtpTransfer(this.ftp.getHostname(),
                                               this.ftp.getPort(),
                                               this.ftp.getUsername(),
                                               this.ftp.getPassword(),
                                               this.ftp.getLocalDir(),
                                               this.listeners);
        transfer.setDebugStream(getDebugStream());
        transfer.setDebug(getDebug());

        return transfer;
    }

    /**
     * Sets block size for use in transferring files.
     *
     * @param blocksize the blocksize in bytes
     */
    public FileTransfer setBlockTransferSize(int blocksize) {
        this.ftp.setBlockTransferSize(blocksize);
        return this;
    }

    /**
     * Gets blocksize for use in transferring files.
     *
     * @return blocksize in bytes
     */
    public int getBlockTransferSize() {
        return this.ftp.getBlockTransferSize();
    }

    /**
     * Sets whether the data will be compressed during the transfering.
     *
     * @param compression When it is true the data will be compressed before it is
     *                    sent between the client and the server.
     */
    public FtpTransfer setCompression(boolean compression) {
        this.ftp.setCompression(compression);
        return this;
    }

    /**
     * See FileTransfer interface for method description.
     *
     * @see FileTransfer#setPort(int)
     */
    public FileTransfer setPort(int port) {
        this.ftp.setPort(port);
        return this;
    }

    public boolean isFeatureSupported(String command)
            throws FileTransferException {
        try {
            return ftp.isFeatureSupported(command);
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    public void checksum(File localFile, String remoteFile)
            throws FileTransferException {
        try {
            this.ftp.checksum(localFile, remoteFile);
        } catch (Exception e) {
            throw wrap(e);
        }
    }

    public void abortDownloadThreads() {
        this.ftp.abortDownloadThreads();
    }

    public void abortDownloadThread(String remoteFile) {
        this.ftp.abortDownloadThread(remoteFile);
    }

    public void abortUploadThread(String remoteFile) {
        this.ftp.abortUploadThread(remoteFile);
    }

    public void abortUploadThreads() {
        this.ftp.abortUploadThreads();
    }

    public void downloadDir(String remoteDir, int retryLimit,
                            boolean performChecksum, int threadCount)
            throws FileTransferException {

        try {
            this.ftp.downloadDir(remoteDir, retryLimit, performChecksum, threadCount);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    public Vector<FileTransferRemoteFile> getRemoteFileList(String remoteDirectory)
            throws FileTransferException {
        return new Vector<>(new RecursiveFileListOperation(remoteDirectory).applyTo(this).files());
    }

    public int getRecursiveDirectoryFileCount(String directory) {
        return this.ftp.getRecursiveDirectoryFileCount(directory);
    }

    public long getRecursiveDirectorySize(String directory) {
        return this.ftp.getRecursiveDirectorySize(directory);
    }

    public void uploadDir(File localDir, int retryLimit,
                          boolean performChecksum, String extension, int threadCount)
            throws FileTransferException {
        try {
            this.ftp.uploadDir(localDir, retryLimit, performChecksum, extension, threadCount);
        } catch (FtpException e) {
            throw wrap(e);
        }
    }

    public void resumeUpload(File localFile, String remoteFile, long localFileOffset)
            throws FileTransferException {
        try {
            this.ftp.resumeUpload(localFile, remoteFile, localFileOffset);
            safeCheckChecksum(localFile, remoteFile);
            this.eventConverter.raiseUploadEvent();
        } catch (Exception e) {
//            if (!ftp.interrupted()) {
//                try {
//                    raiseErrorEvent(new FileTransferErrorEvent(this,
//                                                               localFile.getName(),
//                                                               this.ftp.getDir(),
//                                                               localFile.getPath(),
//                                                               FileTransferProgressEvent.UPLOAD));
//                } catch (Exception x) {
//                    // todo: ignore?
//                }
//            }

            throw wrap(e);
        }
    }

    @Override
    public void resumeDownload(OutputStream out, String remoteFile, long remoteFileOffset)
            throws FileTransferException {
        // todo: implement
    }

    @Override
    public void upload(InputStream in, long length, String remoteFile, boolean shouldAppend)
            throws FileTransferException {
        // todo: implement
    }

    @Override
    public void resumeUpload(InputStream in, long length, String remoteFile, long remoteFileOffset)
            throws FileTransferException {
        // todo: implement
    }

    protected void raiseEvent(FileTransferEvent event) {
        for (FileTransferListener listener : this.listeners) {
            event.accept(listener);
        }
    }

    protected void raiseErrorEvent(FileTransferErrorEvent event) {
        for (FileTransferListener listener : this.listeners) {
            if (listener instanceof FileTransferErrorListener) {
                ((FileTransferErrorListener) listener).error(event);
            }
        }
    }

    protected void safeCheckChecksum(File localFile, String remoteFile)
            throws Exception {
        if (!this.checksumVerificationRequired) {
            return;
        }

        try {
            boolean sameChecksum = sameChecksum(localFile, remoteFile);

            if (!sameChecksum) {
                throw new Exception(String.format("File checksum mismatch for local file '%s' and remote file '%s'.", localFile, remoteFile));
            }
        } catch (FileTransferException e) {
            // todo: log
        }
    }

    protected class FtpEventConverter
            implements FtpErrorListener {

        private final AtomicReference<FtpDownloadEvent> downloadEvent;
        private final AtomicReference<FtpUploadEvent> uploadEvent;

        protected FtpEventConverter() {
            this.downloadEvent = new AtomicReference<>();
            this.uploadEvent = new AtomicReference<>();
        }

        /**
         * {@inheritDoc}
         */
        public void changeDir(FtpChangeDirEvent event) {
            raiseEvent(new FileTransferChangeDirEvent(FtpTransfer.this, event.getDirectory()));
        }

        /**
         * {@inheritDoc}
         */
        public void deleteFile(FtpDeleteFileEvent event) {
            raiseEvent(new FileTransferDeleteFileEvent(FtpTransfer.this, event.getFile(), event.getPath()));
        }

        /**
         * {@inheritDoc}
         */
        public void deleteDir(FtpDeleteDirEvent event) {
            raiseEvent(new FileTransferDeleteDirEvent(FtpTransfer.this, event.getDirectory(), event.getPath()));
        }

        /**
         * {@inheritDoc}
         */
        public void renameFile(FtpRenameFileEvent event) {
            raiseEvent(new FileTransferRenameFileEvent(FtpTransfer.this,
                                                       event.getOldName(),
                                                       event.getNewName(),
                                                       event.getPath()));
        }

        /**
         * {@inheritDoc}
         */
        public void createDir(FtpCreateDirEvent event) {
            raiseEvent(new FileTransferCreateDirEvent(FtpTransfer.this, event.getDirectory(), event.getPath()));
        }

        /**
         * {@inheritDoc}
         */
        public void commandSent(FtpCommandEvent event) {
            raiseEvent(new FileTransferCommandEvent(FtpTransfer.this, event.getCommand()));
        }

        /**
         * {@inheritDoc}
         */
        public void connected(FtpConnectedEvent event) {
            raiseEvent(new FileTransferConnectedEvent(FtpTransfer.this, event.getHostname()));
        }

        /**
         * {@inheritDoc}
         */
        public void connectionLost(FtpConnectionLostEvent event) {
            // empty
        }

        /**
         * {@inheritDoc}
         */
        public void disconnected(FtpDisconnectedEvent event) {
            raiseEvent(new FileTransferDisconnectedEvent(FtpTransfer.this, event.getHostname()));
        }

        /**
         * {@inheritDoc}
         */
        public void download(FtpDownloadEvent event) {
            this.downloadEvent.set(event);
        }

        /**
         * {@inheritDoc}
         */
        public void listing(FtpListingEvent event) {
            // empty
        }

        /**
         * {@inheritDoc}
         */
        public void progress(FtpProgressEvent event) {
            raiseEvent(new FileTransferProgressEvent(FtpTransfer.this,
                                                     event.getFilename(),
                                                     event.getAbsolutePath(),
                                                     event.getLocalFilePath(),
                                                     event.getMode(),
                                                     event.getBytes(),
                                                     event.getReadBytes(),
                                                     event.getTotalBytes()));
        }

        /**
         * {@inheritDoc}
         */
        public void responseReceived(FtpResponseEvent event) {
            raiseEvent(new FileTransferResponseEvent(FtpTransfer.this, event.getResponse()));
        }

        /**
         * {@inheritDoc}
         */
        public void upload(FtpUploadEvent event) {
            this.uploadEvent.set(event);
        }

        public void error(FtpErrorEvent event) {
            raiseErrorEvent(new FileTransferErrorEvent(FtpTransfer.this,
                                                       event.getFilename(),
                                                       event.getPath(),
                                                       event.getLocalPath(),
                                                       event.getMode() == FtpProgressEvent.UPLOAD ? FileTransferProgressEvent.UPLOAD : FileTransferProgressEvent.DOWNLOAD));
        }

        public void raiseDownloadEvent() {
            FtpDownloadEvent event = this.downloadEvent.getAndSet(null);

            if (event != null) {
                raiseEvent(new FileTransferDownloadEvent(FtpTransfer.this,
                                                         event.getFilename(),
                                                         event.getPath(),
                                                         event.getLocalPath(),
                                                         event.getSize(),
                                                         event.getTime(),
                                                         false));
            }
        }

        public void raiseUploadEvent() {
            FtpUploadEvent event = this.uploadEvent.getAndSet(null);

            if (event != null) {
                raiseEvent(new FileTransferUploadEvent(FtpTransfer.this,
                                                       event.getFilename(),
                                                       event.getPath(),
                                                       event.getLocalPath(),
                                                       event.getSize(),
                                                       event.getTime()));
            }
        }
    }


}
