package org.limewire.http.entity; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpEntity; import org.apache.http.entity.FileEntity; import org.apache.http.nio.ContentDecoder; import org.apache.http.nio.ContentEncoder; import org.apache.http.nio.IOControl; import org.apache.http.nio.entity.ProducingNHttpEntity; import org.limewire.nio.NIODispatcher; import org.limewire.nio.observer.Shutdownable; import org.limewire.nio.timeout.StalledUploadWatchdog; /** * An event based {@link HttpEntity} that uploads a {@link File}. A * corresponding {@link FileTransferMonitor} is updated with progress. */ public class FileNIOEntity extends FileEntity implements ProducingNHttpEntity { private static final Log LOG = LogFactory.getLog(FileNIOEntity.class); private final FileTransferMonitor transfer; private final File file; /** Buffer that is currently transferred. */ private ByteBuffer buffer; /** Total number of bytes to transfer. */ private long length; /** Offset of the first byte. */ private long begin; /** Number of bytes remaining to be read from disk. */ private long remaining; private FilePieceReader reader; /** Piece that is currently transferred. */ private Piece piece; private IOControl ioctrl; /** Cancels the transfer if inactivity for too long. */ private StalledUploadWatchdog watchdog; private long timeout = StalledUploadWatchdog.DELAY_TIME; /** shutdownable to shut off in case of a timeout */ private final Shutdownable timeoutable = new Shutdownable() { public void shutdown() { timeout(); } }; public FileNIOEntity(final File file, final String contentType, final FileTransferMonitor transfer, final long beginIndex, final long length) { super(file, contentType); this.transfer = transfer; this.file = file; this.begin = beginIndex; this.length = length; this.remaining = length; if (length < 0) { throw new IllegalStateException("upload end must be >= upload begin"); } } public FileNIOEntity(File file, String contentType, FileTransferMonitor transfer) { this(file, contentType, transfer, 0, file.length()); } public void setTimeout(long timeout) { this.timeout = timeout; } public long getTimeout() { return timeout; } @Override public long getContentLength() { return length; } @Override public InputStream getContent() throws IOException { final FileInputStream in = new FileInputStream(this.file); return new InputStream() { @Override public int read() throws IOException { if (remaining == 0) { return -1; } remaining--; return in.read(); } }; } @Override public boolean isRepeatable() { return false; } @Override public void writeTo(final OutputStream outstream) throws IOException { if (outstream == null) { throw new IllegalArgumentException("Output stream may not be null"); } InputStream instream = new BufferedInputStream(new FileInputStream( this.file)); try { byte[] tmp = new byte[4096]; int l; instream.skip(this.begin); while (remaining > 0 && (l = instream.read(tmp, 0, // (int) Math.min(remaining, tmp.length))) != -1) { outstream.write(tmp, 0, l); remaining -= l; } outstream.flush(); } finally { instream.close(); } } public File getFile() { return file; } public void initializeReader() throws IOException { if (LOG.isDebugEnabled()) LOG.debug("Initializing upload of " + file.getName() + " [begin=" + begin + ",length=" + length + "]"); if (length == 0) { // handle special case of empty file return; } transfer.start(); reader = new FilePieceReader(NIODispatcher.instance().getBufferCache(), file, begin, length, new PieceHandler()); reader.start(); } public void initializeWriter() throws IOException { // TODO implement throw new UnsupportedOperationException(); } public void finish() { deactivateTimeout(); if (reader != null) { reader.shutdown(); reader = null; } ioctrl = null; } public void produceContent(ContentEncoder encoder, IOControl ioctrl) throws IOException { if (this.ioctrl == null) { this.ioctrl = ioctrl; initializeReader(); } // flush current buffer if (buffer != null && buffer.hasRemaining()) { int written = encoder.write(buffer); transfer.addAmountUploaded(written); if (buffer.hasRemaining()) { activateTimeout(); return; } else if (remaining == 0) { reader.release(piece); encoder.complete(); return; } } else if (remaining == 0) { // handle special case of empty file encoder.complete(); return; } int written; do { if (buffer == null || !buffer.hasRemaining()) { if (piece != null) { reader.release(piece); } // get next piece from file synchronized (this) { piece = reader.next(); if (piece == null) { // need to wait for the disk, PieceHandler will turn // interest back on when the next piece is available buffer = null; ioctrl.suspendOutput(); activateTimeout(); return; } buffer = piece.getBuffer(); remaining -= buffer.remaining(); } } if (LOG.isTraceEnabled()) LOG.trace("Uploading " + file.getName() + " [read=" + buffer.remaining() + ",remaining=" + remaining + "]"); written = encoder.write(buffer); transfer.addAmountUploaded(written); } while (written > 0 && remaining > 0); if (remaining == 0 && !buffer.hasRemaining()) { encoder.complete(); } else { activateTimeout(); } } protected void activateTimeout() { if (this.watchdog == null) { this.watchdog = new StalledUploadWatchdog(timeout, NIODispatcher.instance().getScheduledExecutorService()); } this.watchdog.activate(timeoutable); } protected void deactivateTimeout() { if (this.watchdog != null) { this.watchdog.deactivate(); } } public int consumeContent(ContentDecoder decoder, IOControl ioctrl) throws IOException { // TODO implement throw new UnsupportedOperationException(); } @Override public String toString() { return getClass().getName() + " [file=" + file.getName() + "]"; } public void timeout() { if (LOG.isWarnEnabled()) LOG.warn("File transfer timed out: " + transfer); transfer.timeout(); } private class PieceHandler implements PieceListener { public void readFailed(IOException e) { if (LOG.isWarnEnabled()) LOG.warn("Error reading file from disk: " + transfer, e); transfer.failed(e); } public void readSuccessful() { synchronized (FileNIOEntity.this) { ioctrl.requestOutput(); } } } }