package com.limegroup.gnutella.uploader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.CreationTimeCache; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.UploadManager; import com.limegroup.gnutella.http.ConstantHTTPHeaderValue; import com.limegroup.gnutella.http.HTTPHeaderName; import com.limegroup.gnutella.http.HTTPUtils; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.settings.UploadSettings; import com.limegroup.gnutella.util.BandwidthThrottle; /** * An implementation of the UploadState interface for a normal upload situation, * i.e., the real uploader. It should send the appropriate header information, * followed by the actual file. */ public final class NormalUploadState extends UploadState { /** The amount of time that a send/wait cycle should take for throttled * uploads. This should be short enough to not be noticeable in the GUI, * but long enough so that waits are not called so often as to be * inefficient. */ private static final Log LOG = LogFactory.getLog(NormalUploadState.class); private static final int BLOCK_SIZE=1024; private final int _index; private final String _fileName; private final int _fileSize; private InputStream _fis; private int _amountWritten; /** @see HTTPUploader#getUploadBegin */ private int _uploadBegin; /** @see HTTPUploader#getUploadEnd */ private int _uploadEnd; private int _amountRequested; /** * The task that periodically checks to see if the uploader has stalled. */ private StalledUploadWatchdog _stalledChecker; /** * Throttle for the speed of uploads. The rate will get dynamically * reset during the upload if the rate changes. */ private static final BandwidthThrottle THROTTLE = new BandwidthThrottle(UploadManager.getUploadSpeed(), false); /** * UDP throttle. */ private static final BandwidthThrottle UDP_THROTTLE = new BandwidthThrottle(UploadManager.getUploadSpeed(), false); /** * Constructs a new <tt>NormalUploadState</tt>, establishing all * invariants. * * @param uploaded the <tt>HTTPUploader</tt> */ public NormalUploadState(HTTPUploader uploader, StalledUploadWatchdog watchdog) { super(uploader); LOG.debug("creating a normal upload state"); _index = UPLOADER.getIndex(); _fileName = UPLOADER.getFileName(); _fileSize = (int)UPLOADER.getFileSize(); _amountWritten = 0; _stalledChecker = watchdog; //new StalledUploadWatchdog(); } public static void setThrottleSwitching(boolean on) { THROTTLE.setSwitching(on); } public void writeMessageHeaders(OutputStream network) throws IOException { LOG.debug("writing message headers"); try { Writer ostream = new StringWriter(); _fis = UPLOADER.getInputStream(); _uploadBegin = UPLOADER.getUploadBegin(); _uploadEnd = UPLOADER.getUploadEnd(); _amountRequested = (int)UPLOADER.getAmountRequested(); //guard clause if(_fileSize < _uploadBegin) throw new IOException("Invalid Range"); // Initial OK if( _uploadBegin==0 && _amountRequested==_fileSize ) { ostream.write("HTTP/1.1 200 OK\r\n"); } else { ostream.write("HTTP/1.1 206 Partial Content\r\n"); } HTTPUtils.writeHeader(HTTPHeaderName.SERVER, ConstantHTTPHeaderValue.SERVER_VALUE, ostream); HTTPUtils.writeHeader(HTTPHeaderName.CONTENT_TYPE, getMimeType(), ostream); HTTPUtils.writeHeader(HTTPHeaderName.CONTENT_LENGTH, _amountRequested, ostream); HTTPUtils.writeDate(ostream); HTTPUtils.writeContentDisposition(_fileName, ostream); // _uploadEnd is an EXCLUSIVE index internally, but HTTP uses an INCLUSIVE index. if (_uploadBegin != 0 || _amountRequested != _fileSize) { ostream.write("Content-Range: bytes " + _uploadBegin + "-" + ( _uploadEnd - 1 )+ "/" + _fileSize + "\r\n"); } writeAlts(ostream); writeRanges(ostream); writeProxies(ostream); if(FILE_DESC != null) { URN urn = FILE_DESC.getSHA1Urn(); if (UPLOADER.isFirstReply()) { // write the creation time if this is the first reply. // if this is just a continuation, we don't need to send // this information again. // it's possible t do that because we don't use the same // uploader for different files CreationTimeCache cache = CreationTimeCache.instance(); if (cache.getCreationTime(urn) != null) HTTPUtils.writeHeader( HTTPHeaderName.CREATION_TIME, cache.getCreationTime(urn).toString(), ostream); } } // write x-features header once because the downloader is // supposed to cache that information anyway if (UPLOADER.isFirstReply()) HTTPUtils.writeFeatures(ostream); // write X-Thex-URI header with root hash if we have already // calculated the tigertree if (FILE_DESC.getHashTree()!=null) HTTPUtils.writeHeader(HTTPHeaderName.THEX_URI, FILE_DESC.getHashTree(), ostream); ostream.write("\r\n"); _stalledChecker.activate(network); network.write(ostream.toString().getBytes()); } finally { // we do not need to check the return value because // if it was stalled, an IOException would have been thrown // causing us to fall out to the catch clause _stalledChecker.deactivate(); } } public void writeMessageBody(OutputStream ostream) throws IOException { LOG.debug("writing message body"); try { _fis.skip(_uploadBegin); upload(ostream); } catch(IOException e) { _stalledChecker.deactivate(); // no need to kill now throw e; } } /** * Upload the file, throttling the upload by making use of the * BandwidthThrottle class * @exception IOException If there is any I/O problem while uploading file */ private void upload(OutputStream ostream) throws IOException { // construct the buffer outside of the loop, so we don't // have to reconstruct new byte arrays every BLOCK_SIZE. byte[] buf = new byte[BLOCK_SIZE]; while (true) { BandwidthThrottle throttle = UPLOADER.isUDPTransfer() ? UDP_THROTTLE : THROTTLE; throttle.setRate(UploadManager.getUploadSpeed()); int c = -1; // request the bytes from the throttle // BLOCKING (only if we need to throttle) int allowed = BLOCK_SIZE; if(!UPLOADER.isForcedShare()) allowed = throttle.request(BLOCK_SIZE); int burstSent=0; try { c = _fis.read(buf, 0, allowed); } catch(NullPointerException npe) { // happens occasionally :( throw new IOException(npe.getMessage()); } if (c == -1) return; //dont upload more than asked if( c > (_amountRequested - _amountWritten)) c = _amountRequested - _amountWritten; _stalledChecker.activate(ostream); ostream.write(buf, 0, c); // we do not need to check the return value because // if it was stalled, an IOException would have been thrown // causing us to exit immediately. _stalledChecker.deactivate(); _amountWritten += c; UPLOADER.setAmountUploaded(_amountWritten); burstSent += c; //finish uploading if the desired amount //has been uploaded if(_amountWritten >= _amountRequested) return; } } /** * Eventually this method should determine the mime type of a file fill * in the details of this later. Assume binary for now. */ private String getMimeType() { return "application/binary"; } public boolean getCloseConnection() { return false; } // overrides Object.toString public String toString() { return "NormalUploadState:\r\n"+ "File Name: "+_fileName+"\r\n"+ "File Size: "+_fileSize+"\r\n"+ "File Index: "+_index+"\r\n"+ "File Desc: "+FILE_DESC; } }