package com.limegroup.gnutella.downloader;
import java.io.File;
import java.io.Serializable;
import com.limegroup.gnutella.DownloadCallback;
import com.limegroup.gnutella.DownloadManager;
import com.limegroup.gnutella.FileManager;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.util.StringUtils;
/**
* A ManagedDownloader that tries to resume a specific incomplete file. The
* ResumeDownloader initially has no locations to download from. Instead it
* immediately requeries--by hash if possible--and only accepts results that
* would result in resumes from the specified incomplete file. Do not be
* confused by the name; ManagedDownloader CAN resume from incomplete files,
* but it is less strict about its choice of download.
*/
public class ResumeDownloader extends ManagedDownloader
implements Serializable {
/** Ensures backwards compatibility of the downloads.dat file. */
static final long serialVersionUID = -4535935715006098724L;
/** The temporary file to resume to. */
private final File _incompleteFile;
/** The name and size of the completed file, extracted from
* _incompleteFile. */
private final String _name;
private final int _size;
/**
* The hash of the completed file. This field was not included in the LW
* 2.7.0/2.7.1 beta, so it may be null when reading downloads.dat files
* from these rare versions. That's no big deal; it is like not having the
* hash in the first place.
*
* This is not used as much anymore, since ManagedDownloader stores the
* SHA1 anyway. It is still used, however, to keep the sha1 between
* sessions, since it is serialized.
*/
private final URN _hash;
/**
* Creates a RequeryDownloader to finish downloading incompleteFile. This
* constructor has preconditions on several parameters; putting the burden
* on the caller makes the method easier to implement, since the superclass
* constructor immediately starts a download thread.
*
* @param incompleteFile the incomplete file to resume to, which
* MUST be the result of IncompleteFileManager.getFile.
* @param name the name of the completed file, which MUST be the result of
* IncompleteFileManager.getCompletedName(incompleteFile)
* @param size the size of the completed file, which MUST be the result of
* IncompleteFileManager.getCompletedSize(incompleteFile) */
public ResumeDownloader(IncompleteFileManager incompleteFileManager,
File incompleteFile,
String name,
int size) {
super( new RemoteFileDesc[0], incompleteFileManager, null);
if( incompleteFile == null )
throw new NullPointerException("null incompleteFile");
this._incompleteFile=incompleteFile;
if(name==null || name.equals(""))
throw new IllegalArgumentException("Bad name in ResumeDownloader");
this._name=name;
this._size=size;
this._hash=incompleteFileManager.getCompletedHash(incompleteFile);
}
/** Overrides ManagedDownloader to ensure that progress is initially
* non-zero and file previewing works. */
public void initialize(DownloadManager manager,
FileManager fileManager,
DownloadCallback callback) {
if(_hash != null)
downloadSHA1 = _hash;
incompleteFile = _incompleteFile;
super.initialize(manager, fileManager, callback);
}
/**
* Overrides ManagedDownloader to reserve _incompleteFile for this download.
* That is, any download that would use the same incomplete file is
* rejected, even if this is not currently downloading.
*/
public boolean conflictsWithIncompleteFile(File incompleteFile) {
return incompleteFile.equals(_incompleteFile);
}
/**
* Overrides ManagedDownloader to allow any RemoteFileDesc that would
* resume from _incompleteFile.
*/
protected boolean allowAddition(RemoteFileDesc other) {
//Like "_incompleteFile.equals(_incompleteFileManager.getFile(other))"
//but more efficient since no allocations in IncompleteFileManager.
return IncompleteFileManager.same(
_name, _size, downloadSHA1,
other.getFileName(), other.getSize(), other.getSHA1Urn());
}
/**
* Overrides ManagedDownloader to display a reasonable file size even
* when no locations have been found.
*/
public synchronized long getContentLength() {
return _size;
}
protected synchronized String getDefaultFileName() {
return _name;
}
/**
* Overriden to unset deserializedFromDisk too.
*/
public synchronized boolean resume() {
boolean ret = super.resume();
// unset deserialized once we clicked resume
if(ret)
deserializedFromDisk = false;
return ret;
}
/*
* @param numRequeries The number of requeries sent so far.
*/
protected boolean shouldSendRequeryImmediately(int numRequeries) {
// created from starting up LimeWire.
if(deserializedFromDisk)
return false;
// clicked Find More Sources?
else if(numRequeries > 0)
return super.shouldSendRequeryImmediately(numRequeries);
// created from clicking 'Resume' in the library
else
return true;
}
protected boolean shouldInitAltLocs(boolean deserializedFromDisk) {
// we shoudl only initialize alt locs when we are started from the
// library, not when we are resumed from startup.
return !deserializedFromDisk;
}
/** Overrides ManagedDownloader to use the filename and hash (if present) of
* the incomplete file. */
protected QueryRequest newRequery(int numRequeries) {
// Extract a query string from our filename.
String queryName = StringUtils.createQueryString(getDefaultFileName());
if (downloadSHA1 != null)
// TODO: we should be sending the URN with the query, but
// we don't because URN queries are summarily dropped, though
// this may change
return QueryRequest.createQuery(queryName);
else
return QueryRequest.createQuery(queryName);
}
}