diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java index 9d28994f39..53da37bc7a 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java @@ -20,13 +20,11 @@ public class ConfigReseedHandler extends FormHandler { if (_action.equals(_("Save changes and reseed now"))) { saveChanges(); - boolean reseedInProgress = Boolean.valueOf(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress")).booleanValue(); - if (reseedInProgress) { + if (!_context.netDb().reseedChecker().requestReseed()) { addFormError(_("Reseeding is already in progress")); } else { // skip the nonce checking in ReseedHandler addFormNotice(_("Starting reseed process")); - (new ReseedHandler(_context)).requestReseed(); } return; } @@ -74,8 +72,15 @@ public class ConfigReseedHandler extends FormHandler { saveString(Reseeder.PROP_SPROXY_PASSWORD, "spassword"); saveBoolean(Reseeder.PROP_SPROXY_AUTH_ENABLE, "sauth"); String url = getJettyString("reseedURL"); - if (url != null) - changes.put(Reseeder.PROP_RESEED_URL, url.trim().replace("\r\n", ",").replace("\n", ",")); + if (url != null) { + url = url.trim().replace("\r\n", ",").replace("\n", ","); + if (url.length() <= 0) { + addFormNotice("Restoring default URLs"); + removes.add(Reseeder.PROP_RESEED_URL); + } else { + changes.put(Reseeder.PROP_RESEED_URL, url); + } + } String mode = getJettyString("mode"); boolean req = "1".equals(mode); boolean disabled = "2".equals(mode); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ReseedHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ReseedHandler.java index 31c5efc413..9d3fce6c4a 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ReseedHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ReseedHandler.java @@ -24,11 +24,7 @@ public class ReseedHandler extends HelperBase { } } - public void requestReseed() { - synchronized (ReseedHandler.class) { - if (_reseedRunner == null) - _reseedRunner = new Reseeder(_context); - _reseedRunner.requestReseed(); - } + private void requestReseed() { + _context.netDb().reseedChecker().requestReseed(); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java index cfafb6c7e6..a24c341991 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java @@ -676,12 +676,12 @@ public class SummaryHelper extends HelperBase { .append(""); } - boolean reseedInProgress = Boolean.valueOf(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress")).booleanValue(); + boolean reseedInProgress = _context.netDb().reseedChecker().inProgress(); // If showing the reseed link is allowed if (allowReseed()) { if (reseedInProgress) { // While reseed occurring, show status message instead - buf.append("").append(System.getProperty("net.i2p.router.web.ReseedHandler.statusMessage","")).append("
"); + buf.append("").append(_context.netDb().reseedChecker().getStatus()).append("
"); } else { // While no reseed occurring, show reseed link long nonce = _context.random().nextLong(); @@ -696,7 +696,7 @@ public class SummaryHelper extends HelperBase { } // If a new reseed ain't running, and the last reseed had errors, show error message if (!reseedInProgress) { - String reseedErrorMessage = System.getProperty("net.i2p.router.web.ReseedHandler.errorMessage",""); + String reseedErrorMessage = _context.netDb().reseedChecker().getError(); if (reseedErrorMessage.length() > 0) { buf.append("").append(reseedErrorMessage).append("
"); } diff --git a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java index 741f949534..f1fbd1b9fd 100644 --- a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java @@ -17,6 +17,7 @@ import net.i2p.data.DatabaseEntry; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.RouterInfo; +import net.i2p.router.networkdb.reseed.ReseedChecker; /** * Defines the mechanism for interacting with I2P's network database @@ -73,4 +74,7 @@ public abstract class NetworkDatabaseFacade implements Service { public Set getLeases() { return Collections.EMPTY_SET; } /** public for NetDbRenderer in routerconsole */ public Set getRouters() { return Collections.EMPTY_SET; } + + /** @since 0.9 */ + public ReseedChecker reseedChecker() { return null; }; } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index d49e0a9abd..74dbbc4282 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -42,6 +42,7 @@ import net.i2p.router.TunnelPoolSettings; import net.i2p.router.networkdb.DatabaseLookupMessageHandler; import net.i2p.router.networkdb.DatabaseStoreMessageHandler; import net.i2p.router.networkdb.PublishLocalRouterInfoJob; +import net.i2p.router.networkdb.reseed.ReseedChecker; import net.i2p.router.peermanager.PeerProfile; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; @@ -67,6 +68,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { private long _lastExploreNew; protected final PeerSelector _peerSelector; protected final RouterContext _context; + private final ReseedChecker _reseedChecker; + /** * Map of Hash to RepublishLeaseSetJob for leases we'realready managing. * This is added to when we create a new RepublishLeaseSetJob, and the values are @@ -146,6 +149,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _publishingLeaseSets = new HashMap(8); _activeRequests = new HashMap(8); _enforceNetId = DEFAULT_ENFORCE_NETID; + _reseedChecker = new ReseedChecker(context); context.statManager().createRateStat("netDb.lookupLeaseSetDeferred", "how many lookups are deferred for a single leaseSet lookup?", "NetworkDatabase", new long[] { 60*60*1000 }); context.statManager().createRateStat("netDb.exploreKeySet", "how many keys are queued for exploration?", "NetworkDatabase", new long[] { 60*60*1000 }); // following are for StoreJob @@ -167,6 +171,12 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { protected PeerSelector createPeerSelector() { return new PeerSelector(_context); } public PeerSelector getPeerSelector() { return _peerSelector; } + /** @since 0.9 */ + @Override + public ReseedChecker reseedChecker() { + return _reseedChecker; + } + KBucketSet getKBuckets() { return _kb; } DataStore getDataStore() { return _ds; } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 6353d7a595..f5881c7dae 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -382,7 +382,7 @@ class PersistentDataStore extends TransientDataStore { } if (!_alreadyWarned) { - ReseedChecker.checkReseed(_context, routerCount); + _facade.reseedChecker().checkReseed(routerCount); _alreadyWarned = true; _initialized = true; } diff --git a/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java b/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java index ea3e0408d3..efb9b33e21 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java @@ -1,6 +1,7 @@ package net.i2p.router.networkdb.reseed; import java.io.File; +import java.util.concurrent.atomic.AtomicBoolean; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -19,29 +20,144 @@ import net.i2p.util.Log; */ public class ReseedChecker { + private final RouterContext _context; + private final Log _log; + private final AtomicBoolean _inProgress = new AtomicBoolean(); + private String _lastStatus = ""; + private String _lastError = ""; + private static final int MINIMUM = 15; - public static void checkReseed(RouterContext context, int count) { + /** + * All reseeding must be done through this instance. + * Access through context.netDb().reseedChecker(), others should not instantiate + * + * @since 0.9 + */ + public ReseedChecker(RouterContext context) { + _context = context; + _log = context.logManager().getLog(ReseedChecker.class); + } + + /** + * Check if a reseed is needed, and start it + * + * @param count current number of known routers + * @return true if a reseed was started + */ + public boolean checkReseed(int count) { if (count >= MINIMUM) - return; + return false; + + if (_context.getBooleanProperty(Reseeder.PROP_DISABLE)) { + String s = "Only " + count + " peers remaining but reseed disabled by configuration"; + _lastError = s; + _log.logAlways(Log.WARN, s); + return false; + } // we check the i2p installation directory for a flag telling us not to reseed, // but also check the home directory for that flag too, since new users installing i2p // don't have an installation directory that they can put the flag in yet. File noReseedFile = new File(new File(System.getProperty("user.home")), ".i2pnoreseed"); File noReseedFileAlt1 = new File(new File(System.getProperty("user.home")), "noreseed.i2p"); - File noReseedFileAlt2 = new File(context.getConfigDir(), ".i2pnoreseed"); - File noReseedFileAlt3 = new File(context.getConfigDir(), "noreseed.i2p"); - Log _log = context.logManager().getLog(ReseedChecker.class); + File noReseedFileAlt2 = new File(_context.getConfigDir(), ".i2pnoreseed"); + File noReseedFileAlt3 = new File(_context.getConfigDir(), "noreseed.i2p"); if (!noReseedFile.exists() && !noReseedFileAlt1.exists() && !noReseedFileAlt2.exists() && !noReseedFileAlt3.exists()) { if (count <= 1) _log.logAlways(Log.INFO, "Downloading peer router information for a new I2P installation"); else _log.logAlways(Log.WARN, "Very few known peers remaining - reseeding now"); - Reseeder reseeder = new Reseeder(context); - reseeder.requestReseed(); + return requestReseed(); } else { - _log.logAlways(Log.WARN, "Only " + count + " peers remaining but reseed disabled by config file"); + String s = "Only " + count + " peers remaining but reseed disabled by config file"; + _lastError = s; + _log.logAlways(Log.WARN, s); + return false; } } + + /** + * Start a reseed + * + * @return true if a reseed was started, false if already in progress + * @since 0.9 + */ + public boolean requestReseed() { + if (_inProgress.compareAndSet(false, true)) { + try { + Reseeder reseeder = new Reseeder(_context, this); + reseeder.requestReseed(); + return true; + } catch (Throwable t) { + _log.error("Reseed failed to start", t); + done(); + return false; + } + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Reseed already in prgress"); + return false; + } + } + + /** + * Is a reseed in progress? + * + * @since 0.9 + */ + public boolean inProgress() { + return _inProgress.get(); + } + + /** + * The reseed is complete + * + * @since 0.9 + */ + void done() { + _inProgress.set(false); + } + + /** + * Status from current reseed attempt, + * probably empty if no reseed in progress. + * + * @return non-null, may be empty + * @since 0.9 + */ + public String getStatus() { + return _lastStatus; + } + + /** + * Status from current reseed attempt + * + * @param s non-null, may be empty + * @since 0.9 + */ + void setStatus(String s) { + _lastStatus = s; + } + + /** + * Error from last or current reseed attempt + * + * @return non-null, may be empty + * @since 0.9 + */ + public String getError() { + return _lastError; + } + + /** + * Status from last or current reseed attempt + * + * @param s non-null, may be empty + * @since 0.9 + */ + void setError(String s) { + _lastError = s; + } + } diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index 0d896bc46f..cdce1e2908 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -42,10 +42,9 @@ import net.i2p.util.Translate; * the router log, and the wrapper log. */ public class Reseeder { - /** FIXME don't keep a static reference, store _isRunning some other way */ - private static ReseedRunner _reseedRunner; private final RouterContext _context; private final Log _log; + private final ReseedChecker _checker; // Reject unreasonably big files, because we download into a ByteArrayOutputStream. private static final long MAX_RESEED_RESPONSE_SIZE = 2 * 1024 * 1024; @@ -81,11 +80,6 @@ public class Reseeder { "https://75.145.125.59/netDb/" + "," + "https://i2p.mooo.com/netDb/"; - private static final String PROP_INPROGRESS = "net.i2p.router.web.ReseedHandler.reseedInProgress"; - /** the console shows this message while reseedInProgress == false */ - private static final String PROP_ERROR = "net.i2p.router.web.ReseedHandler.errorMessage"; - /** the console shows this message while reseedInProgress == true */ - private static final String PROP_STATUS = "net.i2p.router.web.ReseedHandler.statusMessage"; public static final String PROP_PROXY_HOST = "router.reseedProxyHost"; public static final String PROP_PROXY_PORT = "router.reseedProxyPort"; /** @since 0.8.2 */ @@ -106,32 +100,27 @@ public class Reseeder { public static final String PROP_SPROXY_USERNAME = "router.reseedSSLProxy.username"; public static final String PROP_SPROXY_PASSWORD = "router.reseedSSLProxy.password"; public static final String PROP_SPROXY_AUTH_ENABLE = "router.reseedSSLProxy.authEnable"; + /** @since 0.9 */ + public static final String PROP_DISABLE = "router.reseedDisable"; // from PersistentDataStore private static final String ROUTERINFO_PREFIX = "routerInfo-"; private static final String ROUTERINFO_SUFFIX = ".dat"; - public Reseeder(RouterContext ctx) { + Reseeder(RouterContext ctx, ReseedChecker rc) { _context = ctx; _log = ctx.logManager().getLog(Reseeder.class); + _checker = rc; } - public void requestReseed() { - synchronized (Reseeder.class) { - if (_reseedRunner == null) - _reseedRunner = new ReseedRunner(); - if (_reseedRunner.isRunning()) { - return; - } else { - // set to daemon so it doesn't hang a shutdown - Thread reseed = new I2PAppThread(_reseedRunner, "Reseed", true); - reseed.start(); - } - } - + void requestReseed() { + ReseedRunner reseedRunner = new ReseedRunner(); + // set to daemon so it doesn't hang a shutdown + Thread reseed = new I2PAppThread(reseedRunner, "Reseed", true); + reseed.start(); } - public class ReseedRunner implements Runnable, EepGet.StatusListener { + private class ReseedRunner implements Runnable, EepGet.StatusListener { private boolean _isRunning; private String _proxyHost; private int _proxyPort; @@ -143,20 +132,21 @@ public class Reseeder { public ReseedRunner() { } - public boolean isRunning() { return _isRunning; } - /* * Do it. - * We update PROP_ERROR here. */ public void run() { + try { + run2(); + } finally { + _checker.done(); + } + } + + private void run2() { _isRunning = true; - System.clearProperty(PROP_ERROR); - System.setProperty(PROP_STATUS, _("Reseeding")); - System.setProperty(PROP_INPROGRESS, "true"); - _attemptStarted = 0; - _gotDate = 0; - _sslState = null; // start fresh + _checker.setError(""); + _checker.setStatus(_("Reseeding")); if (_context.getBooleanProperty(PROP_PROXY_ENABLE)) { _proxyHost = _context.getProperty(PROP_PROXY_HOST); _proxyPort = _context.getProperty(PROP_PROXY_PORT, -1); @@ -165,24 +155,22 @@ public class Reseeder { int total = reseed(false); if (total >= 50) { System.out.println("Reseed complete, " + total + " received"); - System.clearProperty(PROP_ERROR); + _checker.setError(""); } else if (total > 0) { System.out.println("Reseed complete, only " + total + " received"); - System.setProperty(PROP_ERROR, ngettext("Reseed fetched only 1 router.", + _checker.setError(ngettext("Reseed fetched only 1 router.", "Reseed fetched only {0} routers.", total)); } else { System.out.println("Reseed failed, check network connection"); System.out.println( "Ensure that nothing blocks outbound HTTP, check the logs, " + "and if nothing helps, read the FAQ about reseeding manually."); - System.setProperty(PROP_ERROR, _("Reseed failed.") + ' ' + + _checker.setError(_("Reseed failed.") + ' ' + _("See {0} for help.", "" + _("reseed configuration page") + "")); } - System.setProperty(PROP_INPROGRESS, "false"); - System.clearProperty(PROP_STATUS); - _sslState = null; // don't hold ref _isRunning = false; + _checker.setStatus(""); } // EepGet status listeners @@ -311,7 +299,7 @@ public class Reseeder { * Jetty directory listings are not compatible, as they look like * HREF="/full/path/to/routerInfo-... * - * We update PROP_STATUS here. + * We update the status here. * * @param echoStatus apparently always false * @return count of routerinfos successfully fetched @@ -320,7 +308,7 @@ public class Reseeder { try { // Don't use context clock as we may be adjusting the time final long timeLimit = System.currentTimeMillis() + MAX_TIME_PER_HOST; - System.setProperty(PROP_STATUS, _("Reseeding: fetching seed URL.")); + _checker.setStatus(_("Reseeding: fetching seed URL.")); System.err.println("Reseeding from " + seedURL); URL dir = new URL(seedURL); byte contentRaw[] = readURL(dir); @@ -377,7 +365,7 @@ public class Reseeder { for (Iterator iter = urlList.iterator(); iter.hasNext() && fetched < 200 && System.currentTimeMillis() < timeLimit; ) { try { - System.setProperty(PROP_STATUS, + _checker.setStatus( _("Reseeding: fetching router info from seed URL ({0} successful, {1} errors).", fetched, errors)); if (!fetchSeed(seedURL, iter.next()))