forked from I2P_Developers/i2p.i2p
i2psnark standalone: Fixes for router startup and shutdown
so that torrents stop when the router stops and restart when the router restarts. - Use BWLimits from the DirMonitor as a periodic test that the router is there - DirMonitor does not attempt to autostart torrents if BWLimits test fails - DirMonitor does autostart existing torrents when BWLimits test passes again - Register disconnect listener with socket manger and stop all torrents on disconnect - Use stopTorrent(true) on router errors to prevent changing the persisted torrent running status - Change autostart default to true for standalone Possibly more todo for corner cases or other start/stop/fail scenarios.
This commit is contained in:
@ -23,6 +23,7 @@ import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketEepGet;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base32;
|
||||
@ -47,7 +48,7 @@ import org.klomp.snark.dht.KRPC;
|
||||
* so we can run multiple instances of single Snarks
|
||||
* (but not multiple SnarkManagers, it is still static)
|
||||
*/
|
||||
public class I2PSnarkUtil {
|
||||
public class I2PSnarkUtil implements DisconnectListener {
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
private final String _baseName;
|
||||
@ -75,6 +76,7 @@ public class I2PSnarkUtil {
|
||||
private List<String> _openTrackers;
|
||||
private DHT _dht;
|
||||
private long _startedTime;
|
||||
private final DisconnectListener _discon;
|
||||
|
||||
private static final int EEPGET_CONNECT_TIMEOUT = 45*1000;
|
||||
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
|
||||
@ -91,17 +93,18 @@ public class I2PSnarkUtil {
|
||||
|
||||
|
||||
public I2PSnarkUtil(I2PAppContext ctx) {
|
||||
this(ctx, "i2psnark");
|
||||
this(ctx, "i2psnark", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseName generally "i2psnark"
|
||||
* @since Jetty 7
|
||||
*/
|
||||
public I2PSnarkUtil(I2PAppContext ctx, String baseName) {
|
||||
public I2PSnarkUtil(I2PAppContext ctx, String baseName, DisconnectListener discon) {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
_baseName = baseName;
|
||||
_discon = discon;
|
||||
_opts = new HashMap<String, String>();
|
||||
//setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", I2PClient.DEFAULT_LISTEN_PORT, null);
|
||||
@ -324,8 +327,11 @@ public class I2PSnarkUtil {
|
||||
if (opts.getProperty(I2PClient.PROP_GZIP) == null)
|
||||
opts.setProperty(I2PClient.PROP_GZIP, "false");
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||
if (_manager != null)
|
||||
if (_manager != null) {
|
||||
_startedTime = _context.clock().now();
|
||||
if (_discon != null)
|
||||
_manager.addDisconnectListener(this);
|
||||
}
|
||||
_connecting = false;
|
||||
}
|
||||
if (_shouldUseDHT && _manager != null && _dht == null)
|
||||
@ -333,6 +339,19 @@ public class I2PSnarkUtil {
|
||||
return (_manager != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* DisconnectListener interface
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void sessionDisconnected() {
|
||||
synchronized(this) {
|
||||
_manager = null;
|
||||
_connecting = false;
|
||||
}
|
||||
if (_discon != null)
|
||||
_discon.sessionDisconnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if disabled or not started
|
||||
* @since 0.8.4
|
||||
|
@ -1259,7 +1259,7 @@ public class Snark
|
||||
*/
|
||||
private void fatalRouter(String s, Throwable t) throws RouterException {
|
||||
_log.error(s, t);
|
||||
stopTorrent();
|
||||
stopTorrent(true);
|
||||
if (completeListener != null)
|
||||
completeListener.fatal(this, s);
|
||||
throw new RouterException(s, t);
|
||||
|
@ -31,6 +31,7 @@ import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.ClientAppState;
|
||||
import net.i2p.app.NotificationService;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener;
|
||||
import net.i2p.crypto.SHA1Hash;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base64;
|
||||
@ -58,7 +59,7 @@ import org.klomp.snark.dht.KRPC;
|
||||
/**
|
||||
* Manage multiple snarks
|
||||
*/
|
||||
public class SnarkManager implements CompleteListener, ClientApp {
|
||||
public class SnarkManager implements CompleteListener, ClientApp, DisconnectListener {
|
||||
|
||||
/**
|
||||
* Map of (canonical) filename of the .torrent file to Snark instance.
|
||||
@ -131,7 +132,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
public static final String PROP_FILES_PUBLIC = "i2psnark.filesPublic";
|
||||
public static final String PROP_OLD_AUTO_START = "i2snark.autoStart"; // oops
|
||||
public static final String PROP_AUTO_START = "i2psnark.autoStart"; // convert in migration to new config file
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
private final boolean DEFAULT_AUTO_START;
|
||||
//public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix";
|
||||
//public static final String DEFAULT_LINK_PREFIX = "file:///";
|
||||
public static final String PROP_STARTUP_DELAY = "i2psnark.startupDelay";
|
||||
@ -269,7 +270,8 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
_contextName = ctxName;
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new UIMessages(MAX_MESSAGES);
|
||||
_util = new I2PSnarkUtil(_context, ctxName);
|
||||
_util = new I2PSnarkUtil(_context, ctxName, this);
|
||||
DEFAULT_AUTO_START = !ctx.isRouterContext();
|
||||
String cfile = ctxName + CONFIG_FILE_SUFFIX;
|
||||
File configFile = new File(cfile);
|
||||
if (!configFile.isAbsolute())
|
||||
@ -344,6 +346,18 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DisconnectListener interface
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void sessionDisconnected() {
|
||||
if (!_context.isRouterContext()) {
|
||||
addMessage(_t("Unable to connect to I2P"));
|
||||
stopAllTorrents(true);
|
||||
_stopping = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by the webapp at Jetty shutdown.
|
||||
* Stops all torrents. Does not close the tunnel, so the announces have a chance.
|
||||
@ -465,7 +479,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
}
|
||||
|
||||
public boolean shouldAutoStart() {
|
||||
return Boolean.parseBoolean(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START));
|
||||
return Boolean.parseBoolean(_config.getProperty(PROP_AUTO_START, Boolean.toString(DEFAULT_AUTO_START)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -816,7 +830,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
if (!_config.containsKey(PROP_DIR))
|
||||
_config.setProperty(PROP_DIR, _contextName);
|
||||
if (!_config.containsKey(PROP_AUTO_START))
|
||||
_config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START);
|
||||
_config.setProperty(PROP_AUTO_START, Boolean.toString(DEFAULT_AUTO_START));
|
||||
if (!_config.containsKey(PROP_REFRESH_DELAY))
|
||||
_config.setProperty(PROP_REFRESH_DELAY, Integer.toString(DEFAULT_REFRESH_DELAY_SECS));
|
||||
if (!_config.containsKey(PROP_STARTUP_DELAY))
|
||||
@ -921,13 +935,22 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
}
|
||||
|
||||
|
||||
/** call from DirMonitor since loadConfig() is called before router I2CP is up */
|
||||
private void getBWLimit() {
|
||||
if (!_config.containsKey(PROP_UPBW_MAX)) {
|
||||
/**
|
||||
* Call from DirMonitor since loadConfig() is called before router I2CP is up.
|
||||
* We also use this as a test that the router is there for standalone.
|
||||
*
|
||||
* @return true if we got a response from the router
|
||||
*/
|
||||
private boolean getBWLimit() {
|
||||
boolean shouldSet = !_config.containsKey(PROP_UPBW_MAX);
|
||||
if (shouldSet || !_context.isRouterContext()) {
|
||||
int[] limits = BWLimits.getBWLimits(_util.getI2CPHost(), _util.getI2CPPort());
|
||||
if (limits != null && limits[1] > 0)
|
||||
if (limits == null)
|
||||
return false;
|
||||
if (shouldSet && limits[1] > 0)
|
||||
_util.setMaxUpBW(limits[1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateConfig() {
|
||||
@ -1578,7 +1601,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
}
|
||||
if (dataDir == null)
|
||||
dataDir = getDataDir();
|
||||
Snark torrent = null;
|
||||
Snark torrent;
|
||||
synchronized (_snarks) {
|
||||
torrent = _snarks.get(filename);
|
||||
}
|
||||
@ -2497,6 +2520,13 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
addMessage(_t("Torrent removed: \"{0}\"", torrent.getBaseName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This calls monitorTorrents() once a minute.
|
||||
* It also gets the bandwidth limits and loads magnets on first run.
|
||||
* For standalone, it also handles checking that the external router is there,
|
||||
* and restarting torrents once the router appears.
|
||||
*
|
||||
*/
|
||||
private class DirMonitor implements Runnable {
|
||||
public void run() {
|
||||
// don't bother delaying if auto start is false
|
||||
@ -2511,17 +2541,65 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
|
||||
// here because we need to delay until I2CP is up
|
||||
// although the user will see the default until then
|
||||
getBWLimit();
|
||||
boolean routerOK = false;
|
||||
boolean doMagnets = true;
|
||||
while (_running) {
|
||||
File dir = getDataDir();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Directory Monitor loop over " + dir.getAbsolutePath());
|
||||
if (routerOK &&
|
||||
(_context.isRouterContext() || _util.connected() || _util.isConnecting())) {
|
||||
autostart = shouldAutoStart();
|
||||
} else {
|
||||
// Test if the router is there
|
||||
// For standalone, this will probe the router every 60 seconds if not connected
|
||||
boolean oldOK = routerOK;
|
||||
routerOK = getBWLimit();
|
||||
if (routerOK) {
|
||||
autostart = shouldAutoStart();
|
||||
if (autostart && !oldOK && !doMagnets && !_snarks.isEmpty()) {
|
||||
// Start previously added torrents
|
||||
for (Snark snark : _snarks.values()) {
|
||||
Properties config = getConfig(snark);
|
||||
String prop = config.getProperty(PROP_META_RUNNING);
|
||||
if (prop == null || Boolean.parseBoolean(prop)) {
|
||||
if (!_util.connected()) {
|
||||
addMessage(_t("Connecting to I2P"));
|
||||
// getBWLimit() was successful so this should work
|
||||
boolean ok = _util.connect();
|
||||
if (!ok) {
|
||||
if (_context.isRouterContext())
|
||||
addMessage(_t("Unable to connect to I2P"));
|
||||
else
|
||||
addMessage(_t("Error connecting to I2P - check your I2CP settings!") + ' ' + _util.getI2CPHost() + ':' + _util.getI2CPPort());
|
||||
routerOK = false;
|
||||
autostart = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
addMessageNoEscape(_t("Starting up torrent {0}", linkify(snark)));
|
||||
try {
|
||||
snark.startTorrent();
|
||||
} catch (Snark.RouterException re) {
|
||||
// Snark.fatal() will log and call fatal() here for user message before throwing
|
||||
break;
|
||||
} catch (RuntimeException re) {
|
||||
// Snark.fatal() will log and call fatal() here for user message before throwing
|
||||
}
|
||||
}
|
||||
}
|
||||
if (routerOK)
|
||||
addMessage(_t("Up bandwidth limit is {0} KBps", _util.getMaxUpBW()));
|
||||
}
|
||||
} else {
|
||||
autostart = false;
|
||||
}
|
||||
}
|
||||
boolean ok;
|
||||
try {
|
||||
// Don't let this interfere with .torrent files being added or deleted
|
||||
synchronized (_snarks) {
|
||||
ok = monitorTorrents(dir);
|
||||
ok = monitorTorrents(dir, autostart);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
_log.error("Error in the DirectoryMonitor", e);
|
||||
@ -2535,7 +2613,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
} catch (RuntimeException e) {
|
||||
_log.error("Error in the DirectoryMonitor", e);
|
||||
}
|
||||
if (!_snarks.isEmpty())
|
||||
if (routerOK && !_snarks.isEmpty())
|
||||
addMessage(_t("Up bandwidth limit is {0} KBps", _util.getMaxUpBW()));
|
||||
// To fix bug where files were left behind,
|
||||
// but also good for when user removes snarks when i2p is not running
|
||||
@ -2544,6 +2622,12 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
// time i2psnark starts. See ticket #1658.
|
||||
if (ok)
|
||||
cleanupTorrentStatus();
|
||||
if (!routerOK) {
|
||||
if (_context.isRouterContext())
|
||||
addMessage(_t("Unable to connect to I2P"));
|
||||
else
|
||||
addMessage(_t("Error connecting to I2P - check your I2CP settings!") + ' ' + _util.getI2CPHost() + ':' + _util.getI2CPPort());
|
||||
}
|
||||
}
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
@ -2719,9 +2803,10 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
/**
|
||||
* caller must synchronize on _snarks
|
||||
*
|
||||
* @param shouldStart should we autostart the torrents
|
||||
* @return success, false if an error adding any torrent.
|
||||
*/
|
||||
private boolean monitorTorrents(File dir) {
|
||||
private boolean monitorTorrents(File dir, boolean shouldStart) {
|
||||
boolean rv = true;
|
||||
File files[] = dir.listFiles(new FileSuffixFilter(".torrent"));
|
||||
List<String> foundNames = new ArrayList<String>(0);
|
||||
@ -2741,7 +2826,6 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("DirMon found: " + DataHelper.toString(foundNames) + " existing: " + DataHelper.toString(existingNames));
|
||||
// lets find new ones first...
|
||||
boolean shouldStart = shouldAutoStart();
|
||||
for (String name : foundNames) {
|
||||
if (existingNames.contains(name)) {
|
||||
// already known. noop
|
||||
|
Reference in New Issue
Block a user