package com.limegroup.gnutella.uploader;
import java.io.File;
import java.io.IOException;
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.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.limewire.http.entity.AbstractProducingNHttpEntity;
import org.limewire.http.entity.FilePieceReader;
import org.limewire.http.entity.Piece;
import org.limewire.http.entity.PieceListener;
import org.limewire.http.reactor.HttpIOSession;
import org.limewire.nio.NIODispatcher;
import com.google.inject.Provider;
import com.limegroup.gnutella.BandwidthManager;
import com.limegroup.gnutella.Constants;
/**
* An event based {@link HttpEntity} that uploads a {@link File}. A
* corresponding {@link HTTPUploader} is updated with progress.
*/
public class FileResponseEntity extends AbstractProducingNHttpEntity {
private static final Log LOG = LogFactory.getLog(FileResponseEntity.class);
private final HTTPUploader uploader;
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 final Provider<BandwidthManager> bandwidthManager;
FileResponseEntity(HTTPUploader uploader, File file, Provider<BandwidthManager> bandwidthManager) {
this.uploader = uploader;
this.file = file;
this.bandwidthManager = bandwidthManager;
setContentType(Constants.FILE_MIME_TYPE);
begin = uploader.getUploadBegin();
long end = uploader.getUploadEnd();
length = end - begin;
remaining = length;
if (length < 0) {
throw new IllegalStateException("upload end must be >= upload begin");
}
}
@Override
public long getContentLength() {
return length;
}
@Override
public void initialize(ContentEncoder contentEncoder, IOControl ioctrl) {
if (LOG.isDebugEnabled())
LOG.debug("Initializing upload of " + file.getName() + " [begin=" + begin + ",length=" + length + "]");
if (length == 0) {
// handle special case of empty file upload
return;
}
HttpIOSession ioSession = uploader.getSession().getIOSession();
ioSession.setThrottle(bandwidthManager.get().getWriteThrottle(ioSession.getSocket()));
reader = new FilePieceReader(NIODispatcher.instance().getBufferCache(), file, begin, length, new PieceHandler(ioctrl));
reader.start();
}
public void finish() {
if (LOG.isDebugEnabled())
LOG.debug("Finished upload of " + file.getName() + " [begin=" + begin + ",length=" + length + ",remaining=" + remaining + "]");
deactivateTimeout();
if (reader != null) {
reader.shutdown();
}
}
@Override
public boolean writeContent(ContentEncoder contentEncoder, IOControl ioctrl) throws IOException {
// Throwable t = new Throwable();
// LOG.debug(t, t);
// flush current buffer
if (buffer != null && buffer.hasRemaining()) {
int written = contentEncoder.write(buffer);
uploader.addAmountUploaded(written);
if (buffer.hasRemaining()) {
activateTimeout();
return true;
} else if (remaining == 0) {
if (LOG.isTraceEnabled())
LOG.trace("... buffer drained and upload complete");
reader.release(piece);
return false;
}
} else if (remaining == 0) {
if (LOG.isTraceEnabled())
LOG.trace("upload complete");
// handle special case of empty file upload
return false;
}
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();
if (LOG.isTraceEnabled())
LOG.trace("Waiting for file contents to be read");
return true;
}
buffer = piece.getBuffer();
remaining -= buffer.remaining();
}
}
if (LOG.isTraceEnabled())
LOG.trace("Uploading " + file.getName() + " [remaining=" + remaining + "+" + buffer.remaining() + "]");
written = contentEncoder.write(buffer);
// if (LOG.isTraceEnabled())
// LOG.trace("wrote " + written + " bytes");
uploader.addAmountUploaded(written);
} while (written > 0 && remaining > 0);
activateTimeout();
// if (LOG.isTraceEnabled())
// LOG.trace("returning " + (remaining > 0 || buffer.hasRemaining()) + " [remaining: " + remaining + ", buffer.hasRemaining(): " + buffer.hasRemaining() + "]");
return remaining > 0 || buffer.hasRemaining();
}
@Override
public void timeout() {
if (LOG.isWarnEnabled())
LOG.warn("File transfer timed out: " + uploader);
uploader.stop();
}
@Override
public String toString() {
return getClass().getName() + " [file=" + file.getName() + "]";
}
private class PieceHandler implements PieceListener {
private final IOControl ioControl;
public PieceHandler(IOControl ioControl) {
this.ioControl = ioControl;
}
public void readFailed(IOException e) {
if (LOG.isWarnEnabled())
LOG.warn("Error reading file from disk: " + uploader, e);
uploader.stop();
}
public void readSuccessful() {
synchronized (FileResponseEntity.this) {
LOG.debug("read successful");
ioControl.requestOutput();
}
}
}
}