001: /**
002: *
003: * Copyright (C) 2007 Enterprise Distributed Technologies Ltd
004: *
005: * www.enterprisedt.com
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Bug fixes, suggestions and comments should be should posted on
022: * http://www.enterprisedt.com/forums/index.php
023: *
024: * Change Log:
025: *
026: * $Log: FTPInputStream.java,v $
027: * Revision 1.1 2007-12-18 07:52:06 bruceb
028: * 2.0 changes
029: *
030: *
031: */package com.enterprisedt.net.ftp;
032:
033: import java.io.BufferedInputStream;
034: import java.io.ByteArrayOutputStream;
035: import java.io.DataInputStream;
036: import java.io.IOException;
037:
038: import com.enterprisedt.util.debug.Logger;
039:
040: /**
041: * Represents an input stream of bytes coming from an FTP server, permitting
042: * the user to download a file by reading the stream. It can only be used
043: * for one download, i.e. after the stream is closed it cannot be reopened.
044: *
045: * @author Bruce Blackshaw
046: * @version $Revision: 1.1 $
047: */
048: public class FTPInputStream extends FileTransferInputStream {
049:
050: private static Logger log = Logger.getLogger("FTPInputStream");
051:
052: /**
053: * Line separator
054: */
055: final private static byte[] LINE_SEPARATOR = System.getProperty(
056: "line.separator").getBytes();
057:
058: /**
059: * Interval that we notify the monitor of progress
060: */
061: private long monitorInterval;
062:
063: /**
064: * The client being used to perform the transfer
065: */
066: private FTPClient client;
067:
068: /**
069: * The input stream from the FTP server
070: */
071: private BufferedInputStream in;
072:
073: /**
074: * Number of bytes downloaded
075: */
076: private long size = 0;
077:
078: /**
079: * Is this an ASCII transfer or not?
080: */
081: private boolean isASCII = false;
082:
083: /**
084: * Was a CR found?
085: */
086: private boolean crFound = false;
087:
088: /**
089: * Buffer that supplies the stream
090: */
091: private byte[] buffer;
092:
093: /**
094: * Current position to read from in the buffer
095: */
096: private int bufpos = 0;
097:
098: /**
099: * The length of the buffer
100: */
101: private int buflen = 0;
102:
103: /**
104: * Buffer read from the FTP server
105: */
106: private byte[] chunk;
107:
108: /**
109: * Output stream to write to
110: */
111: private ByteArrayOutputStream out;
112:
113: /**
114: * Count of byte since last the progress monitor was notified.
115: */
116: private long monitorCount = 0;
117:
118: /**
119: * Progress monitor reference
120: */
121: private FTPProgressMonitor monitor;
122:
123: /**
124: * Progress monitor reference
125: */
126: private FTPProgressMonitorEx monitorEx;
127:
128: /**
129: * Flag to indicated we've started downloading
130: */
131: private boolean started = false;
132:
133: /**
134: * Constructor. A connected FTPClient instance must be supplied. This sets up the
135: * download
136: *
137: * @param client connected FTPClient instance
138: * @param remoteFile remote file
139: * @throws IOException
140: * @throws FTPException
141: */
142: public FTPInputStream(FTPClient client, String remoteFile)
143: throws IOException, FTPException {
144: this .client = client;
145: this .remoteFile = remoteFile;
146: try {
147: client.initGet(remoteFile);
148:
149: // get an input stream to read data from ... AFTER we have
150: // the ok to go ahead AND AFTER we've successfully opened a
151: // stream for the local file
152: in = new BufferedInputStream(new DataInputStream(client
153: .getInputStream()));
154:
155: } catch (IOException ex) {
156: client.validateTransferOnError(ex);
157: throw ex;
158: }
159:
160: this .monitorInterval = client.getMonitorInterval();
161: this .monitor = client.getProgressMonitor();
162: this .chunk = new byte[client.getTransferBufferSize()];
163: this .out = new ByteArrayOutputStream(client
164: .getTransferBufferSize());
165: this .isASCII = (client.getType().equals(FTPTransferType.ASCII));
166: }
167:
168: /**
169: * The input stream uses the progress monitor currently owned by the FTP client.
170: * This method allows a different progress monitor to be passed in, or for the
171: * monitor interval to be altered.
172: *
173: * @param monitor progress monitor reference
174: * @param monitorInterval
175: */
176: public void setMonitor(FTPProgressMonitorEx monitor,
177: long monitorInterval) {
178: this .monitor = monitor;
179: this .monitorEx = monitor;
180: this .monitorInterval = monitorInterval;
181: }
182:
183: /**
184: * Reads the next byte of data from the input stream. The value byte is
185: * returned as an int in the range 0 to 255. If no byte is available because
186: * the end of the stream has been reached, the value -1 is returned.
187: * This method blocks until input data is available, the end of the stream
188: * is detected, or an exception is thrown.
189: */
190: public int read() throws IOException {
191: if (!started) {
192: start();
193: }
194: if (buffer == null)
195: return -1;
196: if (bufpos == buflen) {
197: buffer = refreshBuffer();
198: if (buffer == null)
199: return -1;
200: }
201: return buffer[bufpos++];
202: }
203:
204: /**
205: * Reads up to len bytes of data from the input stream into an array of bytes.
206: * An attempt is made to read as many as len bytes, but a smaller number may
207: * be read, possibly zero. The number of bytes actually read is returned as an integer.
208: * This method blocks until input data is available, end of file is detected,
209: * or an exception is thrown.
210: *
211: * @param b array to read into
212: * @param off offset into the array to start at
213: * @param len the number of bytes to be read
214: *
215: * @return the number of bytes read, or -1 if the end of the stream has been reached.
216: */
217: public int read(byte b[], int off, int len) throws IOException {
218: if (!started) {
219: start();
220: }
221: if (buffer == null || len == 0)
222: return -1;
223:
224: if (bufpos == buflen) {
225: buffer = refreshBuffer();
226: if (buffer == null)
227: return -1;
228: }
229:
230: int available = 0;
231: int remaining = len;
232: while ((available = buflen - bufpos) < remaining) {
233: System.arraycopy(buffer, bufpos, b, off, available);
234: remaining -= available;
235: off += available;
236: buffer = refreshBuffer();
237: if (buffer == null)
238: return len - remaining;
239: }
240: System.arraycopy(buffer, bufpos, b, off, remaining);
241: bufpos += remaining;
242: return len;
243: }
244:
245: /**
246: * Start the transfer
247: *
248: * @throws IOException
249: */
250: private void start() throws IOException {
251: if (monitorEx != null) {
252: monitorEx.transferStarted(TransferDirection.DOWNLOAD,
253: remoteFile);
254: }
255: buffer = refreshBuffer();
256: started = true;
257: }
258:
259: /**
260: * Refresh the buffer by reading the internal FTP input stream
261: * directly from the server
262: *
263: * @return byte array of bytes read, or null if the end of stream is reached
264: * @throws IOException
265: */
266: private byte[] refreshBuffer() throws IOException {
267: bufpos = 0;
268: if (client.isTransferCancelled())
269: return null;
270: int count = client.readChunk(in, chunk, chunk.length);
271: if (count < 0) {
272: if (isASCII && crFound) {
273: size++;
274: buflen = 1;
275: monitorCount++;
276: crFound = false;
277: byte[] tmp = new byte[1];
278: tmp[0] = FTPClient.CARRIAGE_RETURN;
279: return tmp;
280: }
281: return null;
282: }
283: try {
284: if (!isASCII) {
285: size += count;
286: monitorCount += count;
287: buflen = count;
288: return chunk;
289: } else {
290: // transform CRLF
291: out.reset();
292: boolean lfFound = false;
293: for (int i = 0; i < count; i++) {
294: lfFound = chunk[i] == FTPClient.LINE_FEED;
295: // if previous is a CR, write it out if current is LF, otherwise
296: // write out the previous CR
297: if (crFound) {
298: if (lfFound) {
299: out.write(LINE_SEPARATOR, 0,
300: LINE_SEPARATOR.length);
301: size += LINE_SEPARATOR.length;
302: monitorCount += LINE_SEPARATOR.length;
303: } else {
304: // not CR LF so write out previous CR
305: out.write(FTPClient.CARRIAGE_RETURN);
306: size++;
307: monitorCount++;
308: }
309: }
310:
311: // now check if current is CR
312: crFound = chunk[i] == FTPClient.CARRIAGE_RETURN;
313:
314: // if we didn't find a LF this time, write current byte out
315: // unless it is a CR - in that case save it
316: if (!lfFound && !crFound) {
317: out.write(chunk[i]);
318: size++;
319: monitorCount++;
320: }
321: }
322: byte[] result = out.toByteArray();
323: buflen = result.length;
324: return result;
325: }
326: } finally {
327: if (monitor != null && monitorCount > monitorInterval) {
328: monitor.bytesTransferred(size);
329: monitorCount = 0;
330: }
331:
332: }
333: }
334:
335: /**
336: * Closes this input stream and releases any system resources associated
337: * with the stream. This <b>must</b> be called before any other operations
338: * are initiated on the FTPClient.
339: *
340: * @exception IOException if an I/O error occurs.
341: */
342: public void close() throws IOException {
343:
344: if (!closed) {
345: closed = true;
346:
347: client.forceResumeOff();
348:
349: // close streams
350: client.closeDataSocket(in);
351:
352: if (monitor != null)
353: monitor.bytesTransferred(size);
354:
355: // log bytes transferred
356: log
357: .debug("Transferred " + size
358: + " bytes from remote host");
359:
360: try {
361: client.validateTransfer();
362: } catch (FTPException ex) {
363: throw new IOException(ex.getMessage());
364: }
365:
366: if (monitorEx != null)
367: monitorEx.transferComplete(TransferDirection.DOWNLOAD,
368: remoteFile);
369: }
370: }
371:
372: }
|