Compare commits

...

9 Commits

Author SHA1 Message Date
a85faada6e Plugin: get it to recognize when the download is complete 2024-01-23 18:09:35 -05:00
b0818f95ec Plugins: Actually do the part that does the torrent download now 2024-01-23 16:11:14 -05:00
1c070baf6d Plugins: import the necessary parts of I2PSnark to enable torrent updates for plugins 2024-01-22 14:07:16 -05:00
8e2ed37544 Plugin: check if torrent is there first and go directly to HTTP update if it fails, to reduce downloads 2024-01-21 19:42:20 -05:00
25ba8926b7 Plugins: fix flow for http-update-to-torrent-upgrade, torrent-failed-to-http-fallback 2024-01-19 12:52:57 -05:00
9c533b5a27 Plugins: fix up baseline formatting in new classes 2024-01-18 09:58:25 -05:00
818aa6a5c5 Plugins: alter scope of parts of PluginUpdateRunner to make them accessible to extenders 2024-01-17 20:09:02 -05:00
aa681b3f98 Plugins: allow plugin updates to come from torrent sources.
This is a simple(I think), backward-compatible way of updating plugins using torrents as sources.
Plugin developers can keep the existing update URL.
If both `http://host.i2p/plugin.su3` and `http://host.i2p/plugin.su3.torrent` exist, then the updater will
download the torrent and treat the `http://host.i2p/plugin.su3` as a webseed.
When the download is finished, the plugin update proceeds as normal.

The main use-case for this is for very large plugins that potentially come with a lot of static data.
This is true of `i2p.plugins.firefox` in particular which is about ~130mb.
2024-01-17 18:26:11 -05:00
acffbcc1d4 Plugins: allow plugin updates to come from torrent sources.
This is a simple(I think), backward-compatible way of updating plugins using torrents as sources.
Plugin developers can keep the existing update URL.
If both `http://host.i2p/plugin.su3` and `http://host.i2p/plugin.su3.torrent` exist, then the updater will
download the torrent and treat the `http://host.i2p/plugin.su3` as a webseed.
When the download is finished, the plugin update proceeds as normal.

The main use-case for this is for very large plugins that potentially come with a lot of static data.
This is true of `i2p.plugins.firefox` in particular which is about ~130mb.
2024-01-17 18:26:05 -05:00
7 changed files with 593 additions and 14 deletions

View File

@ -789,6 +789,22 @@ public class MetaInfo
return Collections.unmodifiableMap(infoMap);
}
public String toMagnetURI() {
String magnetURIString = "magnet:?xt=urn:btih:" + I2PSnarkUtil.toHex(calculateInfoHash());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Created magnet URI: " + magnetURIString);
long exactLength = getTotalLength();
if (exactLength != 0)
magnetURIString += "&xl=" + exactLength;
String announce = getAnnounce();
if (announce != null)
magnetURIString += "&tr=" + announce;
List<String> webSeeds = getWebSeedURLs();
if (webSeeds != null)
magnetURIString += "&ws=" + webSeeds.get(0);
return magnetURIString;
}
private byte[] calculateInfoHash()
{
Map<String, BEValue> info = createInfoMap();

View File

@ -30,8 +30,10 @@
<pathelement location="../../jetty/jettylib/jetty-i2p.jar" />
<pathelement location="../../systray/java/build/obj" />
<pathelement location="../../desktopgui/build" />
<pathelement location="../../i2psnark/java/build/i2psnark.jar" />
<pathelement location="../../../installer/lib/wrapper/all/wrapper.jar" />
<pathelement location="../../jrobin/java/build/jrobin.jar" />
<pathelement location="../../../build/mstreaming.jar" />
</classpath>
</depend>
</target>
@ -66,6 +68,7 @@
<compilerarg line="${javac.compilerargs}" />
<classpath>
<pathelement location="../../../core/java/build/i2p.jar" />
<pathelement location="../../../build/mstreaming.jar" />
<!-- gnu-getopt.jar only present for debian builds -->
<pathelement location="../../../core/java/build/gnu-getopt.jar" />
<pathelement location="../../../router/java/build/router.jar" />
@ -82,6 +85,7 @@
<pathelement location="../../jetty/jettylib/jetty-i2p.jar" />
<pathelement location="../../systray/java/build/systray.jar" />
<pathelement location="../../desktopgui/dist/desktopgui.jar" />
<pathelement location="../../i2psnark/java/build/i2psnark.jar" />
<pathelement location="../../../installer/lib/wrapper/all/wrapper.jar" />
<pathelement location="../../jrobin/java/build/jrobin.jar" />

View File

@ -1,5 +1,7 @@
package net.i2p.router.update;
import static net.i2p.update.UpdateMethod.TORRENT;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
@ -42,7 +44,7 @@ class PluginUpdateHandler implements Checker, Updater {
public UpdateTask check(UpdateType type, UpdateMethod method,
String appName, String currentVersion, long maxTime) {
if ((type != UpdateType.PLUGIN) ||
method != UpdateMethod.HTTP || appName.length() <= 0)
(method != UpdateMethod.HTTP || method != UpdateMethod.TORRENT) || appName.length() <= 0)
return null;
Properties props = PluginStarter.pluginProperties(_context, appName);
@ -58,6 +60,9 @@ class PluginUpdateHandler implements Checker, Updater {
_log.info("Checking for updates for " + appName + ": " + xpi2pURL);
try {
updateSources = Collections.singletonList(new URI(xpi2pURL));
if (method == UpdateMethod.TORRENT)
updateSources = Collections.singletonList(new URI(xpi2pURL + ".torrent"));
} catch (URISyntaxException use) {}
}
@ -65,8 +70,12 @@ class PluginUpdateHandler implements Checker, Updater {
//updateStatus("<b>" + _t("Cannot check, plugin {0} is not installed", appName) + "</b>");
return null;
}
UpdateRunner update = new PluginUpdateChecker(_context, _mgr, updateSources, appName, oldVersion);
UpdateRunner update = null;
if (method == UpdateMethod.TORRENT) {
update = new PluginUpdateTorrentChecker(_context, _mgr, updateSources, appName, oldVersion);
} else {
update = new PluginUpdateChecker(_context, _mgr, updateSources, appName, oldVersion);
}
return update;
}

View File

@ -50,12 +50,12 @@ import net.i2p.util.VersionComparator;
*/
class PluginUpdateRunner extends UpdateRunner {
private String _appName;
private final String _appDisplayName;
protected String _appName;
protected final String _appDisplayName;
private final String _oldVersion;
private final URI _uri;
private final String _xpi2pURL;
private boolean _updated;
protected final URI _uri;
protected final String _xpi2pURL;
protected boolean _updated;
private String _errMsg = "";
private static final String XPI2P = "app.xpi2p";
@ -199,7 +199,7 @@ class PluginUpdateRunner extends UpdateRunner {
* @since 0.9.15
* @return if SU3
*/
private static boolean isSU3File(File f) throws IOException {
protected static boolean isSU3File(File f) throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
@ -217,7 +217,7 @@ class PluginUpdateRunner extends UpdateRunner {
* @since 0.9.15
* @return success
*/
private void processSUD(File f, File appDir, String url) {
protected void processSUD(File f, File appDir, String url) {
TrustedUpdate up = new TrustedUpdate(_context);
File to = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + ZIP);
// extract to a zip file whether the sig is good or not, so we can get the properties file
@ -326,7 +326,7 @@ class PluginUpdateRunner extends UpdateRunner {
/**
* @since 0.9.15
*/
private void processSU3(File f, File appDir, String url) {
protected void processSU3(File f, File appDir, String url) {
SU3File su3 = new SU3File(_context, f);
File to = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + ZIP);
String sudVersion;
@ -373,7 +373,7 @@ class PluginUpdateRunner extends UpdateRunner {
* @since 0.9.15
* @return null on error
*/
private Properties getPluginConfig(File f, File to, String url) {
protected Properties getPluginConfig(File f, File to, String url) {
File tempDir = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + "-unzip");
if (!FileUtil.extractZip(to, tempDir, Log.ERROR)) {
f.delete();
@ -406,7 +406,7 @@ class PluginUpdateRunner extends UpdateRunner {
* @param pubkey null OK for su3
* @since 0.9.15
*/
private void processFinal(File to, File appDir, String url, Properties props, String sudVersion, String pubkey, String signer) {
protected void processFinal(File to, File appDir, String url, Properties props, String sudVersion, String pubkey, String signer) {
boolean update = false;
String appName = props.getProperty("name");
String version = props.getProperty("version");

View File

@ -0,0 +1,94 @@
package net.i2p.router.update;
import java.io.File;
import java.net.URI;
import java.util.List;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.router.RouterContext;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.update.UpdateMethod;
import net.i2p.update.UpdateType;
import net.i2p.util.EepGet;
import net.i2p.util.PartialEepGet;
import net.i2p.util.PortMapper;
public class PluginUpdateTorrentChecker extends PluginUpdateChecker {
private final String _appName;
private final String _oldVersion;
private final URI _torrentURI;
public PluginUpdateTorrentChecker(RouterContext ctx, ConsoleUpdateManager mgr,
List<URI> uris, String appName, String oldVersion) {
//super(ctx, mgr, UpdateType.PLUGIN, uris, oldVersion);
super(ctx, mgr, uris, appName, oldVersion);
if (!uris.isEmpty())
_currentURI = uris.get(0);
if (uris.size() == 2)
_torrentURI = uris.get(1);
else
_torrentURI = null;
_appName = appName;
_oldVersion = oldVersion;
}
@Override
public void update() {
boolean torrentSeed = updateCheck(_torrentURI.toString(), true);
if (!torrentSeed) {
// No torrent update available, just use the HTTP update
super.update();
return;
} else {
// There's a torrent available
//_mgr.notifyCheckComplete(this, true, true);
boolean httpSeed = updateCheck(_currentURI.toString(), false);
// use the HTTP-only check to determine if there's an update available using
// PartialEepGet
if (httpSeed) {
// Here we know there is an update and a corresponding torrent.
// We will check if the versions match before we peform the real update.
_mgr.notifyCheckComplete(this, httpSeed, torrentSeed);
} else {
// HTTP-only update check failed, we know nothing(special)
_mgr.notifyCheckComplete(this, false, false);
}
}
}
protected boolean updateCheck(String uriString, boolean torrent) {
if (_torrentURI != null)
return false;
_isPartial = torrent;
// performs exactly the same update check as PluginUpdateChecker
// but returns a boolean value
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST,
ConfigUpdateHandler.DEFAULT_PROXY_HOST);
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
if (proxyPort == ConfigUpdateHandler.DEFAULT_PROXY_PORT_INT &&
proxyHost.equals(ConfigUpdateHandler.DEFAULT_PROXY_HOST) &&
_context.portMapper().getPort(PortMapper.SVC_HTTP_PROXY) < 0) {
String msg = _t("HTTP client proxy tunnel must be running");
if (_log.shouldWarn())
_log.warn(msg);
updateStatus("<b>" + msg + "</b>");
return false;
}
updateStatus("<b>" + _t("Checking for update of plugin {0}", _appName) + "</b>");
_baos.reset();
try {
if (_isPartial)
_get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, uriString, TrustedUpdate.HEADER_BYTES);
else
_get = new EepGet(_context, true, proxyHost, proxyPort,
0, 0, 0, null, _baos,
uriString, false, null, null);
if (torrent)
_get.addStatusListener(this);
return _get.fetch(CONNECT_TIMEOUT);
} catch (Throwable t) {
_log.error("Error checking update for plugin", t);
}
return false;
}
}

View File

@ -0,0 +1,456 @@
package net.i2p.router.update;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.IllegalArgumentException;
import java.net.URI;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import net.i2p.CoreVersion;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.crypto.SU3File;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.SigningPublicKey;
import net.i2p.router.RouterContext;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.router.web.Messages;
import net.i2p.router.web.PluginStarter;
import net.i2p.router.web.RouterConsoleRunner;
import net.i2p.update.*;
import net.i2p.util.EepGet;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.PortMapper;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SecureFile;
import net.i2p.util.SimpleTimer2;
import net.i2p.util.SystemVersion;
import net.i2p.util.VersionComparator;
import org.klomp.snark.BandwidthListener;
import org.klomp.snark.BitField;
import org.klomp.snark.CompleteListener;
import org.klomp.snark.I2PSnarkUtil;
import org.klomp.snark.MagnetURI;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Snark;
import org.klomp.snark.SnarkManager;
import org.klomp.snark.Storage;
import org.klomp.snark.comments.CommentSet;
/**
* Check for an updated version of a plugin.
* A plugin is a standard .sud file with a 40-byte signature,
* a 16-byte version, and a .zip file.
*
* So we get the current version and update URL for the installed plugin,
* then fetch the first 56 bytes of the URL, extract the version,
* and compare.
*
* uri list must not be empty.
*
* Moved from web/ and turned into an UpdateTask.
*
* @since 0.9.4 moved from PluginUpdateHandler
*/
class PluginUpdateTorrentRunner extends PluginUpdateRunner implements CompleteListener {
private static final long MAX_LENGTH = 128 * 1024 * 1024;
private static final long METAINFO_TIMEOUT = 60 * 60 * 1000;
private static final long COMPLETE_TIMEOUT = 12 * 60 * 60 * 1000;
private static final long CHECK_INTERVAL = 3 * 60 * 1000;
private final String _xpi2pURLTorrent;
private boolean _isComplete = false;
private boolean _hasMetaInfo = false;
private String _errMsg = "";
private static final String XPI2P = "app.xpi2p";
private static final String ZIP = XPI2P + ".zip";
public static final String PLUGIN_DIR = PluginStarter.PLUGIN_DIR;
private static final String PROP_ALLOW_NEW_KEYS = "routerconsole.allowUntrustedPlugins";
private Snark _snark = null;
public PluginUpdateTorrentRunner(RouterContext ctx, ConsoleUpdateManager mgr, List<URI> uris,
String appName, String oldVersion) {
super(ctx, mgr, uris, appName, oldVersion);
if (uris.size() == 2)
_xpi2pURLTorrent = uris.get(1).toString();
else
_xpi2pURLTorrent = "";
}
@Override
protected void update() {
_updated = false;
SnarkManager _smgr = getSnarkManager();
if (_smgr == null) {
super.update();
return;
}
if (_xpi2pURLTorrent.endsWith(".torrent")) {
updateStatus("<b>" + _t("Downloading plugin from {0}", _xpi2pURLTorrent) + "</b>");
try {
_get = new EepGet(_context, 1, _updateFile, _xpi2pURLTorrent, false);
_get.fetch(CONNECT_TIMEOUT, -1, true ? INACTIVITY_TIMEOUT : NOPROXY_INACTIVITY_TIMEOUT);
File uf = new File(_updateFile);
if (uf.exists()) {
FileInputStream fis = new FileInputStream(uf);
MetaInfo torrent = new MetaInfo(fis);
fis.close();
MagnetURI magnet = new MagnetURI(_smgr.util(), torrent.toMagnetURI());
byte[] ih = torrent.getInfoHash();
_snark = _smgr.getTorrentByInfoHash(ih);
if (_snark != null) {
updateStatus(_snark);
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();
}
}
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?
_mgr.notifyAttemptFailed(this, "No tracker, no DHT, no OT", null);
}
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, null, this);
if (_snark != null) {
updateStatus(
"<b>" + _smgr.util().getString("Updating from {0}", linkify(torrent.toMagnetURI()))
+ "</b>");
new Timeout();
}
_updated = true;
}
} catch (Throwable t) {
_log.error("Error downloading plugin", t);
}
} else {
// attempt an HTTP update using the _xpi2pURL
super.update();
return;
}
if (_updated) {
_mgr.notifyComplete(this, _newVersion, null);
_mgr.notifyComplete(this, _errMsg);
} else {
_mgr.notifyTaskFailed(this, _errMsg, null);
}
}
/**
* Overridden to change the "Updating I2P" text in super
*
* @since 0.9.35
*/
@Override
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining,
String url) {
long d = currentWrite + bytesTransferred;
String status = "<b>" + _t("Downloading plugin") + ": " + _appDisplayName + "</b>";
_mgr.notifyProgress(this, status, d, d + bytesRemaining);
}
@Override
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url,
String outputFile, boolean notModified) {
if (!(_xpi2pURL.startsWith("file:") || _method == UpdateMethod.FILE))
updateStatus("<b>" + _t("Plugin downloaded") + ": " + _appDisplayName + "</b>");
SnarkManager _smgr = getSnarkManager();
if (_smgr == null) {
_log.error("No SnarkManager, can't find plugin update");
super.transferComplete(alreadyTransferred, bytesTransferred, bytesRemaining, url, outputFile, notModified);
return;
}
String file = _snark.getBaseName();
File f = new File(_smgr.getDataDir(), file);
File appDir = new SecureDirectory(_context.getConfigDir(), PLUGIN_DIR);
if ((!appDir.exists()) && (!appDir.mkdir())) {
f.delete();
statusDone("<b>" + _t("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + "</b>");
return;
}
boolean isSU3;
try {
isSU3 = isSU3File(f);
} catch (IOException ioe) {
f.delete();
statusDone("<b>" + ioe + "</b>");
return;
}
if (isSU3)
processSU3(f, appDir, url);
else
processSUD(f, appDir, url);
}
private void statusDone(String msg) {
// if we fail, we will pass this back in notifyTaskFailed()
_errMsg = msg;
updateStatus(msg);
}
private SnarkManager getSnarkManager() {
return new SnarkManager(_context);
}
public BandwidthListener getBandwidthListener() {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return null;
return _smgr.getBandwidthListener();
}
public boolean shouldAutoStart() {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return false;
return _smgr.shouldAutoStart();
}
public void locked_saveComments(Snark snark, CommentSet comments) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return;
_smgr.locked_saveComments(snark, comments);
}
@Override
public void torrentComplete(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return;
_smgr.torrentComplete(snark);
}
@Override
public void updateStatus(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return;
_smgr.updateStatus(snark);
}
@Override
public String gotMetaInfo(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return null;
return _smgr.gotMetaInfo(snark);
}
@Override
public void fatal(Snark snark, String error) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return;
_smgr.fatal(snark, error);
}
@Override
public void addMessage(Snark snark, String message) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return;
_smgr.addMessage(snark, message);
}
@Override
public void gotPiece(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return;
_smgr.gotPiece(snark);
}
@Override
public long getSavedTorrentTime(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return 0;
return _smgr.getSavedTorrentTime(snark);
}
@Override
public BitField getSavedTorrentBitField(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return null;
return _smgr.getSavedTorrentBitField(snark);
}
@Override
public boolean getSavedPreserveNamesSetting(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return false;
return _smgr.getSavedPreserveNamesSetting(snark);
}
@Override
public long getSavedUploaded(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return 0;
return _smgr.getSavedUploaded(snark);
}
@Override
public CommentSet getSavedComments(Snark snark) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null)
return null;
return _smgr.getSavedComments(snark);
}
@Override
public void start() {
if (_snark != null)
_snark.startTorrent();
}
private void processComplete(Snark snark) {
String url = _snark.getMetaInfo().toMagnetURI();
SnarkManager _smgr = getSnarkManager();
if (_smgr == null) {
_log.warn("No SnarkManager");
return;
}
String dataFile = snark.getBaseName();
File f = new File(_smgr.getDataDir(), dataFile);
String sudVersion = TrustedUpdate.getVersionString(f);
if (_newVersion.equals(sudVersion))
_mgr.notifyComplete(this, _newVersion, f);
else
fatal("version mismatch");
_isComplete = true;
long alreadyTransferred = f.getAbsoluteFile().length();
transferComplete(alreadyTransferred, alreadyTransferred, 0, _xpi2pURL, null, false);
}
/**
* 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) {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null) {
_log.warn("No SnarkManager");
return;
}
if (_snark != null) {
if (_hasMetaInfo) {
// avoid loop stopTorrent() ... updateStatus() ... fatal() ...
if (!_snark.isStopped())
_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);
}
}
_mgr.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 notifyProgress() {
SnarkManager _smgr = getSnarkManager();
if (_smgr == null) {
_log.warn("No SnarkManager");
return;
}
if (_hasMetaInfo && _snark != null) {
long total = _snark.getTotalLength();
long remaining = _snark.getRemainingLength();
long transferred = total - remaining;
String status = "<b>" + _smgr.util().getString("Updating") + "</b>";
_mgr.notifyProgress(this, status, total - remaining, total);
bytesTransferred(total, 0, transferred, remaining, _xpi2pURLTorrent);
}
}
}

View File

@ -379,7 +379,7 @@
<copy file="apps/desktopgui/dist/desktopgui.jar" todir="build/" />
</target>
<target name="buildRouterConsole" depends="buildrouter, buildSystray, buildDesktopGui, buildJetty, buildJrobin" >
<target name="buildRouterConsole" depends="buildrouter, buildSystray, buildDesktopGui, buildJetty, buildJrobin, i2psnark" >
<ant dir="apps/routerconsole/java/" target="jar" />
</target>