forked from I2P_Developers/i2p.i2p
310 lines
9.8 KiB
Java
310 lines
9.8 KiB
Java
package org.klomp.snark;
|
|
|
|
import java.io.File;
|
|
import java.net.URI;
|
|
import java.util.List;
|
|
|
|
import net.i2p.I2PAppContext;
|
|
import net.i2p.crypto.TrustedUpdate;
|
|
import net.i2p.update.*;
|
|
import net.i2p.util.Log;
|
|
import net.i2p.util.SimpleTimer2;
|
|
|
|
/**
|
|
* The downloader for router signed updates.
|
|
*
|
|
* @since 0.9.4
|
|
*/
|
|
class UpdateRunner implements UpdateTask, CompleteListener {
|
|
private final I2PAppContext _context;
|
|
private final Log _log;
|
|
private final UpdateManager _umgr;
|
|
private final SnarkManager _smgr;
|
|
private final UpdateType _type;
|
|
private final List<URI> _urls;
|
|
private volatile boolean _isRunning;
|
|
private volatile boolean _hasMetaInfo;
|
|
private volatile boolean _isComplete;
|
|
private final String _newVersion;
|
|
private URI _currentURI;
|
|
private Snark _snark;
|
|
|
|
private static final long MAX_LENGTH = 30*1024*1024;
|
|
private static final long METAINFO_TIMEOUT = 30*60*1000;
|
|
private static final long COMPLETE_TIMEOUT = 3*60*60*1000;
|
|
private static final long CHECK_INTERVAL = 3*60*1000;
|
|
|
|
public UpdateRunner(I2PAppContext ctx, UpdateManager umgr, SnarkManager smgr,
|
|
UpdateType type, List<URI> uris, String newVersion) {
|
|
_context = ctx;
|
|
_log = ctx.logManager().getLog(getClass());
|
|
_umgr = umgr;
|
|
_smgr = smgr;
|
|
_type = type;
|
|
_urls = uris;
|
|
_newVersion = newVersion;
|
|
}
|
|
|
|
//////// begin UpdateTask methods
|
|
|
|
public boolean isRunning() { return _isRunning; }
|
|
|
|
public void shutdown() {
|
|
_isRunning = false;
|
|
if (_snark != null) {
|
|
|
|
}
|
|
}
|
|
|
|
public UpdateType getType() { return _type; }
|
|
|
|
public UpdateMethod getMethod() { return UpdateMethod.TORRENT; }
|
|
|
|
public URI getURI() { return _currentURI; }
|
|
|
|
public String getID() { return ""; }
|
|
|
|
//////// end UpdateTask methods
|
|
|
|
public void start() {
|
|
_isRunning = true;
|
|
update();
|
|
}
|
|
|
|
/**
|
|
* Loop through the entire list of update URLs.
|
|
* For each one, first get the version from the first 56 bytes and see if
|
|
* it is newer than what we are running now.
|
|
* If it is, get the whole thing.
|
|
*/
|
|
private void update() {
|
|
for (URI uri : _urls) {
|
|
_currentURI = uri;
|
|
String updateURL = uri.toString();
|
|
try {
|
|
MagnetURI magnet = new MagnetURI(_smgr.util(), updateURL);
|
|
byte[] ih = magnet.getInfoHash();
|
|
// do we already have it?
|
|
_snark = _smgr.getTorrentByInfoHash(ih);
|
|
if (_snark != null) {
|
|
if (_snark.getMetaInfo() != null) {
|
|
_hasMetaInfo = true;
|
|
Storage storage = _snark.getStorage();
|
|
if (storage != null && storage.complete())
|
|
processComplete(_snark);
|
|
}
|
|
if (!_isComplete) {
|
|
if (_snark.isStopped() && !_snark.isStarting())
|
|
_snark.startTorrent();
|
|
// we aren't a listener so we must poll
|
|
new Watcher();
|
|
}
|
|
break;
|
|
}
|
|
String name = magnet.getName();
|
|
String trackerURL = magnet.getTrackerURL();
|
|
if (trackerURL == null && !_smgr.util().shouldUseDHT() &&
|
|
!_smgr.util().shouldUseOpenTrackers()) {
|
|
// but won't we use OT as a failsafe even if disabled?
|
|
_umgr.notifyAttemptFailed(this, "No tracker, no DHT, no OT", null);
|
|
continue;
|
|
}
|
|
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, this);
|
|
if (_snark != null) {
|
|
updateStatus("<b>" + _smgr.util().getString("Updating from {0}", linkify(updateURL)) + "</b>");
|
|
new Timeout();
|
|
break;
|
|
}
|
|
} catch (IllegalArgumentException iae) {
|
|
_log.error("Invalid update URL", iae);
|
|
}
|
|
}
|
|
if (_snark == null)
|
|
fatal("No valid URLs");
|
|
}
|
|
|
|
/**
|
|
* This will run twice, once at the metainfo timeout and
|
|
* once at the complete timeout.
|
|
*/
|
|
private class Timeout extends SimpleTimer2.TimedEvent {
|
|
private final long _start = _context.clock().now();
|
|
|
|
public Timeout() {
|
|
super(_context.simpleTimer2(), METAINFO_TIMEOUT);
|
|
}
|
|
|
|
public void timeReached() {
|
|
if (_isComplete || !_isRunning)
|
|
return;
|
|
if (!_hasMetaInfo) {
|
|
fatal("Metainfo timeout");
|
|
return;
|
|
}
|
|
if (_context.clock().now() - _start >= COMPLETE_TIMEOUT) {
|
|
fatal("Complete timeout");
|
|
return;
|
|
}
|
|
reschedule(COMPLETE_TIMEOUT - METAINFO_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rarely used - only if the user added the torrent, so
|
|
* we aren't a complete listener.
|
|
* This will periodically until the complete timeout.
|
|
*/
|
|
private class Watcher extends SimpleTimer2.TimedEvent {
|
|
private final long _start = _context.clock().now();
|
|
|
|
public Watcher() {
|
|
super(_context.simpleTimer2(), CHECK_INTERVAL);
|
|
}
|
|
|
|
public void timeReached() {
|
|
if (_hasMetaInfo && _snark.getRemainingLength() == 0 && !_isComplete)
|
|
processComplete(_snark);
|
|
if (_isComplete || !_isRunning)
|
|
return;
|
|
if (_context.clock().now() - _start >= METAINFO_TIMEOUT && !_hasMetaInfo) {
|
|
fatal("Metainfo timeout");
|
|
return;
|
|
}
|
|
if (_context.clock().now() - _start >= COMPLETE_TIMEOUT) {
|
|
fatal("Complete timeout");
|
|
return;
|
|
}
|
|
notifyProgress();
|
|
reschedule(CHECK_INTERVAL);
|
|
}
|
|
}
|
|
|
|
private void fatal(String error) {
|
|
if (_snark != null) {
|
|
if (_hasMetaInfo) {
|
|
_smgr.stopTorrent(_snark, true);
|
|
String file = _snark.getName();
|
|
_smgr.removeTorrent(file);
|
|
// delete torrent file
|
|
File f = new File(_smgr.getDataDir(), file);
|
|
f.delete();
|
|
// delete data
|
|
file = _snark.getBaseName();
|
|
f = new File(_smgr.getDataDir(), file);
|
|
f.delete();
|
|
} else {
|
|
_smgr.deleteMagnet(_snark);
|
|
}
|
|
}
|
|
_umgr.notifyTaskFailed(this, error, null);
|
|
_log.error(error);
|
|
_isRunning = false;
|
|
// stop the tunnel if we were the only one running
|
|
if (_smgr.util().connected() && !_smgr.util().isConnecting()) {
|
|
for (Snark s : _smgr.getTorrents()) {
|
|
if (!s.isStopped())
|
|
return;
|
|
}
|
|
_smgr.util().disconnect();
|
|
}
|
|
}
|
|
|
|
private void processComplete(Snark snark) {
|
|
String dataFile = snark.getBaseName();
|
|
File f = new File(_smgr.getDataDir(), dataFile);
|
|
String sudVersion = TrustedUpdate.getVersionString(f);
|
|
if (_newVersion.equals(sudVersion))
|
|
_umgr.notifyComplete(this, _newVersion, f);
|
|
else
|
|
fatal("version mismatch");
|
|
_isComplete = true;
|
|
}
|
|
|
|
private void notifyProgress() {
|
|
if (_hasMetaInfo) {
|
|
long total = _snark.getTotalLength();
|
|
long remaining = _snark.getRemainingLength();
|
|
String status = "<b>" + _smgr.util().getString("Updating") + "</b>";
|
|
_umgr.notifyProgress(this, status, total - remaining, total);
|
|
}
|
|
}
|
|
|
|
//////// begin CompleteListener methods
|
|
//////// all pass through to SnarkManager
|
|
|
|
public void torrentComplete(Snark snark) {
|
|
processComplete(snark);
|
|
_smgr.torrentComplete(snark);
|
|
}
|
|
|
|
/**
|
|
* This is called by stopTorrent() among others
|
|
*/
|
|
public void updateStatus(Snark snark) {
|
|
if (snark.isStopped()) {
|
|
if (!_isComplete)
|
|
fatal("stopped by user");
|
|
}
|
|
_smgr.updateStatus(snark);
|
|
}
|
|
|
|
public String gotMetaInfo(Snark snark) {
|
|
MetaInfo info = snark.getMetaInfo();
|
|
if (info.getFiles() != null) {
|
|
fatal("more than 1 file");
|
|
return null;
|
|
}
|
|
if (info.isPrivate()) {
|
|
fatal("private torrent");
|
|
return null;
|
|
}
|
|
if (info.getTotalLength() > MAX_LENGTH) {
|
|
fatal("too big");
|
|
return null;
|
|
}
|
|
_hasMetaInfo = true;
|
|
notifyProgress();
|
|
snark.setAutoStoppable(true);
|
|
return _smgr.gotMetaInfo(snark);
|
|
}
|
|
|
|
public void fatal(Snark snark, String error) {
|
|
fatal(error);
|
|
_smgr.fatal(snark, error);
|
|
}
|
|
|
|
public void addMessage(Snark snark, String message) {
|
|
_smgr.addMessage(snark, message);
|
|
}
|
|
|
|
public void gotPiece(Snark snark) {
|
|
notifyProgress();
|
|
_smgr.gotPiece(snark);
|
|
}
|
|
|
|
public long getSavedTorrentTime(Snark snark) {
|
|
return _smgr.getSavedTorrentTime(snark);
|
|
}
|
|
|
|
public BitField getSavedTorrentBitField(Snark snark) {
|
|
return _smgr.getSavedTorrentBitField(snark);
|
|
}
|
|
|
|
//////// end CompleteListener methods
|
|
|
|
private static String linkify(String url) {
|
|
String durl = url.length() <= 28 ? url :
|
|
url.substring(0, 25) + "…";
|
|
return "<a target=\"_blank\" href=\"" + url + "\"/>" + durl + "</a>";
|
|
}
|
|
|
|
private void updateStatus(String s) {
|
|
_umgr.notifyProgress(this, s);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return getClass().getName() + ' ' + getType() + ' ' + getID() + ' ' + getMethod() + ' ' + getURI();
|
|
}
|
|
}
|