package net.i2p.router.networkdb.reseed;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* Moved from RouterConsoleRunner.java
*
* Reseeding is not strictly a router function, it used to be
* in the routerconsole app, but this made it impossible to
* bootstrap an embedded router lacking a routerconsole,
* in iMule or android for example, without additional modifications.
*
* Also, as this is now called from PersistentDataStore, not from the
* routerconsole, we can get started as soon as the netdb has read
* the netDb/ directory, not when the console starts.
*/
public class ReseedChecker {
private final RouterContext _context;
private final Log _log;
private final AtomicBoolean _inProgress = new AtomicBoolean();
private volatile String _lastStatus = "";
private volatile String _lastError = "";
private volatile boolean _networkLogged;
public static final int MINIMUM = 50;
private static final long STATUS_CLEAN_TIME = 20*60*1000;
/**
* 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, includes us
* @return true if a reseed was started
*/
public boolean checkReseed(int count) {
if (count >= MINIMUM)
return false;
if (_context.getBooleanProperty(Reseeder.PROP_DISABLE)) {
int x = count - 1; // us
// no ngettext, this is rare
String s;
if (x > 0)
s = "Only " + x + " peers remaining but reseed disabled by configuration";
else
s = "No peers remaining but reseed disabled by configuration";
_lastError = s;
_log.logAlways(Log.WARN, s);
return false;
}
if (_context.router().gracefulShutdownInProgress()) {
int x = count - 1;
// no ngettext, this is rare
String s;
if (x > 0)
s = "Only " + x + " peers remaining but reseed disabled by shutdown in progress";
else
s = "No peers remaining but reseed disabled by shutdown in progress";
_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");
if (!noReseedFile.exists() && !noReseedFileAlt1.exists() && !noReseedFileAlt2.exists() && !noReseedFileAlt3.exists()) {
if (!Addresses.isConnected()) {
if (!_networkLogged) {
_log.logAlways(Log.WARN, "Cannot reseed, no network connection");
_networkLogged = true;
}
return false;
}
_networkLogged = false;
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");
return requestReseed();
} else {
int x = count - 1; // us
// no ngettext, this is rare
String s;
if (x > 0)
s = "Only " + x + " peers remaining but reseed disabled by config file";
else
s = "No 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 progress");
return false;
}
}
/**
* Start a reseed from a zip or su3 URI.
*
* @return true if a reseed was started, false if already in progress
* @throws IllegalArgumentException if it doesn't end with zip or su3
* @since 0.9.19
*/
public boolean requestReseed(URI url) throws IllegalArgumentException {
if (_inProgress.compareAndSet(false, true)) {
Reseeder reseeder = new Reseeder(_context, this);
try {
reseeder.requestReseed(url);
return true;
} catch (IllegalArgumentException iae) {
if (iae.getMessage() != null)
setError(DataHelper.escapeHTML(iae.getMessage()));
done();
throw iae;
} catch (Throwable t) {
_log.error("Reseed failed to start", t);
done();
return false;
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Reseed already in progress");
return false;
}
}
/**
* Reseed from a zip or su3 input stream. Blocking.
*
* @return true if a reseed was started, false if already in progress
* @throws IOException if already in progress or on most other errors
* @since 0.9.19
*/
public int requestReseed(InputStream in) throws IOException {
// don't really need to check for in progress here
if (_inProgress.compareAndSet(false, true)) {
try {
Reseeder reseeder = new Reseeder(_context, this);
return reseeder.requestReseed(in);
} catch (IOException ioe) {
if (ioe.getMessage() != null)
setError(DataHelper.escapeHTML(ioe.getMessage()));
throw ioe;
} finally {
done();
}
} else {
throw new IOException("Reseed already in progress");
}
}
/** .
* 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);
_context.simpleTimer2().addEvent(new StatusCleaner(_lastStatus, _lastError), STATUS_CLEAN_TIME);
}
/**
* Status from current reseed attempt,
* probably empty if no reseed in progress.
* May include HTML.
*
* @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.
* May include HTML.
*
* @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;
}
/**
* @since 0.9.19
*/
private class StatusCleaner implements SimpleTimer.TimedEvent {
private final String _status, _error;
public StatusCleaner(String status, String error) {
_status = status;
_error = error;
}
public void timeReached() {
if (_status.equals(getStatus()))
setStatus("");
if (_error.equals(getError()))
setError("");
}
}
}