Upload no ftp trava ao final do upload

Bom dia

Eu to fazendo upload de arquivos para o ftp em java normalmente, porém quando o arquivo é muito grande ele trava no final, e não libera o processo. Fica travado no 100% e não vai, não mostra nenhum log de erro, nada.
Se eu for olhar no ftp, o arquivo já transferiu 100%. Quando o arquivo é menor, o problema não ocorre. Eu testei com dois ftp’s diferentes e ocorreu o mesmo problema.

Abaixo segue o fonte que conecta no FTP

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.rmi.ConnectException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.io.CopyStreamAdapter;

import br.com.backupsafe.generic.config.Config;
import br.com.backupsafe.generic.debug.Debug;
import br.com.backupsafe.generic.exception.FTPException;
import br.com.backupsafe.generic.exception.NoXmlConfigException;
import br.com.backupsafe.generic.framework.conexaobd.sqllite.ConexaoSqlLite;
import br.com.backupsafe.generic.framework.utils.DateUtils;

public class FtpUtils  implements IFtp{


	private static FTPClient ftp;
	private static FtpUtils ftpUtils;

	private String host;
	private Integer port;
	private String username;
	private String password;
	private int percent = 0;

	private String dir;

	public boolean iniciaFtp() throws FTPException {
		try{

			ftp = new FTPClient();

			ftp.connect(this.host, this.port);

			if(ftp.login(this.username, this.password) == false){					
				throw new FTPException("Nao foi possivel conectar! \n" +ftp.getReplyString());						
			}else{
				Debug.imprimir(DateUtils.formataDataLog(new Date())	+" - Conectado ao FTP com sucesso!");
			};

			if(dir!= null && !dir.isEmpty()){
				ftp.makeDirectory(dir);
				ftp.changeWorkingDirectory(dir);
			}

			ftp.enterLocalPassiveMode();
			//			ftp.setFileTransferMode(FTP.BLOCK_TRANSFER_MODE);
			ftp.setFileType(FTP.BINARY_FILE_TYPE);
			ftp.setBufferSize(1048576);
			
			
			return true;
		}catch(UnknownHostException e){
			throw new FTPException("Nao foi possivel conectar ao FTP (Host invalido)", e);
		}catch(ConnectException e){
			throw new FTPException("Nao foi possivel conectar ao FTP (Conexao interrompida)", e);
		} catch (IOException e) {
			throw new FTPException(e);
		}
	}

	public static FtpUtils getInstance() {
		if (ftpUtils == null) {
			ftpUtils = new FtpUtils();
		}
		return ftpUtils;
	}


	public boolean uploadArquivo(final File file, String nome,final String cnpj) throws FTPException {

		try {

			// 1 - StoreFile 2 - StoreFileStream
			Integer metodo = 1;
			boolean completed = false;

			boolean ok = this.iniciaFtp();
			
			
			CopyStreamAdapter streamListener = new CopyStreamAdapter() {

		@Override
				public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) {
					int perc = (int) (totalBytesTransferred * 100 / file.length());					
						setPercent(perc);
						//ConexaoWs.getInstance().notificaExecucao(cnpj, true, getPercent());
						Debug.printUpload("Percentual de upload: "+ getPercent()+" %.");					
				}
			};

			ftp.setCopyStreamListener(streamListener);

			if(ok){
				
				ftp.makeDirectory(cnpj);
				ftp.changeWorkingDirectory(cnpj);

				Debug.imprimir(DateUtils.formataDataLog(new Date())	+" - Fazendo upload de arquivo para o FTP ("+this.host +" "+ this.port+" "+  this.dir + ")");
				String localFileName = file.getAbsolutePath();

				FileInputStream in = new FileInputStream(localFileName);

				if(metodo == 1){
					completed = ftp.storeFile(nome, in);
					in.close();		

				}else{

					byte[] buffer = new byte[8192];
					OutputStream out = ftp.storeFileStream(nome);
					while (true) {
						int bytes = in.read(buffer);				

						if (bytes < 0)
							break;
						out.write(buffer, 0, bytes);
					}			
					in.close();
					out.close();

					completed = ftp.completePendingCommand();
				}
				Debug.imprimir(DateUtils.formataDataLog(new Date())	+" - Arquivo enviado com sucesso!");
			}
			return completed;

		}catch(IOException e){
			throw new FTPException(e);
		}finally {

			try {
				if(ftp!= null){
					ftp.logout();
					ftp.disconnect();
				}
			} catch (IOException e) {
				throw new FTPException(e);
			}
		}
	}

	public boolean enviaArquivoParaFtp(String arqZip, File arqBackupZip,String cnpj) throws NoXmlConfigException, FTPException{

		for (int i = 1; i <= Config.getInstance().getQtdTentativas(); i++) {

			Boolean ok = false;
			Debug.imprimir(DateUtils.formataDataLog(new Date())	+" - "+i+" tentativa de enviar ao FTP!");
			/*
			 *  Upload
			 */
			try{

				Debug.imprimir(DateUtils.formataDataLog(new Date())	+" - Fazendo upload do arquivo: "+arqZip+"... Tamanho: "+arqBackupZip.length());			

				ok = this.uploadArquivo(arqBackupZip, arqZip,cnpj);
				return ok;


			}catch(FTPException e){

				if (i == Config.getInstance().getQtdTentativas()){
					throw e;
				}							
			}
			if(ok){
				return ok;
			}
		}
		return false;
	}

	@Override
	public void setHost(String hostFtp) {
		this.host = hostFtp;		
	}

	@Override
	public void setPort(Integer portaFtp) {
		this.port = portaFtp;		
	}

	@Override
	public void setUsername(String userFtp) {
		this.username = userFtp;
	}

	@Override
	public void setPassword(String passwordDecrypt) {
		this.password = passwordDecrypt;

	}

	@Override
	public void setDir(String diretorioFtp) {
		this.dir = diretorioFtp;
	}

	public int getPercent() {
		return percent;
	}

	public void setPercent(int percent) {
		this.percent = percent;
	}
}

Será que não está faltando alguma coisa ao final do upload?

Obrigado

Já verificou se não está ficando bloqueado naquele while(true) ?

Eu trocaria esse código aqui:

byte[] buffer = new byte[8192];
OutputStream out = ftp.storeFileStream(nome);
while (true) {
    int bytes = in.read(buffer);				
    if (bytes < 0) {
        break;
    }
    out.write(buffer, 0, bytes);
}			
in.close();
out.close();

Por este:

OutputStream out = ftp.storeFileStream(nome);
byte[] buffer = new byte[8192];
for (int read = -1; (read = in.read(buffer)) != -1; out.write(buffer, 0, read)) {} // corpo vazio mesmo, a mágica acontece nos blocos do prório for
out.flush();
out.close();
in.close();
1 curtida

Do jeito que o código está, esse comando: ftp.completePendingCommand();, não será executado. Será que pode ser isso?

Vou fazer o teste aqui, mas ali tem dois métodos que podem fazer upload no ftp.(criei pora testar)
O método 1, que é o que estou usando, é usando storefile, e pelo que li não precisa fazer o ftp.completePendingCommand();
Já o método 2 usa StoreFileStream e aí sim precisa do ftp.completePendingCommand();, porém o método bytesTransferred não é executado!

Eu acho que no seu CopyStreamAdapter você sobrescreveu o método errado.

Veja a documentação da interface CopyStreamListener:
https://commons.apache.org/proper/commons-net/javadocs/api-3.6/org/apache/commons/net/io/CopyStreamListener.html

Ela diz o seguinte para o método que você implementou:

bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize)
This method is not part of the JavaBeans model and is used by the static methods in the org.apache.commons.io.Util class for efficiency.

Ou seja, embora o método faça parte da interface, aparentemente ele é pra uso interno da API.

Ao invés de implementar seu CopyStreamListener assim:

CopyStreamAdapter streamListener = new CopyStreamAdapter() {

    @Override
    public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) {
        int perc = (int) (totalBytesTransferred * 100 / file.length());
        setPercent(perc);
        Debug.printUpload("Percentual de upload: " + getPercent() + " %.");
    }
};

Tente implementar assim:

CopyStreamAdapter streamListener = new CopyStreamAdapter() {

    @Override
    public void bytesTransferred(CopyStreamEvent event) {
        int perc = (int) (event.getTotalBytesTransferred() * 100 / file.length());
        setPercent(perc);
        Debug.printUpload("Percentual de upload: " + getPercent() + " %.");
    }
};

Quando eu implementei da forma que você falou o método CopyStreamAdapter, ele não executa mais, ou seja, não mostra o percentual de upload.(Isso que subi um arquivo de 286 mb só)
O problema de ele travar no final continua ocorrendo, não resolveu o meu problema!

Em que momento ele trava?
Está executando em modo de depuração?
Faz um pause na execução para ver em qual método está parado.

Ele tá travando nesse comando aqui:
completed = ftp.completePendingCommand();

Alguma Idéia?

Eae cara, conseguiu resolver o problema?

Ainda não resolvi o problema!

Vou tentar issoa qui https://stackoverflow.com/questions/9706968/apache-commons-ftpclient-hanging e depois coloco aqui se funcionou.