package com.limegroup.gnutella; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Iterator; import java.util.Set; import com.limegroup.gnutella.downloader.Interval; import com.limegroup.gnutella.downloader.ManagedDownloader; import com.limegroup.gnutella.downloader.VerifyingFile; import com.limegroup.gnutella.http.HTTPHeaderValue; import com.limegroup.gnutella.tigertree.HashTree; /** * This class extends FileDesc and wraps an incomplete File, * so it can be used for partial file sharing. */ public class IncompleteFileDesc extends FileDesc implements HTTPHeaderValue { /** * Ranges smalles than this will never be offered to other servents */ private final static int MIN_CHUNK_SIZE = 102400; // 100K /** * Needed to find out what ranges are available */ private VerifyingFile _verifyingFile; /** * The name of the file, as returned by IncompleteFileManager * .getCompletedName(FILE). */ private final String _name; /** * The size of the file, casted to an <tt>int</tt>. */ private final int _size; /** * Constructor for the IncompleteFileDesc object. */ public IncompleteFileDesc(File file, Set urns, int index, String completedName, int completedSize, VerifyingFile vf) { super(file, urns, index); _name = completedName; _size = completedSize; _verifyingFile = vf; } /** * Returns the completed size of the file on disk, in bytes. * * @return the size of the file on disk, in bytes */ public long getFileSize() { return _size; } /** * Returns the completed name of this file. * * @return the name of this file */ public String getFileName() { return _name; } /** * Opens an input stream to the <tt>File</tt> instance for this * <tt>FileDesc</tt>. * * @return an <tt>InputStream</tt> to the <tt>File</tt> instance * @throws <tt>FileNotFoundException</tt> if the file represented * by the <tt>File</tt> instance could not be found */ public InputStream createInputStream() throws FileNotFoundException { // if we don't have any available ranges, we should never // have entered the download mesh in the first place!!! if (getFile().length() == 0) throw new FileNotFoundException("nothing downloaded"); return new BufferedInputStream(new FileInputStream(getFile())); } /** * Returns null, overrides super.getHashTree to prevent us from offering * HashTrees for incomplete files. * @return null */ public HashTree getHashTree() { return null; } private ManagedDownloader getMyDownloader() { return RouterService.getDownloadManager().getDownloaderForURN(getSHA1Urn()); } /** * Returns whether or not we are actively downloading this file. */ public boolean isActivelyDownloading() { ManagedDownloader md = getMyDownloader(); if(md == null) return false; switch(md.getState()) { case Downloader.QUEUED: case Downloader.BUSY: case Downloader.ABORTED: case Downloader.GAVE_UP: case Downloader.DISK_PROBLEM: case Downloader.CORRUPT_FILE: case Downloader.REMOTE_QUEUED: case Downloader.WAITING_FOR_USER: return false; default: return true; } } public byte [] getRangesAsByte() { return _verifyingFile.toBytes(); } /** * Returns the available ranges as an HTTP string value. */ public String getAvailableRanges() { StringBuffer ret = new StringBuffer("bytes"); boolean added = false; // This must be synchronized so that downloaders writing // to the verifying file do not cause concurrent mod // exceptions. synchronized(_verifyingFile) { for (Iterator iter = _verifyingFile.getVerifiedBlocks(); iter.hasNext(); ) { Interval interval = (Interval) iter.next(); // don't offer ranges that are smaller than MIN_CHUNK_SIZE // ( we add one because HTTP values are exclusive ) if (interval.high - interval.low + 1 < MIN_CHUNK_SIZE) continue; added = true; // ( we subtract one because HTTP values are exclusive ) ret.append(" ").append(interval.low).append("-").append(interval.high -1).append(","); } } // truncate off the last ',' if atleast one was added. // it is necessary to do this (instead of checking hasNext when // adding the comma) because it's possible that the last range // is smaller than MIN_CHUNK_SIZE, leaving an extra comma at the end. if(added) ret.setLength(ret.length()-1); return ret.toString(); } /** * Determines whether or not the given range is satisfied by this * incomplete file. */ public boolean isRangeSatisfiable(int low, int high) { // This must be synchronized so that downloaders writing // to the verifying file do not cause concurrent mod // exceptions. synchronized(_verifyingFile) { for (Iterator iter = _verifyingFile.getVerifiedBlocks(); iter.hasNext(); ) { Interval interval = (Interval) iter.next(); if (low >= interval.low && high <= interval.high) return true; } } return false; } /** * Adjusts the requested range to the available range. * @return Interval that has been clipped to match the available range, null * if the interval does not overlap any available ranges */ public Interval getAvailableSubRange(int low, int high) { synchronized(_verifyingFile) { for (Iterator iter = _verifyingFile.getVerifiedBlocks(); iter.hasNext(); ) { Interval interval = (Interval) iter.next(); if ((interval.low <= high && low <= interval.high)) // overlap found return new Interval(Math.max(interval.low, low), Math.min(interval.high, high)); else if (interval.low > high) // passed all viable intervals break; } return null; } } /** * Determines whether or not the given interval is within the range * of our incomplete file. */ public boolean isRangeSatisfiable(Interval range) { return isRangeSatisfiable(range.low, range.high); } // implements HTTPHeaderValue public String httpStringValue() { return getAvailableRanges(); } // overrides Object.toString to provide a more useful description public String toString() { return ("IncompleteFileDesc:\r\n"+ "name: "+_name+"\r\n"+ "index: "+getIndex()+"\r\n"); } }