/**
*
* Copyright (C) 2007 Enterprise Distributed Technologies Ltd
*
* www.enterprisedt.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Bug fixes, suggestions and comments should be should posted on
* http://www.enterprisedt.com/forums/index.php
*
* Change Log:
*
* $Log: FTPInputStream.java,v $
* Revision 1.3 2009-09-21 00:53:34 bruceb
* fix bug where LF were stripped when should have been left in ASCII mode
*
* Revision 1.2 2008-07-29 02:58:00 bruceb
* fix read() bug
*
* Revision 1.1 2007-12-18 07:52:06 bruceb
* 2.0 changes
*
*
*/
package com.enterprisedt.net.ftp;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import com.enterprisedt.util.debug.Logger;
/**
* Represents an input stream of bytes coming from an FTP server, permitting
* the user to download a file by reading the stream. It can only be used
* for one download, i.e. after the stream is closed it cannot be reopened.
*
* @author Bruce Blackshaw
* @version $Revision: 1.3 $
*/
public class FTPInputStream extends FileTransferInputStream {
private static Logger log = Logger.getLogger("FTPInputStream");
/**
* Line separator
*/
final private static byte[] LINE_SEPARATOR = System.getProperty("line.separator").getBytes();
/**
* Interval that we notify the monitor of progress
*/
private long monitorInterval;
/**
* The client being used to perform the transfer
*/
private FTPClient client;
/**
* The input stream from the FTP server
*/
private BufferedInputStream in;
/**
* Number of bytes downloaded
*/
private long size = 0;
/**
* Is this an ASCII transfer or not?
*/
private boolean isASCII = false;
/**
* Buffer that supplies the stream
*/
private byte [] buffer;
/**
* Current position to read from in the buffer
*/
private int bufpos = 0;
/**
* The length of the buffer
*/
private int buflen = 0;
/**
* Buffer read from the FTP server
*/
private byte[] chunk;
private byte[] prevBuf = new byte[FTPClient.FTP_LINE_SEPARATOR.length];
private int matchpos = 0;
/**
* Output stream to write to
*/
private ByteArrayOutputStream out;
/**
* Count of byte since last the progress monitor was notified.
*/
private long monitorCount = 0;
/**
* Progress monitor reference
*/
private FTPProgressMonitor monitor;
/**
* Progress monitor reference
*/
private FTPProgressMonitorEx monitorEx;
/**
* Flag to indicated we've started downloading
*/
private boolean started = false;
/**
* Constructor. A connected FTPClient instance must be supplied. This sets up the
* download
*
* @param client connected FTPClient instance
* @param remoteFile remote file
* @throws IOException
* @throws FTPException
*/
public FTPInputStream(FTPClient client, String remoteFile) throws IOException, FTPException {
this.client = client;
this.remoteFile = remoteFile;
try {
client.initGet(remoteFile);
// get an input stream to read data from ... AFTER we have
// the ok to go ahead AND AFTER we've successfully opened a
// stream for the local file
in = new BufferedInputStream(new DataInputStream(client.getInputStream()));
}
catch (IOException ex) {
client.validateTransferOnError(ex);
throw ex;
}
this.monitorInterval = client.getMonitorInterval();
this.monitor = client.getProgressMonitor();
this.chunk = new byte[client.getTransferBufferSize()];
this.out = new ByteArrayOutputStream(client.getTransferBufferSize());
this.isASCII = (client.getType().equals(FTPTransferType.ASCII));
}
/**
* The input stream uses the progress monitor currently owned by the FTP client.
* This method allows a different progress monitor to be passed in, or for the
* monitor interval to be altered.
*
* @param monitor progress monitor reference
* @param monitorInterval
*/
public void setMonitor(FTPProgressMonitorEx monitor, long monitorInterval) {
this.monitor = monitor;
this.monitorEx = monitor;
this.monitorInterval = monitorInterval;
}
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an int in the range 0 to 255. If no byte is available because
* the end of the stream has been reached, the value -1 is returned.
* This method blocks until input data is available, the end of the stream
* is detected, or an exception is thrown.
*/
public int read() throws IOException {
if (!started) {
start();
}
if (buffer == null)
return -1;
if (bufpos == buflen) {
buffer = refreshBuffer();
if (buffer == null)
return -1;
}
return 0xFF & buffer[bufpos++];
}
/**
* Reads up to len bytes of data from the input stream into an array of bytes.
* An attempt is made to read as many as len bytes, but a smaller number may
* be read, possibly zero. The number of bytes actually read is returned as an integer.
* This method blocks until input data is available, end of file is detected,
* or an exception is thrown.
*
* @param b array to read into
* @param off offset into the array to start at
* @param len the number of bytes to be read
*
* @return the number of bytes read, or -1 if the end of the stream has been reached.
*/
public int read(byte b[], int off, int len) throws IOException {
if (!started) {
start();
}
if (buffer == null || len == 0)
return -1;
if (bufpos == buflen) {
buffer = refreshBuffer();
if (buffer == null)
return -1;
}
int available = 0;
int remaining = len;
while ( (available = buflen-bufpos) < remaining ) {
System.arraycopy(buffer, bufpos, b, off, available);
remaining -= available;
off += available;
buffer = refreshBuffer();
if (buffer == null)
return len-remaining;
}
System.arraycopy(buffer, bufpos, b, off, remaining);
bufpos += remaining;
return len;
}
/**
* Start the transfer
*
* @throws IOException
*/
private void start() throws IOException {
if (monitorEx != null) {
monitorEx.transferStarted(TransferDirection.DOWNLOAD, remoteFile);
}
buffer = refreshBuffer();
started = true;
}
/**
* Refresh the buffer by reading the internal FTP input stream
* directly from the server
*
* @return byte array of bytes read, or null if the end of stream is reached
* @throws IOException
*/
private byte[] refreshBuffer() throws IOException {
bufpos = 0;
if (client.isTransferCancelled())
return null;
int count = client.readChunk(in, chunk, chunk.length);
if (count < 0) {
if (isASCII && matchpos > 0) {
size += matchpos;
buflen = matchpos;
monitorCount += matchpos;
byte[] tmp = new byte[matchpos];
System.arraycopy(tmp, 0, prevBuf, 0, matchpos);
matchpos = 0;
return tmp;
}
return null;
}
try {
if (!isASCII) {
size += count;
monitorCount += count;
buflen = count;
return chunk;
}
else {
// transform CRLF
out.reset();
for (int i = 0; i < count; i++) {
if (chunk[i] == FTPClient.FTP_LINE_SEPARATOR[matchpos]) {
prevBuf[matchpos] = chunk[i];
matchpos++;
if (matchpos == FTPClient.FTP_LINE_SEPARATOR.length) {
out.write(LINE_SEPARATOR);
size += LINE_SEPARATOR.length;
monitorCount += LINE_SEPARATOR.length;
matchpos = 0;
}
}
else { // no match
// write out existing matches
if (matchpos > 0) {
out.write(prevBuf, 0, matchpos);
size += matchpos;
monitorCount += matchpos;
}
out.write(chunk[i]);
size++;
monitorCount++;
matchpos = 0;
}
}
byte[] result = out.toByteArray();
buflen = result.length;
return result;
}
}
finally {
if (monitor != null && monitorCount > monitorInterval) {
monitor.bytesTransferred(size);
monitorCount = 0;
}
}
}
/**
* Closes this input stream and releases any system resources associated
* with the stream. This <b>must</b> be called before any other operations
* are initiated on the FTPClient.
*
* @exception IOException if an I/O error occurs.
*/
public void close() throws IOException {
if (!closed) {
closed = true;
client.forceResumeOff();
// close streams
client.closeDataSocket(in);
if (monitor != null)
monitor.bytesTransferred(size);
// log bytes transferred
log.debug("Transferred " + size + " bytes from remote host");
try {
client.validateTransfer();
}
catch (FTPException ex) {
throw new IOException(ex.getMessage());
}
if (monitorEx != null)
monitorEx.transferComplete(TransferDirection.DOWNLOAD, remoteFile);
}
}
}