net.seedboxer.common.ftp.FtpUploaderCommons.java Source code

Java tutorial

Introduction

Here is the source code for net.seedboxer.common.ftp.FtpUploaderCommons.java

Source

/*******************************************************************************
 * FtpUploaderCommons.java
 * 
 * Copyright (c) 2012 Team SeedBoxer.
 * 
 * This file is part of SeedBoxer FTPCommon.
 * 
 * SeedBoxer FTPCommon is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * SeedBoxer FTPCommon is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with SeedBoxer FTPCommon.  If not, see <http ://www.gnu.org/licenses/>.
 ******************************************************************************/
package net.seedboxer.common.ftp;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import net.seedboxer.common.ftp.exception.AbortedTransferException;
import net.seedboxer.common.ftp.exception.FtpConnectionException;
import net.seedboxer.common.ftp.exception.FtpException;
import net.seedboxer.common.ftp.exception.FtpInvalidLoginException;
import net.seedboxer.common.ftp.exception.FtpListFilesException;
import net.seedboxer.common.ftp.exception.FtpTransferException;
import net.seedboxer.common.ftp.filter.DirectoryFileFilter;
import net.seedboxer.common.ftp.filter.NormalFileFilter;

import org.apache.commons.net.MalformedServerReplyException;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPListParseEngine;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
import org.apache.commons.net.io.CopyStreamAdapter;
import org.apache.commons.net.io.CopyStreamException;
import org.apache.commons.net.io.CopyStreamListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of {@link FtpUploader} with Apache Commons NET Library
 * 
 * @author Jorge Davison (jdavisonc)
 *
 */
public class FtpUploaderCommons implements FtpUploader {

    private final static Logger LOGGER = LoggerFactory.getLogger(FtpUploaderCommons.class);

    private final static int TIMEOUT = 2 * 60 * 1000;

    private final static FileFilter directoryFileFilter = new DirectoryFileFilter();

    private final static FileFilter normalFileFilter = new NormalFileFilter();

    private String server;

    private String username;

    private String password;

    private String remotePath;

    private FTPClient ftpClient;

    private String type;

    private final AtomicBoolean aborted = new AtomicBoolean(false);

    @Override
    public void configure(String server, String username, String password, String remotePath, boolean ssl)
            throws Exception {
        if (ssl) {
            this.ftpClient = new FTPSClient("SSL", true);
        } else {
            this.ftpClient = new FTPClient();
        }
        this.server = server;
        this.username = username;
        this.password = password;
        this.remotePath = remotePath;
    }

    @Override
    public void connect() throws FtpException {
        try {

            ftpClient.setDataTimeout(TIMEOUT);
            ftpClient.setConnectTimeout(TIMEOUT);
            ftpClient.setDefaultTimeout(TIMEOUT);

            ftpClient.connect(server);
            ftpClient.enterLocalPassiveMode();
            ftpClient.login(username, password);

            int reply = ftpClient.getReplyCode();
            if (FTPReply.isPositiveCompletion(reply)) {

                type = ftpClient.getSystemType().toUpperCase();

                if (remotePath != null) {
                    LOGGER.debug("Moving to directory {}", remotePath);
                    ftpClient.changeWorkingDirectory(remotePath);
                }
                // Set ftp client configurations
                ftpClient.setSoTimeout(TIMEOUT);
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            } else {
                ftpClient.disconnect();
                throw new FtpInvalidLoginException();
            }
        } catch (IOException e) {
            throw new FtpConnectionException(e);
        }
    }

    @Override
    public void disconnect() throws FtpException {
        try {
            ftpClient.logout();
            if (ftpClient.isConnected()) {
                ftpClient.disconnect();
            }
        } catch (IOException e) {
            /*ignore */ }
    }

    @Override
    public void abort() throws FtpException {
        aborted.set(true);
    }

    @Override
    public void upload(File fileToUpload, FtpUploaderListener listener) throws FtpException {
        try {
            Map<String, Long> filesInServer = listFiles();
            if (fileToUpload.isDirectory()) {
                uploadDirectory(fileToUpload, filesInServer, listener);
            } else {
                uploadFile(fileToUpload, filesInServer, listener);
            }
        } catch (FtpException e) {
            throw e;
        } catch (IOException e) {
            throw new FtpTransferException(e);
        }
    }

    private void uploadDirectory(File directoryToUpload, Map<String, Long> filesInServer,
            FtpUploaderListener listener) throws IOException {

        checkAborted();

        // Create the directory if it was not created
        boolean exist = filesInServer.containsKey(directoryToUpload.getName());
        if (!exist) {
            try {
                LOGGER.debug("" + ftpClient.makeDirectory(directoryToUpload.getName()));
                LOGGER.debug("Directory created! {}", directoryToUpload.getName());
            } catch (Exception e) {
                LOGGER.debug("Could not create directory, may be it already exist");
            }
        }
        // Change to the directory
        LOGGER.debug("Moving to directory {}", directoryToUpload.getName());
        ftpClient.changeWorkingDirectory(directoryToUpload.getName());

        // Check the files inside the directory
        Map<String, Long> filesInServerDirectory;
        if (!exist) {
            filesInServerDirectory = Collections.emptyMap();
        } else {
            filesInServerDirectory = listFiles();
        }

        // Upload all directories first
        for (File childFile : directoryToUpload.listFiles(directoryFileFilter)) {
            uploadDirectory(childFile, filesInServerDirectory, listener);
        }
        // Upload all files
        for (File childFile : directoryToUpload.listFiles(normalFileFilter)) {
            uploadFile(childFile, filesInServerDirectory, listener);
        }

        // Back to the original directory
        LOGGER.debug("Moving to directory up");
        ftpClient.changeToParentDirectory();
    }

    private void uploadFile(File fileToUpload, Map<String, Long> filesListInServer, FtpUploaderListener listener)
            throws IOException {

        String fileName = fileToUpload.getName();
        Long size = filesListInServer.get(fileName);
        if (size != null && size != 0L) {
            // Tell listener that already exist and transfer (part) of the file
            listener.bytesTransferred(size);
            if (size == fileToUpload.length()) {
                LOGGER.debug("File already exists {}", fileName);
            } else {
                LOGGER.trace("Resuming file {} from {} MB", fileName, (size / (1024 * 1024)));
                // Set the offset
                ftpClient.setRestartOffset(size);
                // Create stream and skip first SIZE bytes
                InputStream ins = new FileInputStream(fileToUpload);
                ins.skip(size);
                // Upload file
                storeFile(fileName, ins, fileToUpload.length() - size, listener);

                LOGGER.debug("File {} successfully uploaded", fileName);
            }
        } else {
            storeFile(fileName, new FileInputStream(fileToUpload), fileToUpload.length(), listener);
            LOGGER.debug("File {} successfully uploaded", fileName);
        }
    }

    private void storeFile(String fileName, InputStream ins, Long size, final FtpUploaderListener listener)
            throws IOException {

        checkAborted();

        OutputStream outs = ftpClient.storeFileStream(fileName);

        CopyStreamAdapter adapter = new CopyStreamAdapter() {
            @Override
            public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) {
                if (listener != null) {
                    listener.bytesTransferred(bytesTransferred);
                }
            }
        };

        try {
            copyStream(ins, outs, ftpClient.getBufferSize(), size, adapter);
        } finally {
            outs.close();
            ins.close();
        }

        try {
            if (!ftpClient.completePendingCommand()) {
                throw new FtpTransferException();
            }
        } catch (MalformedServerReplyException e) {
            if (!e.getMessage().toLowerCase().contains("ok")
                    && !e.getMessage().toLowerCase().contains("complete")) {
                throw e;
            }
        }
    }

    private void checkAborted() {
        if (aborted.get()) {
            throw new AbortedTransferException();
        }
    }

    private void copyStream(InputStream source, OutputStream dest, int bufferSize, long streamSize,
            CopyStreamListener listener) throws CopyStreamException {
        int bytes;
        long total;
        byte[] buffer;

        buffer = new byte[bufferSize];
        total = 0;

        try {
            while ((bytes = source.read(buffer)) != -1 || !aborted.get()) {
                if (bytes == 0) {
                    bytes = source.read();
                    if (bytes < 0) {
                        break;
                    }
                    dest.write(bytes);
                    dest.flush();
                    ++total;
                    if (listener != null) {
                        listener.bytesTransferred(total, 1, streamSize);
                    }
                    continue;
                }

                dest.write(buffer, 0, bytes);
                dest.flush();
                total += bytes;
                if (listener != null) {
                    listener.bytesTransferred(total, bytes, streamSize);
                }
            }
        } catch (IOException e) {
            throw new CopyStreamException("IOException caught while copying.", total, e);
        }
    }

    /**
     * List files inside the current folder.
     * 
     * @return List with files names and size
     * @throws IOException
     */
    private Map<String, Long> listFiles() throws FtpException {
        int attempts = 0;
        Map<String, Long> files = new LinkedHashMap<String, Long>();
        while (true) {
            try {
                FTPListParseEngine engine = null;
                if (type.startsWith("UNIX")) {
                    engine = ftpClient.initiateListParsing(FTPClientConfig.SYST_UNIX, null);
                } else {
                    engine = ftpClient.initiateListParsing();
                }

                FTPFile[] list = engine.getFiles();
                if (list != null) {
                    for (FTPFile ftpFile : list) {
                        files.put(ftpFile.getName(), ftpFile.getSize());
                    }
                }
                return files;
            } catch (Exception e) {
                attempts++;
                if (attempts > 3) {
                    throw new FtpListFilesException(e);
                } else {
                    LOGGER.trace("First attempt to get list of files FAILED! attempt={}", attempts);
                }
            }
        }
    }

}